From efed5968236a8ae3b26a7697e4972f243add4292 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:10:16 +0530 Subject: [PATCH 01/38] fix(connector): [Forte] Response Handling for Verify Action (#2601) Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> --- .../src/connector/forte/transformers.rs | 2 + .../.meta.json | 3 + .../Payments - Create/.event.meta.json | 3 + .../Payments - Create/event.test.js | 71 +++++++++++++++ .../Payments - Create/request.json | 89 +++++++++++++++++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 3 + .../Payments - Retrieve/event.test.js | 71 +++++++++++++++ .../Payments - Retrieve/request.json | 28 ++++++ .../Payments - Retrieve/response.json | 1 + .../QuickStart/Payments - Create/request.json | 4 + 11 files changed, 276 insertions(+) create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/.meta.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/event.test.js create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/request.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/response.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/response.json diff --git a/crates/router/src/connector/forte/transformers.rs b/crates/router/src/connector/forte/transformers.rs index 7851608d11b1..dd78324c9b8b 100644 --- a/crates/router/src/connector/forte/transformers.rs +++ b/crates/router/src/connector/forte/transformers.rs @@ -179,6 +179,7 @@ impl ForeignFrom<(ForteResponseCode, ForteAction)> for enums::AttemptStatus { ForteResponseCode::A01 => match action { ForteAction::Authorize => Self::Authorized, ForteAction::Sale => Self::Pending, + ForteAction::Verify => Self::Charged, }, ForteResponseCode::A05 | ForteResponseCode::A06 => Self::Authorizing, _ => Self::Failure, @@ -232,6 +233,7 @@ pub struct ResponseStatus { pub enum ForteAction { Sale, Authorize, + Verify, } #[derive(Debug, Deserialize)] diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/.meta.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/.meta.json new file mode 100644 index 000000000000..69b505c6d863 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/.meta.json @@ -0,0 +1,3 @@ +{ + "childrenOrder": ["Payments - Create", "Payments - Retrieve"] +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/.event.meta.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/event.test.js b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/event.test.js new file mode 100644 index 000000000000..bd103ebf78b4 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "processing" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/request.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/request.json new file mode 100644 index 000000000000..0bf23604d848 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/request.json @@ -0,0 +1,89 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 0, + "currency": "USD", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+65", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "routing": { + "type": "single", + "data": "forte" + }, + "return_url": "https://duck.com", + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "PiX", + "last_name": "Fix" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "PiX", + "last_name": "Fix" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/response.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/.event.meta.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/event.test.js b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..16efd2245bec --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "processing" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/request.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/request.json new file mode 100644 index 000000000000..6cd4b7d96c52 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/request.json @@ -0,0 +1,28 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": ["{{baseUrl}}"], + "path": ["payments", ":id"], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/response.json b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/forte/Flow Testcases/Happy Cases/Scenario7-Create payment with Zero Amount/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/forte/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/forte/Flow Testcases/QuickStart/Payments - Create/request.json index 05fde18c2d97..04be684704b6 100644 --- a/postman/collection-dir/forte/Flow Testcases/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/forte/Flow Testcases/QuickStart/Payments - Create/request.json @@ -21,6 +21,10 @@ "amount": 6540, "currency": "USD", "confirm": true, + "routing": { + "type": "single", + "data": "forte" + }, "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", "amount_to_capture": 6540, From 88e1f29dae13622bc58b8f5df1cd84b929b28ac6 Mon Sep 17 00:00:00 2001 From: Seemebadnekai <51400137+SagarDevAchar@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:58:06 +0530 Subject: [PATCH 02/38] feat(connector): [OpenNode] Currency Unit Conversion (#2645) --- crates/router/src/connector/opennode.rs | 12 +++- .../src/connector/opennode/transformers.rs | 60 +++++++++++++++---- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index 6fad0f472050..07d33382a21e 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -72,6 +72,10 @@ impl ConnectorCommon for Opennode { "opennode" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -169,7 +173,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = opennode::OpennodePaymentsRequest::try_from(req)?; + let connector_router_data = opennode::OpennodeRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = opennode::OpennodePaymentsRequest::try_from(&connector_router_data)?; let opennode_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index b367012ca75d..794fc8573417 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -10,6 +10,37 @@ use crate::{ types::{self, api, storage::enums}, }; +#[derive(Debug, Serialize)] +pub struct OpennodeRouterData { + pub amount: i64, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for OpennodeRouterData +{ + type Error = error_stack::Report; + + fn try_from( + (_currency_unit, _currency, amount, router_data): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount, + router_data, + }) + } +} + //TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct OpennodePaymentsRequest { @@ -22,9 +53,11 @@ pub struct OpennodePaymentsRequest { order_id: String, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for OpennodePaymentsRequest { +impl TryFrom<&OpennodeRouterData<&types::PaymentsAuthorizeRouterData>> for OpennodePaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item: &OpennodeRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { get_crypto_specific_payment_data(item) } } @@ -146,11 +179,13 @@ pub struct OpennodeRefundRequest { pub amount: i64, } -impl TryFrom<&types::RefundsRouterData> for OpennodeRefundRequest { +impl TryFrom<&OpennodeRouterData<&types::RefundsRouterData>> for OpennodeRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: &OpennodeRouterData<&types::RefundsRouterData>, + ) -> Result { Ok(Self { - amount: item.request.refund_amount, + amount: item.router_data.request.refund_amount, }) } } @@ -222,14 +257,15 @@ pub struct OpennodeErrorResponse { } fn get_crypto_specific_payment_data( - item: &types::PaymentsAuthorizeRouterData, + item: &OpennodeRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result> { - let amount = item.request.amount; - let currency = item.request.currency.to_string(); - let description = item.get_description()?; + let amount = item.amount; + let currency = item.router_data.request.currency.to_string(); + let description = item.router_data.get_description()?; let auto_settle = true; - let success_url = item.get_return_url()?; - let callback_url = item.request.get_webhook_url()?; + let success_url = item.router_data.get_return_url()?; + let callback_url = item.router_data.request.get_webhook_url()?; + let order_id = item.router_data.connector_request_reference_id.clone(); Ok(OpennodePaymentsRequest { amount, @@ -238,7 +274,7 @@ fn get_crypto_specific_payment_data( auto_settle, success_url, callback_url, - order_id: item.connector_request_reference_id.clone(), + order_id, }) } From 3578db7640d8eda8f063e11b8bb64452fb987eef Mon Sep 17 00:00:00 2001 From: Seemebadnekai <51400137+SagarDevAchar@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:25:35 +0530 Subject: [PATCH 03/38] feat(connector): [Mollie] Currency Unit Conversion (#2671) Co-authored-by: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> --- crates/router/src/connector/mollie.rs | 20 ++- .../src/connector/mollie/transformers.rs | 123 ++++++++++++------ 2 files changed, 99 insertions(+), 44 deletions(-) diff --git a/crates/router/src/connector/mollie.rs b/crates/router/src/connector/mollie.rs index 38abed9dea6a..337e1b78c9a9 100644 --- a/crates/router/src/connector/mollie.rs +++ b/crates/router/src/connector/mollie.rs @@ -62,6 +62,10 @@ impl ConnectorCommon for Mollie { "mollie" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { connectors.mollie.base_url.as_ref() } @@ -229,7 +233,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = mollie::MolliePaymentsRequest::try_from(req)?; + let router_obj = mollie::MollieRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = mollie::MolliePaymentsRequest::try_from(&router_obj)?; let mollie_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -417,7 +427,13 @@ impl ConnectorIntegration, ) -> CustomResult, errors::ConnectorError> { - let req_obj = mollie::MollieRefundRequest::try_from(req)?; + let router_obj = mollie::MollieRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = mollie::MollieRefundRequest::try_from(&router_obj)?; let mollie_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, diff --git a/crates/router/src/connector/mollie/transformers.rs b/crates/router/src/connector/mollie/transformers.rs index d0036d3c2f55..3c23c9f1d39b 100644 --- a/crates/router/src/connector/mollie/transformers.rs +++ b/crates/router/src/connector/mollie/transformers.rs @@ -19,6 +19,38 @@ use crate::{ type Error = error_stack::Report; +#[derive(Debug, Serialize)] +pub struct MollieRouterData { + pub amount: String, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for MollieRouterData +{ + type Error = error_stack::Report; + + fn try_from( + (currency_unit, currency, amount, router_data): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data, + }) + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct MolliePaymentsRequest { @@ -120,50 +152,55 @@ pub struct MollieBrowserInfo { language: String, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for MolliePaymentsRequest { +impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MolliePaymentsRequest { type Error = Error; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item: &MollieRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { let amount = Amount { - currency: item.request.currency, - value: utils::to_currency_base_unit(item.request.amount, item.request.currency)?, + currency: item.router_data.request.currency, + value: item.amount.clone(), }; - let description = item.get_description()?; - let redirect_url = item.request.get_return_url()?; - let payment_method_data = match item.request.capture_method.unwrap_or_default() { - enums::CaptureMethod::Automatic => match &item.request.payment_method_data { - api_models::payments::PaymentMethodData::Card(_) => { - let pm_token = item.get_payment_method_token()?; - Ok(PaymentMethodData::CreditCard(Box::new( - CreditCardMethodData { - billing_address: get_billing_details(item)?, - shipping_address: get_shipping_details(item)?, - card_token: Some(Secret::new(match pm_token { - types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } - })), - }, - ))) - } - api_models::payments::PaymentMethodData::BankRedirect(ref redirect_data) => { - PaymentMethodData::try_from(redirect_data) + let description = item.router_data.get_description()?; + let redirect_url = item.router_data.request.get_return_url()?; + let payment_method_data = match item.router_data.request.capture_method.unwrap_or_default() + { + enums::CaptureMethod::Automatic => { + match &item.router_data.request.payment_method_data { + api_models::payments::PaymentMethodData::Card(_) => { + let pm_token = item.router_data.get_payment_method_token()?; + Ok(PaymentMethodData::CreditCard(Box::new( + CreditCardMethodData { + billing_address: get_billing_details(item.router_data)?, + shipping_address: get_shipping_details(item.router_data)?, + card_token: Some(Secret::new(match pm_token { + types::PaymentMethodToken::Token(token) => token, + types::PaymentMethodToken::ApplePayDecrypt(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + })), + }, + ))) + } + api_models::payments::PaymentMethodData::BankRedirect(ref redirect_data) => { + PaymentMethodData::try_from(redirect_data) + } + api_models::payments::PaymentMethodData::Wallet(ref wallet_data) => { + get_payment_method_for_wallet(item.router_data, wallet_data) + } + api_models::payments::PaymentMethodData::BankDebit(ref directdebit_data) => { + PaymentMethodData::try_from(directdebit_data) + } + _ => Err(errors::ConnectorError::NotImplemented( + "Payment Method".to_string(), + )) + .into_report(), } - api_models::payments::PaymentMethodData::Wallet(ref wallet_data) => { - get_payment_method_for_wallet(item, wallet_data) - } - api_models::payments::PaymentMethodData::BankDebit(ref directdebit_data) => { - PaymentMethodData::try_from(directdebit_data) - } - _ => Err(errors::ConnectorError::NotImplemented( - "Payment Method".to_string(), - )) - .into_report(), - }, + } _ => Err(errors::ConnectorError::FlowNotSupported { flow: format!( "{} capture", - item.request.capture_method.unwrap_or_default() + item.router_data.request.capture_method.unwrap_or_default() ), connector: "Mollie".to_string(), }) @@ -526,16 +563,18 @@ pub struct MollieRefundRequest { description: Option, } -impl TryFrom<&types::RefundsRouterData> for MollieRefundRequest { +impl TryFrom<&MollieRouterData<&types::RefundsRouterData>> for MollieRefundRequest { type Error = Error; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: &MollieRouterData<&types::RefundsRouterData>, + ) -> Result { let amount = Amount { - currency: item.request.currency, - value: utils::to_currency_base_unit(item.request.refund_amount, item.request.currency)?, + currency: item.router_data.request.currency, + value: item.amount.clone(), }; Ok(Self { amount, - description: item.request.reason.to_owned(), + description: item.router_data.request.reason.to_owned(), }) } } From 4138c8f5431dea4fe400b47c919c68b7c8f7b402 Mon Sep 17 00:00:00 2001 From: Hangsaai <148209280+Hangsaai@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:35:44 +0530 Subject: [PATCH 04/38] refactor(connector): [Airwallex] Remove default case handling (#2703) --- .../src/connector/airwallex/transformers.rs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/crates/router/src/connector/airwallex/transformers.rs b/crates/router/src/connector/airwallex/transformers.rs index 51258643c64c..031a8276bb0d 100644 --- a/crates/router/src/connector/airwallex/transformers.rs +++ b/crates/router/src/connector/airwallex/transformers.rs @@ -186,8 +186,18 @@ impl TryFrom<&AirwallexRouterData<&types::PaymentsAuthorizeRouterData>> })) } api::PaymentMethodData::Wallet(ref wallet_data) => get_wallet_details(wallet_data), - _ => Err(errors::ConnectorError::NotImplemented( - "Unknown payment method".to_string(), + api::PaymentMethodData::PayLater(_) + | api::PaymentMethodData::BankRedirect(_) + | api::PaymentMethodData::BankDebit(_) + | api::PaymentMethodData::BankTransfer(_) + | api::PaymentMethodData::CardRedirect(_) + | api::PaymentMethodData::Crypto(_) + | api::PaymentMethodData::MandatePayment + | api::PaymentMethodData::Reward + | api::PaymentMethodData::Upi(_) + | api::PaymentMethodData::Voucher(_) + | api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("airwallex"), )), }?; @@ -215,9 +225,35 @@ fn get_wallet_details( payment_method_type: AirwallexPaymentType::Googlepay, })) } - _ => Err(errors::ConnectorError::NotImplemented( - "Payment method".to_string(), - ))?, + api_models::payments::WalletData::AliPayQr(_) + | api_models::payments::WalletData::AliPayRedirect(_) + | api_models::payments::WalletData::AliPayHkRedirect(_) + | api_models::payments::WalletData::MomoRedirect(_) + | api_models::payments::WalletData::KakaoPayRedirect(_) + | api_models::payments::WalletData::GoPayRedirect(_) + | api_models::payments::WalletData::GcashRedirect(_) + | api_models::payments::WalletData::ApplePay(_) + | api_models::payments::WalletData::ApplePayRedirect(_) + | api_models::payments::WalletData::ApplePayThirdPartySdk(_) + | api_models::payments::WalletData::DanaRedirect {} + | api_models::payments::WalletData::GooglePayRedirect(_) + | api_models::payments::WalletData::GooglePayThirdPartySdk(_) + | api_models::payments::WalletData::MbWayRedirect(_) + | api_models::payments::WalletData::MobilePayRedirect(_) + | api_models::payments::WalletData::PaypalRedirect(_) + | api_models::payments::WalletData::PaypalSdk(_) + | api_models::payments::WalletData::SamsungPay(_) + | api_models::payments::WalletData::TwintRedirect {} + | api_models::payments::WalletData::VippsRedirect {} + | api_models::payments::WalletData::TouchNGoRedirect(_) + | api_models::payments::WalletData::WeChatPayRedirect(_) + | api_models::payments::WalletData::WeChatPayQr(_) + | api_models::payments::WalletData::CashappQr(_) + | api_models::payments::WalletData::SwishQr(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("airwallex"), + ))? + } }; Ok(wallet_details) } From 05c2f842e3b9c579f611716b08a10766a6d13a30 Mon Sep 17 00:00:00 2001 From: Tejas Mate Date: Fri, 27 Oct 2023 17:44:43 +0530 Subject: [PATCH 05/38] refactor(connector): Use connector_request_reference_id for Fiserv (#2698) --- crates/router/src/connector/fiserv/transformers.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/router/src/connector/fiserv/transformers.rs b/crates/router/src/connector/fiserv/transformers.rs index c9c2f0c4087a..ae8eed0af314 100644 --- a/crates/router/src/connector/fiserv/transformers.rs +++ b/crates/router/src/connector/fiserv/transformers.rs @@ -62,6 +62,7 @@ pub struct Amount { pub struct TransactionDetails { capture_flag: Option, reversal_reason_code: Option, + merchant_transaction_id: String, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -112,6 +113,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for FiservPaymentsRequest { Some(enums::CaptureMethod::Automatic) | None )), reversal_reason_code: None, + merchant_transaction_id: item.connector_request_reference_id.clone(), }; let metadata = item.get_connector_meta()?; let session: SessionObject = metadata @@ -208,6 +210,7 @@ impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest { transaction_details: TransactionDetails { capture_flag: None, reversal_reason_code: Some(item.request.get_cancellation_reason()?), + merchant_transaction_id: item.connector_request_reference_id.clone(), }, }) } @@ -407,6 +410,7 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for FiservCaptureRequest { transaction_details: TransactionDetails { capture_flag: Some(true), reversal_reason_code: None, + merchant_transaction_id: item.connector_request_reference_id.clone(), }, merchant_details: MerchantDetails { merchant_id: auth.merchant_account, From af90089010e06ed45a70c51d4143260eec45b6dc Mon Sep 17 00:00:00 2001 From: Mohammed Shakeel Date: Fri, 27 Oct 2023 17:45:17 +0530 Subject: [PATCH 06/38] feat(connector): [Dlocal] Implement feature to use connector_request_reference_id as reference to the connector (#2704) --- crates/router/src/connector/dlocal/transformers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/dlocal/transformers.rs b/crates/router/src/connector/dlocal/transformers.rs index 5146dd0ea031..668a335cce88 100644 --- a/crates/router/src/connector/dlocal/transformers.rs +++ b/crates/router/src/connector/dlocal/transformers.rs @@ -145,7 +145,7 @@ impl TryFrom<&DlocalRouterData<&types::PaymentsAuthorizeRouterData>> for DlocalP .clone() .map(|_| "1".to_string()), }), - order_id: item.router_data.payment_id.clone(), + order_id: item.router_data.connector_request_reference_id.clone(), three_dsecure: match item.router_data.auth_type { diesel_models::enums::AuthenticationType::ThreeDs => { Some(ThreeDSecureReqData { force: true }) @@ -237,7 +237,7 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for DlocalPaymentsCaptureRequest authorization_id: item.request.connector_transaction_id.clone(), amount: item.request.amount_to_capture, currency: item.request.currency.to_string(), - order_id: item.payment_id.clone(), + order_id: item.connector_request_reference_id.clone(), }) } } From 78e5cd00b55ad2bd25083aecceaa8762efe3b48d Mon Sep 17 00:00:00 2001 From: Shivansh Bhatnagar Date: Fri, 27 Oct 2023 18:38:54 +0530 Subject: [PATCH 07/38] refactor(connector): [Rapyd] add and implement the get_currency_unit function (#2664) Co-authored-by: Shivansh Bhatnagar --- crates/router/src/connector/rapyd.rs | 28 ++++++- .../src/connector/rapyd/transformers.rs | 75 ++++++++++++++----- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index 292e6c55f26f..29f21f37381d 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -64,6 +64,10 @@ impl ConnectorCommon for Rapyd { "rapyd" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -179,7 +183,13 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, ) -> CustomResult, errors::ConnectorError> { - let req_obj = rapyd::RapydPaymentsRequest::try_from(req)?; + let connector_router_data = rapyd::RapydRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -483,7 +493,13 @@ impl &self, req: &types::PaymentsCaptureRouterData, ) -> CustomResult, errors::ConnectorError> { - let req_obj = rapyd::CaptureRequest::try_from(req)?; + let connector_router_data = rapyd::RapydRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount_to_capture, + req, + ))?; + let req_obj = rapyd::CaptureRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -615,7 +631,13 @@ impl services::ConnectorIntegration, ) -> CustomResult, errors::ConnectorError> { - let req_obj = rapyd::RapydRefundRequest::try_from(req)?; + let connector_router_data = rapyd::RapydRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs index 79ad6838ac35..9df699b938bb 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/router/src/connector/rapyd/transformers.rs @@ -13,6 +13,36 @@ use crate::{ utils::OptionExt, }; +#[derive(Debug, Serialize)] +pub struct RapydRouterData { + pub amount: i64, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for RapydRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount, + router_data: item, + }) + } +} + #[derive(Default, Debug, Serialize)] pub struct RapydPaymentsRequest { pub amount: i64, @@ -69,18 +99,23 @@ pub struct RapydWallet { token: Option, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for RapydPaymentsRequest { +impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - let (capture, payment_method_options) = match item.payment_method { + fn try_from( + item: &RapydRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let (capture, payment_method_options) = match item.router_data.payment_method { diesel_models::enums::PaymentMethod::Card => { - let three_ds_enabled = matches!(item.auth_type, enums::AuthenticationType::ThreeDs); + let three_ds_enabled = matches!( + item.router_data.auth_type, + enums::AuthenticationType::ThreeDs + ); let payment_method_options = PaymentMethodOptions { three_ds: three_ds_enabled, }; ( Some(matches!( - item.request.capture_method, + item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None )), Some(payment_method_options), @@ -88,7 +123,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for RapydPaymentsRequest { } _ => (None, None), }; - let payment_method = match item.request.payment_method_data { + let payment_method = match item.router_data.request.payment_method_data { api_models::payments::PaymentMethodData::Card(ref ccard) => { Some(PaymentMethod { pm_type: "in_amex_card".to_owned(), //[#369] Map payment method type based on country @@ -128,10 +163,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for RapydPaymentsRequest { .change_context(errors::ConnectorError::NotImplemented( "payment_method".to_owned(), ))?; - let return_url = item.request.get_return_url()?; + let return_url = item.router_data.request.get_return_url()?; Ok(Self { - amount: item.request.amount, - currency: item.request.currency, + amount: item.amount, + currency: item.router_data.request.currency, payment_method, capture, payment_method_options, @@ -276,13 +311,17 @@ pub struct RapydRefundRequest { pub currency: Option, } -impl TryFrom<&types::RefundsRouterData> for RapydRefundRequest { +impl TryFrom<&RapydRouterData<&types::RefundsRouterData>> for RapydRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &RapydRouterData<&types::RefundsRouterData>) -> Result { Ok(Self { - payment: item.request.connector_transaction_id.to_string(), - amount: Some(item.request.refund_amount), - currency: Some(item.request.currency), + payment: item + .router_data + .request + .connector_transaction_id + .to_string(), + amount: Some(item.amount), + currency: Some(item.router_data.request.currency), }) } } @@ -380,11 +419,13 @@ pub struct CaptureRequest { statement_descriptor: Option, } -impl TryFrom<&types::PaymentsCaptureRouterData> for CaptureRequest { +impl TryFrom<&RapydRouterData<&types::PaymentsCaptureRouterData>> for CaptureRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + fn try_from( + item: &RapydRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { Ok(Self { - amount: Some(item.request.amount_to_capture), + amount: Some(item.amount), receipt_email: None, statement_descriptor: None, }) From 05100ea38d540d17e211e06ea99fcfeae7958975 Mon Sep 17 00:00:00 2001 From: HeetVekariya <91054457+HeetVekariya@users.noreply.github.com> Date: Fri, 27 Oct 2023 21:54:52 +0530 Subject: [PATCH 08/38] refactor(connector): [Square] remove default case handling (#2701) Co-authored-by: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> --- .../src/connector/square/transformers.rs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/crates/router/src/connector/square/transformers.rs b/crates/router/src/connector/square/transformers.rs index 01ed507bf34a..54a7c461dbfc 100644 --- a/crates/router/src/connector/square/transformers.rs +++ b/crates/router/src/connector/square/transformers.rs @@ -23,7 +23,10 @@ impl TryFrom<(&types::TokenizationRouterData, BankDebitData)> for SquareTokenReq "Payment Method".to_string(), )) .into_report(), - _ => Err(errors::ConnectorError::NotSupported { + + BankDebitData::SepaBankDebit { .. } + | BankDebitData::BecsBankDebit { .. } + | BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotSupported { message: format!("{:?}", item.request.payment_method_data), connector: "Square", })?, @@ -85,7 +88,14 @@ impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequ errors::ConnectorError::NotImplemented("Payment Method".to_string()), ) .into_report(), - _ => Err(errors::ConnectorError::NotSupported { + + PayLaterData::KlarnaRedirect { .. } + | PayLaterData::KlarnaSdk { .. } + | PayLaterData::AffirmRedirect { .. } + | PayLaterData::PayBrightRedirect { .. } + | PayLaterData::WalleyRedirect { .. } + | PayLaterData::AlmaRedirect { .. } + | PayLaterData::AtomeRedirect { .. } => Err(errors::ConnectorError::NotSupported { message: format!("{:?}", item.request.payment_method_data), connector: "Square", })?, @@ -106,7 +116,31 @@ impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenReques "Payment Method".to_string(), )) .into_report(), - _ => Err(errors::ConnectorError::NotSupported { + + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) => Err(errors::ConnectorError::NotSupported { message: format!("{:?}", item.request.payment_method_data), connector: "Square", })?, @@ -295,7 +329,14 @@ impl TryFrom<&types::ConnectorAuthType> for SquareAuthType { api_key: api_key.to_owned(), key1: key1.to_owned(), }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + + types::ConnectorAuthType::HeaderKey { .. } + | types::ConnectorAuthType::SignatureKey { .. } + | types::ConnectorAuthType::MultiAuthKey { .. } + | types::ConnectorAuthType::CurrencyAuthKey { .. } + | types::ConnectorAuthType::NoKey { .. } => { + Err(errors::ConnectorError::FailedToObtainAuthType.into()) + } } } } From 2815443c1b147e005a2384ff817292b1845a9f88 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:20:23 +0530 Subject: [PATCH 09/38] docs(changelog): Fix typo in changelog (#2713) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8804ff4ea827..3848c9877088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to HyperSwitch will be documented here. ### Features -- **connector:** [OpenNode] Use connector_request_reference_id as referemce to connector ([#2596](https://github.com/juspay/hyperswitch/pull/2596)) ([`96b790c`](https://github.com/juspay/hyperswitch/commit/96b790cb4b44cd4867be62e2889cb4aa23622161)) +- **connector:** [OpenNode] Use connector_request_reference_id as reference to connector ([#2596](https://github.com/juspay/hyperswitch/pull/2596)) ([`96b790c`](https://github.com/juspay/hyperswitch/commit/96b790cb4b44cd4867be62e2889cb4aa23622161)) ### Bug Fixes From 4afe552563c6a0cb9544a9a2f870bb9d07d7cf18 Mon Sep 17 00:00:00 2001 From: Dmitriy Danilov Date: Sat, 28 Oct 2023 20:46:17 +0300 Subject: [PATCH 10/38] refactor(connector): Use connector_request_reference_id for Iatapay (#2692) --- crates/router/src/connector/iatapay/transformers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index f98798fe5be4..d4731b024c86 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -110,7 +110,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest { utils::to_currency_base_unit_asf64(item.request.amount, item.request.currency)?; let payload = Self { merchant_id: IatapayAuthType::try_from(&item.connector_auth_type)?.merchant_id, - merchant_payment_id: Some(item.payment_id.clone()), + merchant_payment_id: Some(item.connector_request_reference_id.clone()), amount, currency: item.request.currency.to_string(), country: country.clone(), From 8eca66a2eb8770783c671b299765aa15d7fa72f8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 14:45:27 +0000 Subject: [PATCH 11/38] test(postman): update postman collection files --- .../forte.postman_collection.json | 244 +++++++++++++++++- 1 file changed, 243 insertions(+), 1 deletion(-) diff --git a/postman/collection-json/forte.postman_collection.json b/postman/collection-json/forte.postman_collection.json index cdf381f9f2d5..8297b4778f4a 100644 --- a/postman/collection-json/forte.postman_collection.json +++ b/postman/collection-json/forte.postman_collection.json @@ -523,7 +523,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"routing\":{\"type\":\"single\",\"data\":\"forte\"},\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -646,6 +646,248 @@ { "name": "Happy Cases", "item": [ + { + "name": "Scenario7-Create payment with Zero Amount", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"processing\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":0,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"routing\":{\"type\":\"single\",\"data\":\"forte\"},\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"Fix\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"processing\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, { "name": "Scenario1-Create payment with confirm true", "item": [ From 8125ea19912f6d6446412d13842e35545cc1a484 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 14:45:27 +0000 Subject: [PATCH 12/38] chore(version): v1.68.0 --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3848c9877088..b424379a8586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,42 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.68.0 (2023-10-29) + +### Features + +- **connector:** + - [OpenNode] Currency Unit Conversion ([#2645](https://github.com/juspay/hyperswitch/pull/2645)) ([`88e1f29`](https://github.com/juspay/hyperswitch/commit/88e1f29dae13622bc58b8f5df1cd84b929b28ac6)) + - [Mollie] Currency Unit Conversion ([#2671](https://github.com/juspay/hyperswitch/pull/2671)) ([`3578db7`](https://github.com/juspay/hyperswitch/commit/3578db7640d8eda8f063e11b8bb64452fb987eef)) + - [Dlocal] Implement feature to use connector_request_reference_id as reference to the connector ([#2704](https://github.com/juspay/hyperswitch/pull/2704)) ([`af90089`](https://github.com/juspay/hyperswitch/commit/af90089010e06ed45a70c51d4143260eec45b6dc)) +- **events:** Add masked json serializer for logging PII values ([#2681](https://github.com/juspay/hyperswitch/pull/2681)) ([`13c66df`](https://github.com/juspay/hyperswitch/commit/13c66df92c5b7db9e44852d4afee7a4e5ae52a15)) + +### Bug Fixes + +- **connector:** [Forte] Response Handling for Verify Action ([#2601](https://github.com/juspay/hyperswitch/pull/2601)) ([`efed596`](https://github.com/juspay/hyperswitch/commit/efed5968236a8ae3b26a7697e4972f243add4292)) + +### Refactors + +- **connector:** + - [Airwallex] Remove default case handling ([#2703](https://github.com/juspay/hyperswitch/pull/2703)) ([`4138c8f`](https://github.com/juspay/hyperswitch/commit/4138c8f5431dea4fe400b47c919c68b7c8f7b402)) + - Use connector_request_reference_id for Fiserv ([#2698](https://github.com/juspay/hyperswitch/pull/2698)) ([`05c2f84`](https://github.com/juspay/hyperswitch/commit/05c2f842e3b9c579f611716b08a10766a6d13a30)) + - [Rapyd] add and implement the get_currency_unit function ([#2664](https://github.com/juspay/hyperswitch/pull/2664)) ([`78e5cd0`](https://github.com/juspay/hyperswitch/commit/78e5cd00b55ad2bd25083aecceaa8762efe3b48d)) + - [Square] remove default case handling ([#2701](https://github.com/juspay/hyperswitch/pull/2701)) ([`05100ea`](https://github.com/juspay/hyperswitch/commit/05100ea38d540d17e211e06ea99fcfeae7958975)) + - Use connector_request_reference_id for Iatapay ([#2692](https://github.com/juspay/hyperswitch/pull/2692)) ([`4afe552`](https://github.com/juspay/hyperswitch/commit/4afe552563c6a0cb9544a9a2f870bb9d07d7cf18)) + +### Testing + +- **postman:** Update postman collection files ([`8eca66a`](https://github.com/juspay/hyperswitch/commit/8eca66a2eb8770783c671b299765aa15d7fa72f8)) + +### Documentation + +- **changelog:** Fix typo in changelog ([#2713](https://github.com/juspay/hyperswitch/pull/2713)) ([`2815443`](https://github.com/juspay/hyperswitch/commit/2815443c1b147e005a2384ff817292b1845a9f88)) + +**Full Changelog:** [`v1.67.0...v1.68.0`](https://github.com/juspay/hyperswitch/compare/v1.67.0...v1.68.0) + +- - - + + ## 1.67.0 (2023-10-26) ### Features From 23bd364a7819a48c3f5f89ff5b71cc237d6e2d46 Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:13:28 +0530 Subject: [PATCH 13/38] feat(connector): [VOLT] Implement payment flows and bank redirect payment method (#2582) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> --- .github/secrets/connector_auth.toml.gpg | Bin 3183 -> 3310 bytes crates/api_models/src/enums.rs | 5 +- crates/router/src/connector/volt.rs | 197 +++++++---- .../router/src/connector/volt/transformers.rs | 333 ++++++++++++++---- crates/router/src/core/admin.rs | 4 + crates/router/src/types.rs | 5 + crates/router/src/types/api.rs | 2 +- openapi/openapi_spec.json | 1 + postman/collection-dir/volt/.auth.json | 22 ++ postman/collection-dir/volt/.event.meta.json | 6 + postman/collection-dir/volt/.info.json | 9 + postman/collection-dir/volt/.meta.json | 6 + postman/collection-dir/volt/.variable.json | 101 ++++++ .../volt/Flow Testcases/.meta.json | 6 + .../Flow Testcases/Happy Cases/.meta.json | 8 + .../.meta.json | 6 + .../Payments - Create/.event.meta.json | 5 + .../Payments - Create/event.test.js | 80 +++++ .../Payments - Create/request.json | 102 ++++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 5 + .../Payments - Retrieve/event.test.js | 80 +++++ .../Payments - Retrieve/request.json | 33 ++ .../Payments - Retrieve/response.json | 1 + .../.meta.json | 7 + .../Payments - Confirm/.event.meta.json | 6 + .../Payments - Confirm/event.prerequest.js | 0 .../Payments - Confirm/event.test.js | 103 ++++++ .../Payments - Confirm/request.json | 63 ++++ .../Payments - Confirm/response.json | 1 + .../Payments - Create/.event.meta.json | 5 + .../Payments - Create/event.test.js | 71 ++++ .../Payments - Create/request.json | 102 ++++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 5 + .../Payments - Retrieve/event.test.js | 91 +++++ .../Payments - Retrieve/request.json | 33 ++ .../Payments - Retrieve/response.json | 1 + .../.meta.json | 7 + .../Payments - Confirm/.event.meta.json | 6 + .../Payments - Confirm/event.prerequest.js | 0 .../Payments - Confirm/event.test.js | 73 ++++ .../Payments - Confirm/request.json | 73 ++++ .../Payments - Confirm/response.json | 1 + .../Payments - Create/.event.meta.json | 5 + .../Payments - Create/event.test.js | 71 ++++ .../Payments - Create/request.json | 83 +++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 5 + .../Payments - Retrieve/event.test.js | 71 ++++ .../Payments - Retrieve/request.json | 33 ++ .../Payments - Retrieve/response.json | 1 + .../.meta.json | 7 + .../Payments - Confirm/.event.meta.json | 6 + .../Payments - Confirm/event.prerequest.js | 0 .../Payments - Confirm/event.test.js | 103 ++++++ .../Payments - Confirm/request.json | 73 ++++ .../Payments - Confirm/response.json | 1 + .../Payments - Create/.event.meta.json | 5 + .../Payments - Create/event.test.js | 71 ++++ .../Payments - Create/request.json | 83 +++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 5 + .../Payments - Retrieve/event.test.js | 71 ++++ .../Payments - Retrieve/request.json | 33 ++ .../Payments - Retrieve/response.json | 1 + .../volt/Flow Testcases/QuickStart/.meta.json | 9 + .../API Key - Create/.event.meta.json | 5 + .../QuickStart/API Key - Create/event.test.js | 46 +++ .../QuickStart/API Key - Create/request.json | 52 +++ .../QuickStart/API Key - Create/response.json | 1 + .../.event.meta.json | 5 + .../Merchant Account - Create/event.test.js | 56 +++ .../Merchant Account - Create/request.json | 95 +++++ .../Merchant Account - Create/response.json | 1 + .../.event.meta.json | 5 + .../Payment Connector - Create/event.test.js | 39 ++ .../Payment Connector - Create/request.json | 93 +++++ .../Payment Connector - Create/response.json | 1 + .../Payments - Create/.event.meta.json | 5 + .../Payments - Create/event.test.js | 61 ++++ .../QuickStart/Payments - Create/request.json | 102 ++++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 5 + .../Payments - Retrieve/event.test.js | 61 ++++ .../Payments - Retrieve/request.json | 27 ++ .../Payments - Retrieve/response.json | 1 + .../volt/Health check/.meta.json | 5 + .../Health check/New Request/.event.meta.json | 5 + .../Health check/New Request/event.test.js | 4 + .../Health check/New Request/request.json | 20 ++ .../Health check/New Request/response.json | 1 + .../collection-dir/volt/event.prerequest.js | 0 postman/collection-dir/volt/event.test.js | 13 + 94 files changed, 2970 insertions(+), 146 deletions(-) create mode 100644 postman/collection-dir/volt/.auth.json create mode 100644 postman/collection-dir/volt/.event.meta.json create mode 100644 postman/collection-dir/volt/.info.json create mode 100644 postman/collection-dir/volt/.meta.json create mode 100644 postman/collection-dir/volt/.variable.json create mode 100644 postman/collection-dir/volt/Flow Testcases/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.prerequest.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.prerequest.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/response.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/response.json create mode 100644 postman/collection-dir/volt/Health check/.meta.json create mode 100644 postman/collection-dir/volt/Health check/New Request/.event.meta.json create mode 100644 postman/collection-dir/volt/Health check/New Request/event.test.js create mode 100644 postman/collection-dir/volt/Health check/New Request/request.json create mode 100644 postman/collection-dir/volt/Health check/New Request/response.json create mode 100644 postman/collection-dir/volt/event.prerequest.js create mode 100644 postman/collection-dir/volt/event.test.js diff --git a/.github/secrets/connector_auth.toml.gpg b/.github/secrets/connector_auth.toml.gpg index ae8c09612643729fb63dcfe68cf78adb764e1954..487e436df4638117aa49684ae4f7f45f682e44cd 100644 GIT binary patch literal 3310 zcmVX@^j z?Sb&uy#On^RBnZ*k@YxvAvf_8Tj&1(+ipUGLtD?antwuh9_SeCj1`=DK_1IzpJRjA zHsd%NR`3TKf`_-)k2qe(x~xju7NiLYxi(PW@ml~^7BXp_a(XDHFk}W+P!0Dc5+if% zWV{~Tq_E?tqa`Nt+2W#U-#%2~y#>WRhW!L7xInUJdTR++nI0`pe))dejx2EHh2NMG zu*N;@T^tM4TT%^w@K9e`FnB_YV!ov_j8?bNnNSu-_)X&n&V;^g%tE>tkH0#(P!mPo zB{L=%1AiX#yi!lJ9|bSGF1ZG)`Ar#?HS^fS%`I>f0Pw0+(Q5+`Ha{kcoh935ry^?G zIJ|$pEP>kt{kw6%{WjNS=OkO2xSLG^Y4}D8?|~<8r%VB zBG&q|`=WEi2Sk}ALR@A0klZA#CahoP(<}v|#*WLIypVZbA*OEZY|mg^@U?7F1l|rNPB-iXiLh9z>Buea z_dYTWN4%qsu=4R^OIsJ;#3s5DYa9W&;HE-%z%CBU@4qD$a_psbkY4^XoW1)N)9p%%KWyk738qTYaW3p5dgA$uX*2ey|hX|)d`NtJSA zVF@fNe79(6UIrEOdWxMZ`kRA-PfteihO$GAziL@CB@I`Zv8Hq*am!D{)XP_s^e7`B~}H*dTnX&YjGjiLx+=YE|lX zHQ{|zj|{C{n3ocGTbmtVbv3$_*OarX!q51gNMXkh-XxaC9^hRpJ^aGx^9O?fQ#Dwt zh5FimU_@qE>KLZ8aNOo4gRZ@-tMESHtY@3IQLxOKSaQURw}dSdz~M)OB)F?VnE*U0 z96%(gI1_8QFcp=VIvg?PB$&uJjNr3} zj@=0uUU*SM1Ool(YpF>XND(hcgGWcqIFb)l9ATT^6VW@?@l5>4W zZ>F>Z?vuvHBeiD0nL6}rC+t?BF+QeQ;dwBG0!Nj?K~8n3V!gnKZIZtOa8`wue*6SW zviRFz)G{8EqU3VvVYqwkGz@)XhK-<_77M2zuGC!jLc{He+sStL1zok920(YA11h_6 z2_DY_EU;Aoc2R%J*5xl@J!(H}H2Sq|4=)MXI*?N5Zw{wrRE($5g^PQjbVGkiu9;6q zu;d6)G=h!$`%j{IE$q#3cX{qFYC|&VlAr$!TFXe>`k@!_6-7Gtsp>z*n)54b6t~Bv zYBFeE8YAW4SFiRw@rqWCC|!PfYJfpBT**Da9{~1dp9E_!dh!XPztIsfX;G2daxIeX zmz8S*l6{$X^Z#;A|6EQ#A4srsx^>yj1WTBui!mQ#93YK^`c8D`8t?LJ#I-XAj7l4W z65?$HTe~(>h6oVpbH>nFIrE0y%+U5<@4J*KQ*#1K$D>sKM{M&$g2(71*$v}mmh7-n z^7=sbc$r3mYvL2K@>xBWJTIWo00(q{c?#5$Es*=hGVnO9;X!tj9S#O6M)=?4gK>a7 zy<3;>S~NWfeZMm+gzm|+H@5M*%U9hIcU1N6 z^btXKy&*cWH%>jA-@oM(RFuYc5!YIh-Ys)W;qLzi{5#nIU^?AZHGG0-2aG;~P2u25 z&uRS&r!4ys9qj;%$q{c}?2G-%EUBgd?B47_N`x*W(-!HQ#Z^s{rI+@~WWWS*n1CCg z?D}AEU7h$VCSz!Ax>KRz2H_@@e0?nApkCje>CF4&EM-{jN~mTMzb4psNK&kukl@9v8c3DY%$58^T7n0ClzCB~6n*NVP^8Hr3^~pqAvYzZc?eI2UO`|z5^+T_YT;EMZgX&bwuc%0N zsnZVdSP7T!t}SQq5i1rok03<_Ve>$GNn5XA?%xr(NMxgu&yS1GWp_;+pQjKCALhd! z4~Zxmb+!UqthXq4m;(genlz6pQ-8#pa!Uq*^-ZBg^&Di)EgdX#Ai~zdh_pabT{Bs( z1_HM|e@vqw4T{F!M16+p`N}gU)wf=%FO%fgff)#6XTg#yx9djL?3CkmZJ|)bCu{5h zz@o@Y*sR!c9Xo#YI!+fvHcQH;UdD8Q9xFcXQnV5lr@f3lZtjfO28G(G?Zj%7;ps~{ z$^J9)4NpPJZNSXm^|wQpR^_z*pW=F~G`$bR@wBNAIJUuzNzrMgF%dUhDG<~abcdH1=WqD4jIH&So?hDDgRm>(Q-ER<)J#DFb1?M1fYq3LA0GXQ zcG>E6GY`V&x}O*aOmpc-~}K2z}0%LyOs<;=SmB=IzxfC2Cb0nNx?T&)%;Bh?>F zgZz95Df`XDg@gv$+z0~Dr7b`?DeiOPm5BGdq@S8u@t#UYke2XtA*(=9!cWlGor%?P zF}5&(u06+t+pKhsCW&Ao3^m3SoRtK`gy$UPY0G??EiI{CeW0hIF*_`Lm5oHs?*2AR z1jLZM=3IMRXYMm#slc2x4DtZDq;LfdkzHXL{-#8s+9sChpOu@i#VDr=KtmpxeWatp z*5Gvr)2P^Zf2)0+9pIfWL3RlH`r~TBxl*C3XBwhRdv7x{6s*dU){%g-5D(~54Lwk<`-@!??(Z{Lv@sxO@Z7kx?} z3KK1&tt!_h9+kvdqxB$ey5z3)aAcU~IRombAgXImQ2-xofrOBrQ2q2!a}-JN*$Y>@~F0u@M0 z_t}e3-Q$D+KI!|qSHa4w`H-qLKfjuzM82`)S=8}~vxGt8QSrPrA$f+hkZ+_s`1;@G zulMeS$}*k{Ldx`D?RO9tt^Yl~Kn(Wx8)8vW2JtmQmcB)c@et;^Lw?2))byI~f-p?} z_J;4hVQD7_Z7&Q|uEF*z8B&%5_ce%`HRg6TCR!!QT8<2snr|MJ>a2;b&LvE;*t_71 s_Sy+{An_^L*Qbd0DMs*_fFEN)%u3nS3!R@JrnrOXXDHeQgilF&;>e+F8vp1oqPF0fHf(j_zE4#{?NBs{d`j9G<^V_X#Ssf_fyC z*b}xb_Tk9JcDX^nRC$jjh@7AxqYyiq#+nb5n(ssM%H$#qdBLPu!b(xD4!0=qRl8h@ zxgSmpRAs5nTF$X~3(@*k&^O5&wzog2Eqa5*l*o33K@9I_#JXqLW+o2oY}d+sAS9qwk875_}T$tARV#oflgTNny(CuwGzssOS!inAg3`q&qzW@M*Lt5GuQv1#dKF zN0ls_;JH%^N5FKIX5zh%2A}PaQ7n zii-ft$RSZbQvTzhP#8|sABdvZOf<6{BZ2bkN=~nJ#mdpD>(Lm!P-vm^>SW&l!P_+} z8=tb5Bb za)*o`eOZ>{GBz;74h~Hz&mT zxTg6fY|MKg`lH(5#Q4rCXX1b`Jp{%Ysi(=n!*VbL2%B_V{s;*llK+jbfePLe?KAn( z%$L0T+%4Y1XtSH(mmOcqBzw32OM9I&|Q||Ocy|DI( z#n%c2}hdA426Ht*~{z15R7|m`h-M>3#xY)w6A0 z(4D?$h@>B`y9Ou>^)FKTViG7Ae377}yxhe(l< zL+=Tnqb4)I4Hkn#N64?CJwL;?mfB`{@c@4FpX@O}=Cjw#+B0j>9XjP%ZqRLsMGw#b zaot%UAjrPz<>cXulJC`}AYfpTgqk$G&xu=oc zNS&S*9L7ZOm*;Mmv9mV(D$)gzuvOk`O3j|Hl z?CC_Gww8D2o1ub`#S~(0J4Cw)_1h7s}H6net%7^(2Zs|5#)-8fe2(Wd>F% z8V(MZn1dT-X~Rr}h08vRnDf^of=95(5H6xfY7p89z+p0jdCB)o9{$hSy4qPN zQgOtox$n%u_tvr!rV6vkrz1Df9sBIDUV@FtKT9sd$4$LBNP~B*$?4UGvu3g-*4AQT zEK?&Tx#;>XJ!pc_$`)GUf(gCf*m#*s^MLG`N>RkO{ObH+{ZAb~XCm!A{5a(~jR zDq33Vl$JbV=D%wk6_^xgLb9wkQa+%xOk7M1I}1NX! zc0Gz^{8-dS>3(@6!u-QX3U;7mNHp>UdmwrE`HG8BSp%0q$em3jr}(n%n71_hI zdwLOzT@+8ZKdNq%>dA-?mEd9g=cTax|bbxykgGxQ&7N2w<>4<3x|f5g>Epup1Op(+t>{UrbSKa(H9X&QSt zQTn$yj2Wnv$E7)VA>#DeG}4D-uA1M7z^LrZe_@T$v+q;n9jwebb}ME7&5L4qIr<5( z!cvT#&(cJp(l3w91r(7bgk9Kfl7MeBY)?KW0M^}a_L|~DX2uFc8@St^b<=fQGugx_wmTx&3vobqd)$rB*IsP?xI?Kr$~=s2Yr zoo+vz{MtrwjX%dQRkrKDLOj#zwp&xuu*oi-vS@W%!{JGR1GN={Yd0g@gZYNSiOVsi z5`SG-f_0&b#@@-J8uz4ip`$*8i*6aEvKiK)oc**ca(|VGtk_~(_C(aO>gG~8h^QDy z=PtCs0ny$0el0P$I931+oO%!qRs&%%Gc$=2ybQ@-8<9oa=`D*Pxr|qBTTkLfw zHpY9fHB29H&vz4DHvRKx1IgAi)Q`JkoHqGFRm8_zMoDAy5Ty*lR;1f-Rs6_6+IZX( z?utG#*Rs{gZoxVP(k5uGGZ3c-rEKgn^ylnowH(STgZVR-X%q^Hd9=EY!dZf~Y8gj7k#u_I)hYjy z+#!u$CERZ>D0uB54n|)~-yRnip6)Hf8BuVKzCEHXO*u)HycD<0Q&zSuWhC8Eo#WYf zG#w{GqCIo&ma3v-uC_t|Z^f)Q9o0{+uXe-T3GO20jPK|R&9Oryw2nTMRlrN=Pu8lP zgF7xeSBkeDDX(vOR6gfRq>~Zh1p_3@s|n~z9kXYT?1?Scum{5t$Pcg08wdBn+-k3Q z?pO;>74jnC+;16jwcl5$HJ5}-i@oe7($H2#nb#cmZxg_2iNj7e0hVGIAo(<8G5d4g zeJEwH**}&i%^Kluj((q<*%jL=n2JODLsM>Yi^4SRbm`Zq0RH;&->0P-n}`L|@0)Ax zBq(NTm!bbR3kYp3CV&EDh?|VY#peO?g0vJ!mv(nPs|B0)6 z`W~|i;*$3v9_}g`1d%N64tV50$OGolTKJcS?}Huc3+UlAD&&MyyRvduhRHCO>N%n_ z;ysBo;iL3H@{O|3{oLFn6fkKmBo@*O5=orC-(ac?bssWFxq|E3B{Nz*$!Y1$uWu0V z%1jHaN!M?(`nPIl@;vHod=>QfDVSmsa*jU9%LXyI~B2S8uU|f7C{+JxhY-C|a!I*=~4rpJEf$<(@maiNNBq_L0ZA%w2o3kI_L-8G^}7cM^4~aTSUu Vc=YfA@PGf2n5hOELiKgSA}Sr-LjeE) diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 65f6dea1b5ed..ee67c1187e6b 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -116,7 +116,7 @@ pub enum Connector { Trustpay, // Tsys, Tsys, - //Volt, added as template code for future usage, + Volt, Wise, Worldline, Worldpay, @@ -135,6 +135,7 @@ impl Connector { | (Self::Payu, _) | (Self::Trustpay, PaymentMethod::BankRedirect) | (Self::Iatapay, _) + | (Self::Volt, _) ) } pub fn supports_file_storage_module(&self) -> bool { @@ -235,7 +236,7 @@ pub enum RoutableConnectors { Trustpay, // Tsys, Tsys, - // Volt, added as template code for future usage + Volt, Wise, Worldline, Worldpay, diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 92d2955a0e0d..e1af4c008b12 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -3,7 +3,7 @@ pub mod transformers; use std::fmt::Debug; use error_stack::{IntoReport, ResultExt}; -use masking::ExposeInterface; +use masking::{ExposeInterface, PeekInterface}; use transformers as volt; use crate::{ @@ -64,8 +64,15 @@ where .to_string() .into(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.peek()).into_masked(), + ); + header.push(auth_header); Ok(header) } } @@ -95,7 +102,7 @@ impl ConnectorCommon for Volt { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth.username.expose().into_masked(), )]) } @@ -108,11 +115,20 @@ impl ConnectorCommon for Volt { .parse_struct("VoltErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let reason = match &response.exception.error_list { + Some(error_list) => error_list + .iter() + .map(|error| error.message.clone()) + .collect::>() + .join(" & "), + None => response.exception.message.clone(), + }; + Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.exception.message.to_string(), + message: response.exception.message.clone(), + reason: Some(reason), }) } } @@ -130,6 +146,84 @@ impl ConnectorIntegration for Volt { + fn get_url( + &self, + _req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}oauth", self.base_url(connectors))) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + fn get_headers( + &self, + _req: &types::RefreshTokenRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + types::RefreshTokenType::get_content_type(self) + .to_string() + .into(), + )]) + } + + fn get_request_body( + &self, + req: &types::RefreshTokenRouterData, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = volt::VoltAuthUpdateRequest::try_from(req)?; + let volt_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::url_encode, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + Ok(Some(volt_req)) + } + + fn build_request( + &self, + req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .attach_default_headers() + .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .body(types::RefreshTokenType::get_request_body(self, req)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &types::RefreshTokenRouterData, + res: Response, + ) -> CustomResult { + let response: volt::VoltAuthUpdateResponse = res + .response + .parse_struct("Volt VoltAuthUpdateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } } impl @@ -159,9 +253,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}v2/payments", self.base_url(connectors))) } fn get_request_body( @@ -244,10 +338,18 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}payments/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -270,7 +372,7 @@ impl ConnectorIntegration CustomResult { - let response: volt::VoltPaymentsResponse = res + let response: volt::VoltPsyncResponse = res .response .parse_struct("volt PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -381,10 +483,14 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}payments/{connector_payment_id}/request-refund", + self.base_url(connectors), + )) } fn get_request_body( @@ -448,64 +554,7 @@ impl ConnectorIntegration for Volt { - fn get_headers( - &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .body(types::RefundSyncType::get_request_body(self, req)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::RefundSyncRouterData, - res: Response, - ) -> CustomResult { - let response: volt::RefundResponse = - res.response - .parse_struct("volt RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - types::RouterData::try_from(types::ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - ) -> CustomResult { - self.build_error_response(res) - } + //Volt does not support Refund Sync } #[async_trait::async_trait] diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs index 1bbbe5ff1eb4..e603ef2db06c 100644 --- a/crates/router/src/connector/volt/transformers.rs +++ b/crates/router/src/connector/volt/transformers.rs @@ -1,12 +1,17 @@ +use common_utils::pii::Email; +use diesel_models::enums; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{self, AddressDetailsData, RouterData}, core::errors, - types::{self, api, storage::enums}, + services, + types::{self, api, storage::enums as storage_enums}, }; +const PASSWORD: &str = "password"; + pub struct VoltRouterData { pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, @@ -29,7 +34,6 @@ impl T, ), ) -> Result { - //Todo : use utils to convert the amount to the type of amount that a connector accepts Ok(Self { amount, router_data: item, @@ -37,20 +41,38 @@ impl } } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct VoltPaymentsRequest { amount: i64, - card: VoltCard, + currency_code: storage_enums::Currency, + #[serde(rename = "type")] + transaction_type: TransactionType, + merchant_internal_reference: String, + shopper: ShopperDetails, + notification_url: Option, + payment_success_url: Option, + payment_failure_url: Option, + payment_pending_url: Option, + payment_cancel_url: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum TransactionType { + Bills, + Goods, + PersonToPerson, + Other, + Services, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct VoltCard { - name: Secret, - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Debug, Serialize)] +pub struct ShopperDetails { + reference: String, + email: Option, + first_name: Secret, + last_name: Secret, } impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPaymentsRequest { @@ -59,63 +81,184 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme item: &VoltRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - api::PaymentMethodData::Card(req_card) => { - let card = VoltCard { - name: req_card.card_holder_name, - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.to_owned(), - card, - }) + api::PaymentMethodData::BankRedirect(ref bank_redirect) => match bank_redirect { + api_models::payments::BankRedirectData::OpenBankingUk { .. } => { + let amount = item.amount; + let currency_code = item.router_data.request.currency; + let merchant_internal_reference = + item.router_data.connector_request_reference_id.clone(); + let payment_success_url = item.router_data.request.router_return_url.clone(); + let payment_failure_url = item.router_data.request.router_return_url.clone(); + let payment_pending_url = item.router_data.request.router_return_url.clone(); + let payment_cancel_url = item.router_data.request.router_return_url.clone(); + let notification_url = item.router_data.request.webhook_url.clone(); + let address = item.router_data.get_billing_address()?; + let shopper = ShopperDetails { + email: item.router_data.request.email.clone(), + first_name: address.get_first_name()?.to_owned(), + last_name: address.get_last_name()?.to_owned(), + reference: item.router_data.get_customer_id()?.to_owned(), + }; + let transaction_type = TransactionType::Services; //transaction_type is a form of enum, it is pre defined and value for this can not be taken from user so we are keeping it as Services as this transaction is type of service. + + Ok(Self { + amount, + currency_code, + merchant_internal_reference, + payment_success_url, + payment_failure_url, + payment_pending_url, + payment_cancel_url, + notification_url, + shopper, + transaction_type, + }) + } + api_models::payments::BankRedirectData::BancontactCard { .. } + | api_models::payments::BankRedirectData::Bizum {} + | api_models::payments::BankRedirectData::Blik { .. } + | api_models::payments::BankRedirectData::Eps { .. } + | api_models::payments::BankRedirectData::Giropay { .. } + | api_models::payments::BankRedirectData::Ideal { .. } + | api_models::payments::BankRedirectData::Interac { .. } + | api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { .. } + | api_models::payments::BankRedirectData::OnlineBankingFinland { .. } + | api_models::payments::BankRedirectData::OnlineBankingPoland { .. } + | api_models::payments::BankRedirectData::OnlineBankingSlovakia { .. } + | api_models::payments::BankRedirectData::Przelewy24 { .. } + | api_models::payments::BankRedirectData::Sofort { .. } + | api_models::payments::BankRedirectData::Trustly { .. } + | api_models::payments::BankRedirectData::OnlineBankingFpx { .. } + | api_models::payments::BankRedirectData::OnlineBankingThailand { .. } => { + Err(errors::ConnectorError::NotSupported { + message: utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Volt", + } + .into()) + } + }, + api_models::payments::PaymentMethodData::Card(_) + | api_models::payments::PaymentMethodData::CardRedirect(_) + | api_models::payments::PaymentMethodData::Wallet(_) + | api_models::payments::PaymentMethodData::PayLater(_) + | api_models::payments::PaymentMethodData::BankDebit(_) + | api_models::payments::PaymentMethodData::BankTransfer(_) + | api_models::payments::PaymentMethodData::Crypto(_) + | api_models::payments::PaymentMethodData::MandatePayment + | api_models::payments::PaymentMethodData::Reward + | api_models::payments::PaymentMethodData::Upi(_) + | api_models::payments::PaymentMethodData::Voucher(_) + | api_models::payments::PaymentMethodData::GiftCard(_) => { + Err(errors::ConnectorError::NotSupported { + message: utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Volt", + } + .into()) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct VoltAuthUpdateRequest { + grant_type: String, + client_id: Secret, + client_secret: Secret, + username: Secret, + password: Secret, +} + +impl TryFrom<&types::RefreshTokenRouterData> for VoltAuthUpdateRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefreshTokenRouterData) -> Result { + let auth = VoltAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + grant_type: PASSWORD.to_string(), + username: auth.username, + password: auth.password, + client_id: auth.client_id, + client_secret: auth.client_secret, + }) + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct VoltAuthUpdateResponse { + pub access_token: Secret, + pub token_type: String, + pub expires_in: i64, + pub refresh_token: String, +} + +impl TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, + }), + ..item.data + }) + } +} + pub struct VoltAuthType { - pub(super) api_key: Secret, + pub(super) username: Secret, + pub(super) password: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, } impl TryFrom<&types::ConnectorAuthType> for VoltAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + types::ConnectorAuthType::MultiAuthKey { + api_key, + key1, + api_secret, + key2, + } => Ok(Self { + username: api_key.to_owned(), + password: api_secret.to_owned(), + client_id: key1.to_owned(), + client_secret: key2.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum VoltPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} - impl From for enums::AttemptStatus { fn from(item: VoltPaymentStatus) -> Self { match item { - VoltPaymentStatus::Succeeded => Self::Charged, - VoltPaymentStatus::Failed => Self::Failure, - VoltPaymentStatus::Processing => Self::Authorizing, + VoltPaymentStatus::Completed + | VoltPaymentStatus::Received + | VoltPaymentStatus::Settled => Self::Charged, + VoltPaymentStatus::DelayedAtBank => Self::Pending, + VoltPaymentStatus::NewPayment + | VoltPaymentStatus::BankRedirect + | VoltPaymentStatus::AwaitingCheckoutAuthorisation => Self::AuthenticationPending, + VoltPaymentStatus::RefusedByBank + | VoltPaymentStatus::RefusedByRisk + | VoltPaymentStatus::NotReceived + | VoltPaymentStatus::ErrorAtBank + | VoltPaymentStatus::CancelledByUser + | VoltPaymentStatus::AbandonedByUser + | VoltPaymentStatus::Failed => Self::Failure, } } } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct VoltPaymentsResponse { - status: VoltPaymentStatus, + checkout_url: String, id: String, } @@ -126,16 +269,73 @@ impl type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData, + ) -> Result { + let url = item.response.checkout_url; + let redirection_data = Some(services::RedirectForm::Form { + endpoint: url, + method: services::Method::Get, + form_fields: Default::default(), + }); + Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.id), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum VoltPaymentStatus { + NewPayment, + Completed, + Received, + NotReceived, + BankRedirect, + DelayedAtBank, + AwaitingCheckoutAuthorisation, + RefusedByBank, + RefusedByRisk, + ErrorAtBank, + CancelledByUser, + AbandonedByUser, + Failed, + Settled, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltPsyncResponse { + status: VoltPaymentStatus, + id: String, + merchant_internal_reference: Option, +} + +impl TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status), response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item + .response + .merchant_internal_reference + .or(Some(item.response.id)), }), ..item.data }) @@ -147,13 +347,15 @@ impl #[derive(Default, Debug, Serialize)] pub struct VoltRefundRequest { pub amount: i64, + pub external_reference: String, } impl TryFrom<&VoltRouterData<&types::RefundsRouterData>> for VoltRefundRequest { type Error = error_stack::Report; fn try_from(item: &VoltRouterData<&types::RefundsRouterData>) -> Result { Ok(Self { - amount: item.amount.to_owned(), + amount: item.router_data.request.refund_amount, + external_reference: item.router_data.request.refund_id.clone(), }) } } @@ -180,10 +382,9 @@ impl From for enums::RefundStatus { } } -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Deserialize)] pub struct RefundResponse { id: String, - status: RefundStatus, } impl TryFrom> @@ -196,34 +397,28 @@ impl TryFrom> Ok(Self { response: Ok(types::RefundsResponseData { connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + refund_status: enums::RefundStatus::Pending, //We get Refund Status only by Webhooks }), ..item.data }) } } -impl TryFrom> - for types::RefundsRouterData -{ - type Error = error_stack::Report; - fn try_from( - item: types::RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct VoltErrorResponse { + pub exception: VoltErrorException, } #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct VoltErrorResponse { - pub status_code: u16, - pub code: String, +#[serde(rename_all = "camelCase")] +pub struct VoltErrorException { + pub code: u64, + pub message: String, + pub error_list: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct VoltErrorList { + pub property: String, pub message: String, - pub reason: Option, } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index c0d6c576dd56..11cdc49dd64f 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1502,6 +1502,10 @@ pub(crate) fn validate_auth_and_metadata_type( tsys::transformers::TsysAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Volt => { + volt::transformers::VoltAuthType::try_from(val)?; + Ok(()) + } api_enums::Connector::Wise => { wise::transformers::WiseAuthType::try_from(val)?; Ok(()) diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 9f1dbad1428d..261195d166cb 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -958,6 +958,11 @@ impl TryFrom for AccessTokenRequestData { app_id: api_key, id: Some(key1), }), + ConnectorAuthType::MultiAuthKey { api_key, key1, .. } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + _ => Err(errors::ApiErrorResponse::InvalidDataValue { field_name: "connector_account_details", }), diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 00e40cc908b3..8f5a0f8a59f2 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -362,7 +362,7 @@ impl ConnectorData { enums::Connector::Paypal => Ok(Box::new(&connector::Paypal)), enums::Connector::Trustpay => Ok(Box::new(&connector::Trustpay)), enums::Connector::Tsys => Ok(Box::new(&connector::Tsys)), - // enums::Connector::Volt => Ok(Box::new(&connector::Volt)), it is added as template code for future usage + enums::Connector::Volt => Ok(Box::new(&connector::Volt)), enums::Connector::Zen => Ok(Box::new(&connector::Zen)), enums::Connector::Signifyd | enums::Connector::Plaid => { Err(report!(errors::ConnectorError::InvalidConnectorName) diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 8b5988dcb47e..45d0bde9d323 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -3935,6 +3935,7 @@ "stripe", "trustpay", "tsys", + "volt", "wise", "worldline", "worldpay", diff --git a/postman/collection-dir/volt/.auth.json b/postman/collection-dir/volt/.auth.json new file mode 100644 index 000000000000..915a28357900 --- /dev/null +++ b/postman/collection-dir/volt/.auth.json @@ -0,0 +1,22 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + } +} diff --git a/postman/collection-dir/volt/.event.meta.json b/postman/collection-dir/volt/.event.meta.json new file mode 100644 index 000000000000..2df9d47d936d --- /dev/null +++ b/postman/collection-dir/volt/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/.info.json b/postman/collection-dir/volt/.info.json new file mode 100644 index 000000000000..802b98a9e8f6 --- /dev/null +++ b/postman/collection-dir/volt/.info.json @@ -0,0 +1,9 @@ +{ + "info": { + "_postman_id": "ac5a089e-b4a3-43b2-8938-b2e44056e455", + "name": "volt", + "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "27008363" + } +} diff --git a/postman/collection-dir/volt/.meta.json b/postman/collection-dir/volt/.meta.json new file mode 100644 index 000000000000..91b6a65c5bc6 --- /dev/null +++ b/postman/collection-dir/volt/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Health check", + "Flow Testcases" + ] +} diff --git a/postman/collection-dir/volt/.variable.json b/postman/collection-dir/volt/.variable.json new file mode 100644 index 000000000000..755dab38d54a --- /dev/null +++ b/postman/collection-dir/volt/.variable.json @@ -0,0 +1,101 @@ +{ + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + }, + { + "key": "admin_api_key", + "value": "", + "type": "string" + }, + { + "key": "api_key", + "value": "", + "type": "string" + }, + { + "key": "merchant_id", + "value": "" + }, + { + "key": "payment_id", + "value": "" + }, + { + "key": "customer_id", + "value": "" + }, + { + "key": "mandate_id", + "value": "" + }, + { + "key": "payment_method_id", + "value": "" + }, + { + "key": "refund_id", + "value": "" + }, + { + "key": "merchant_connector_id", + "value": "" + }, + { + "key": "client_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_api_key", + "value": "", + "type": "string" + }, + { + "key": "connector_api_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_key1", + "value": "", + "type": "string" + }, + { + "key": "connector_key2", + "value": "", + "type": "string" + }, + { + "key": "publishable_key", + "value": "", + "type": "string" + }, + { + "key": "api_key_id", + "value": "", + "type": "string" + }, + { + "key": "payment_token", + "value": "" + }, + { + "key": "gateway_merchant_id", + "value": "", + "type": "string" + }, + { + "key": "certificate", + "value": "", + "type": "string" + }, + { + "key": "certificate_keys", + "value": "", + "type": "string" + } + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/.meta.json b/postman/collection-dir/volt/Flow Testcases/.meta.json new file mode 100644 index 000000000000..bd972090b19e --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "QuickStart", + "Happy Cases" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/.meta.json new file mode 100644 index 000000000000..2429b1c3988c --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/.meta.json @@ -0,0 +1,8 @@ +{ + "childrenOrder": [ + "Scenario1-Create payment with confirm true", + "Scenario2-Create payment with confirm false", + "Scenario3-Create payment without PMD", + "Scenario4-Bank Redirect-open_banking_uk" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json new file mode 100644 index 000000000000..60051ecca220 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js new file mode 100644 index 000000000000..3fd8b3c7cbe6 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js @@ -0,0 +1,80 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json new file mode 100644 index 000000000000..6bfc68500963 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -0,0 +1,102 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "EUR", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "payment_method": "bank_redirect", + "payment_method_type": "open_banking_uk", + "payment_method_data": { + "bank_redirect": { + "open_banking_uk": { + "issuer": "citi", + "country": "GB" + } + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "volt" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..d1f1dc048ae2 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js @@ -0,0 +1,80 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json new file mode 100644 index 000000000000..b9ebc1be4aa3 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json new file mode 100644 index 000000000000..57d3f8e2bc7e --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json new file mode 100644 index 000000000000..4ac527d834af --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js new file mode 100644 index 000000000000..6a2040fb5b82 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -0,0 +1,103 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", + function () { + pm.expect(jsonData.amount).to.eql(6540); + }, + ); +} + +// Response body should have value "6540" for "amount_capturable" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 6540'", + function () { + pm.expect(jsonData.amount_capturable).to.eql(6540); + }, + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json new file mode 100644 index 000000000000..16f6e13983f8 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json @@ -0,0 +1,63 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "client_secret": "{{client_secret}}" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js new file mode 100644 index 000000000000..55dc35b91280 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_confirmation" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'", + function () { + pm.expect(jsonData.status).to.eql("requires_confirmation"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json new file mode 100644 index 000000000000..a4192d96b4a7 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -0,0 +1,102 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "EUR", + "confirm": false, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "payment_method": "bank_redirect", + "payment_method_type": "open_banking_uk", + "payment_method_data": { + "bank_redirect": { + "open_banking_uk": { + "issuer": "citi", + "country": "GB" + } + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "volt" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..0463f0e86c0a --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js @@ -0,0 +1,91 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", + function () { + pm.expect(jsonData.amount).to.eql(6540); + }, + ); +} + +// Response body should have value "6540" for "amount_capturable" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 6540'", + function () { + pm.expect(jsonData.amount_capturable).to.eql(6540); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json new file mode 100644 index 000000000000..b9ebc1be4aa3 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/.meta.json new file mode 100644 index 000000000000..57d3f8e2bc7e --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json new file mode 100644 index 000000000000..4ac527d834af --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.prerequest.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js new file mode 100644 index 000000000000..8bbfce6d5b59 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js @@ -0,0 +1,73 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json new file mode 100644 index 000000000000..f0f67b12fb46 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json @@ -0,0 +1,73 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "bank_redirect", + "payment_method_type": "open_banking_uk", + "payment_method_data": { + "bank_redirect": { + "open_banking_uk": { + "issuer": "citi", + "country": "GB" + } + } + }, + "client_secret": "{{client_secret}}" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/event.test.js new file mode 100644 index 000000000000..0444324000a6 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json new file mode 100644 index 000000000000..7323f440e645 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json @@ -0,0 +1,83 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "EUR", + "confirm": false, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "singh" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "volt" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..9053ddab13be --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json new file mode 100644 index 000000000000..b9ebc1be4aa3 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/.meta.json new file mode 100644 index 000000000000..57d3f8e2bc7e --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/.event.meta.json new file mode 100644 index 000000000000..4ac527d834af --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.prerequest.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.test.js new file mode 100644 index 000000000000..9624b67240a6 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/event.test.js @@ -0,0 +1,103 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} + +// Response body should have "next_action.redirect_to_url" +pm.test( + "[POST]::/payments - Content check if 'next_action.redirect_to_url' exists", + function () { + pm.expect(typeof jsonData.next_action.redirect_to_url !== "undefined").to.be + .true; + }, +); + +// Response body should have value "open_banking_uk" for "payment_method_type" +if (jsonData?.payment_method_type) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'open_banking_uk'", + function () { + pm.expect(jsonData.payment_method_type).to.eql("open_banking_uk"); + }, + ); +} + +// Response body should have value "volt" for "connector" +if (jsonData?.connector) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'volt'", + function () { + pm.expect(jsonData.connector).to.eql("volt"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/request.json new file mode 100644 index 000000000000..f0f67b12fb46 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/request.json @@ -0,0 +1,73 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "bank_redirect", + "payment_method_type": "open_banking_uk", + "payment_method_data": { + "bank_redirect": { + "open_banking_uk": { + "issuer": "citi", + "country": "GB" + } + } + }, + "client_secret": "{{client_secret}}" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/event.test.js new file mode 100644 index 000000000000..0444324000a6 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/request.json new file mode 100644 index 000000000000..7323f440e645 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/request.json @@ -0,0 +1,83 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "EUR", + "confirm": false, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "singh" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "volt" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/event.test.js b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..9053ddab13be --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/request.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/request.json new file mode 100644 index 000000000000..b9ebc1be4aa3 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/response.json b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/Happy Cases/Scenario4-Bank Redirect-open_banking_uk/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/.meta.json new file mode 100644 index 000000000000..e3596ba357bc --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/.meta.json @@ -0,0 +1,9 @@ +{ + "childrenOrder": [ + "Merchant Account - Create", + "API Key - Create", + "Payment Connector - Create", + "Payments - Create", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/event.test.js new file mode 100644 index 000000000000..4e27c5a50253 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/event.test.js @@ -0,0 +1,46 @@ +// Validate status 2xx +pm.test("[POST]::/api_keys/:merchant_id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/api_keys/:merchant_id - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id +if (jsonData?.key_id) { + pm.collectionVariables.set("api_key_id", jsonData.key_id); + console.log( + "- use {{api_key_id}} as collection variable for value", + jsonData.key_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.", + ); +} + +// pm.collectionVariables - Set api_key as variable for jsonData.api_key +if (jsonData?.api_key) { + pm.collectionVariables.set("api_key", jsonData.api_key); + console.log( + "- use {{api_key}} as collection variable for value", + jsonData.api_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.", + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/request.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/request.json new file mode 100644 index 000000000000..b89ff6896855 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/request.json @@ -0,0 +1,52 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw_json_formatted": { + "name": "API Key 1", + "description": null, + "expiration": "2099-09-23T01:02:03.000Z" + } + }, + "url": { + "raw": "{{baseUrl}}/api_keys/:merchant_id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api_keys", + ":merchant_id" + ], + "variable": [ + { + "key": "merchant_id", + "value": "{{merchant_id}}" + } + ] + } +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/response.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/API Key - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js new file mode 100644 index 000000000000..7de0d5beb316 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js @@ -0,0 +1,56 @@ +// Validate status 2xx +pm.test("[POST]::/accounts - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/accounts - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_id as variable for jsonData.merchant_id +if (jsonData?.merchant_id) { + pm.collectionVariables.set("merchant_id", jsonData.merchant_id); + console.log( + "- use {{merchant_id}} as collection variable for value", + jsonData.merchant_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_id}}, as jsonData.merchant_id is undefined.", + ); +} + +// pm.collectionVariables - Set api_key as variable for jsonData.api_key +if (jsonData?.api_key) { + pm.collectionVariables.set("api_key", jsonData.api_key); + console.log( + "- use {{api_key}} as collection variable for value", + jsonData.api_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.", + ); +} + +// pm.collectionVariables - Set publishable_key as variable for jsonData.publishable_key +if (jsonData?.publishable_key) { + pm.collectionVariables.set("publishable_key", jsonData.publishable_key); + console.log( + "- use {{publishable_key}} as collection variable for value", + jsonData.publishable_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{publishable_key}}, as jsonData.publishable_key is undefined.", + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/request.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/request.json new file mode 100644 index 000000000000..9f0ff5575c00 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/request.json @@ -0,0 +1,95 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "merchant_id": "postman_merchant_GHAction_{{$guid}}", + "locker_id": "m0010", + "merchant_name": "NewAge Retailer", + "merchant_details": { + "primary_contact_person": "John Test", + "primary_email": "JohnTest@test.com", + "primary_phone": "sunt laborum", + "secondary_contact_person": "John Test2", + "secondary_email": "JohnTest2@test.com", + "secondary_phone": "cillum do dolor id", + "website": "www.example.com", + "about_business": "Online Retail with a wide selection of organic products for North America", + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US" + } + }, + "return_url": "https://duck.com", + "webhook_details": { + "webhook_version": "1.0.1", + "webhook_username": "ekart_retail", + "webhook_password": "password_ekart@123", + "payment_created_enabled": true, + "payment_succeeded_enabled": true, + "payment_failed_enabled": true + }, + "sub_merchants_enabled": false, + "metadata": { + "city": "NY", + "unit": "245" + }, + "primary_business_details": [ + { + "country": "US", + "business": "default" + } + ] + } + }, + "url": { + "raw": "{{baseUrl}}/accounts", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "accounts" + ] + }, + "description": "Create a new account for a merchant. The merchant could be a seller or retailer or client who likes to receive and send payments." +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/response.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Merchant Account - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js new file mode 100644 index 000000000000..88e92d8d84a2 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js @@ -0,0 +1,39 @@ +// Validate status 2xx +pm.test( + "[POST]::/account/:account_id/connectors - Status code is 2xx", + function () { + pm.response.to.be.success; + }, +); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/account/:account_id/connectors - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id +if (jsonData?.merchant_connector_id) { + pm.collectionVariables.set( + "merchant_connector_id", + jsonData.merchant_connector_id, + ); + console.log( + "- use {{merchant_connector_id}} as collection variable for value", + jsonData.merchant_connector_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.", + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/request.json new file mode 100644 index 000000000000..4c086eea1a6a --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -0,0 +1,93 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "connector_type": "fiz_operations", + "connector_name": "volt", + "business_country": "US", + "business_label": "default", + "connector_account_details": { + "auth_type": "MultiAuthKey", + "api_key": "{{connector_api_key}}", + "api_secret": "{{connector_api_secret}}", + "key1": "{{connector_key1}}", + "key2": "{{connector_key2}}" + }, + "test_mode": false, + "disabled": false, + "payment_methods_enabled": [ + { + "payment_method": "bank_redirect", + "payment_method_types": [ + { + "payment_method_type": "open_banking_uk", + "payment_experience": null, + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + } + ] + } + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors" + ], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}", + "description": "(Required) The unique identifier for the merchant account" + } + ] + }, + "description": "Create a new Payment Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialised services like Fraud / Accounting etc." +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/response.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payment Connector - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/event.test.js b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/event.test.js new file mode 100644 index 000000000000..a6947db94c0b --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/event.test.js @@ -0,0 +1,61 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/request.json new file mode 100644 index 000000000000..6bfc68500963 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/request.json @@ -0,0 +1,102 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "EUR", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "payment_method": "bank_redirect", + "payment_method_type": "open_banking_uk", + "payment_method_data": { + "bank_redirect": { + "open_banking_uk": { + "issuer": "citi", + "country": "GB" + } + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "volt" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/response.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..d0a02af74367 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js @@ -0,0 +1,61 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/request.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/request.json new file mode 100644 index 000000000000..c71774083b2c --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/request.json @@ -0,0 +1,27 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/response.json b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Flow Testcases/QuickStart/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/Health check/.meta.json b/postman/collection-dir/volt/Health check/.meta.json new file mode 100644 index 000000000000..66ee7e50cab8 --- /dev/null +++ b/postman/collection-dir/volt/Health check/.meta.json @@ -0,0 +1,5 @@ +{ + "childrenOrder": [ + "New Request" + ] +} diff --git a/postman/collection-dir/volt/Health check/New Request/.event.meta.json b/postman/collection-dir/volt/Health check/New Request/.event.meta.json new file mode 100644 index 000000000000..688c85746ef1 --- /dev/null +++ b/postman/collection-dir/volt/Health check/New Request/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/volt/Health check/New Request/event.test.js b/postman/collection-dir/volt/Health check/New Request/event.test.js new file mode 100644 index 000000000000..b490b8be090f --- /dev/null +++ b/postman/collection-dir/volt/Health check/New Request/event.test.js @@ -0,0 +1,4 @@ +// Validate status 2xx +pm.test("[POST]::/accounts - Status code is 2xx", function () { + pm.response.to.be.success; +}); diff --git a/postman/collection-dir/volt/Health check/New Request/request.json b/postman/collection-dir/volt/Health check/New Request/request.json new file mode 100644 index 000000000000..4cc8d4b1a966 --- /dev/null +++ b/postman/collection-dir/volt/Health check/New Request/request.json @@ -0,0 +1,20 @@ +{ + "method": "GET", + "header": [ + { + "key": "x-feature", + "value": "router-custom", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{baseUrl}}/health", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] + } +} diff --git a/postman/collection-dir/volt/Health check/New Request/response.json b/postman/collection-dir/volt/Health check/New Request/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/volt/Health check/New Request/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/volt/event.prerequest.js b/postman/collection-dir/volt/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/volt/event.test.js b/postman/collection-dir/volt/event.test.js new file mode 100644 index 000000000000..fb52caec30fc --- /dev/null +++ b/postman/collection-dir/volt/event.test.js @@ -0,0 +1,13 @@ +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log("[LOG]::payment_id - " + jsonData.payment_id); +} + +console.log("[LOG]::x-request-id - " + pm.response.headers.get("x-request-id")); From d6824710015b134a50986b3e85d3840902322711 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:24:28 +0530 Subject: [PATCH 14/38] feat(organization): add organization table (#2669) --- crates/api_models/src/lib.rs | 1 + crates/api_models/src/organization.rs | 13 ++ crates/diesel_models/src/lib.rs | 1 + crates/diesel_models/src/organization.rs | 35 +++++ crates/diesel_models/src/query.rs | 1 + .../diesel_models/src/query/organization.rs | 38 +++++ crates/diesel_models/src/schema.rs | 12 ++ crates/router/src/core/admin.rs | 24 +++- crates/router/src/db.rs | 2 + crates/router/src/db/organization.rs | 131 ++++++++++++++++++ crates/router/src/types/transformers.rs | 11 ++ crates/storage_impl/src/mock_db.rs | 2 + .../down.sql | 2 + .../up.sql | 5 + .../Merchant Account - Create/event.test.js | 8 ++ 15 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 crates/api_models/src/organization.rs create mode 100644 crates/diesel_models/src/organization.rs create mode 100644 crates/diesel_models/src/query/organization.rs create mode 100644 crates/router/src/db/organization.rs create mode 100644 migrations/2023-10-23-101023_add_organization_table/down.sql create mode 100644 migrations/2023-10-23-101023_add_organization_table/up.sql diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 29ad9be051b6..dab1b46adbad 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -11,6 +11,7 @@ pub mod ephemeral_key; pub mod errors; pub mod files; pub mod mandates; +pub mod organization; pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] diff --git a/crates/api_models/src/organization.rs b/crates/api_models/src/organization.rs new file mode 100644 index 000000000000..db4ae21a0dfb --- /dev/null +++ b/crates/api_models/src/organization.rs @@ -0,0 +1,13 @@ +pub struct OrganizationNew { + pub org_id: String, + pub org_name: Option, +} + +impl OrganizationNew { + pub fn new(org_name: Option) -> Self { + Self { + org_id: common_utils::generate_id_with_default_len("org"), + org_name, + } + } +} diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index 3de35d73f822..528446678015 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -23,6 +23,7 @@ pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; +pub mod organization; pub mod payment_attempt; pub mod payment_intent; pub mod payment_link; diff --git a/crates/diesel_models/src/organization.rs b/crates/diesel_models/src/organization.rs new file mode 100644 index 000000000000..2f407b8cbfdd --- /dev/null +++ b/crates/diesel_models/src/organization.rs @@ -0,0 +1,35 @@ +use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; + +use crate::schema::organization; + +#[derive(Clone, Debug, Identifiable, Queryable)] +#[diesel(table_name = organization, primary_key(org_id))] +pub struct Organization { + pub org_id: String, + pub org_name: Option, +} + +#[derive(Clone, Debug, Insertable)] +#[diesel(table_name = organization, primary_key(org_id))] +pub struct OrganizationNew { + pub org_id: String, + pub org_name: Option, +} + +#[derive(Clone, Debug, AsChangeset)] +#[diesel(table_name = organization)] +pub struct OrganizationUpdateInternal { + org_name: Option, +} + +pub enum OrganizationUpdate { + Update { org_name: Option }, +} + +impl From for OrganizationUpdateInternal { + fn from(value: OrganizationUpdate) -> Self { + match value { + OrganizationUpdate::Update { org_name } => Self { org_name }, + } + } +} diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index ef4ab9f32fa3..6b705e29873e 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -16,6 +16,7 @@ pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; +pub mod organization; pub mod payment_attempt; pub mod payment_intent; pub mod payment_link; diff --git a/crates/diesel_models/src/query/organization.rs b/crates/diesel_models/src/query/organization.rs new file mode 100644 index 000000000000..0bea1012c9a9 --- /dev/null +++ b/crates/diesel_models/src/query/organization.rs @@ -0,0 +1,38 @@ +use diesel::{associations::HasTable, ExpressionMethods}; +use router_env::tracing::{self, instrument}; + +use crate::{ + organization::*, query::generics, schema::organization::dsl, PgPooledConn, StorageResult, +}; + +impl OrganizationNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Organization { + pub async fn find_by_org_id(conn: &PgPooledConn, org_id: String) -> StorageResult { + generics::generic_find_one::<::Table, _, _>(conn, dsl::org_id.eq(org_id)) + .await + } + + pub async fn update_by_org_id( + conn: &PgPooledConn, + org_id: String, + update: OrganizationUpdate, + ) -> StorageResult { + generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + dsl::org_id.eq(org_id), + OrganizationUpdateInternal::from(update), + ) + .await + } +} diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 6c1b13bacf9d..926b31c0ceb4 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -505,6 +505,17 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + organization (org_id) { + #[max_length = 32] + org_id -> Varchar, + org_name -> Nullable, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -879,6 +890,7 @@ diesel::allow_tables_to_appear_in_same_query!( merchant_account, merchant_connector_account, merchant_key_store, + organization, payment_attempt, payment_intent, payment_link, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 11cdc49dd64f..2e10aeaaf288 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -26,13 +26,11 @@ use crate::{ types::{self as domain_types, AsyncLift}, }, storage::{self, enums::MerchantStorageScheme}, - transformers::ForeignTryFrom, + transformers::{ForeignFrom, ForeignTryFrom}, }, utils::{self, OptionExt}, }; -const DEFAULT_ORG_ID: &str = "org_abcdefghijklmn"; - #[inline] pub fn create_merchant_publishable_key() -> String { format!( @@ -146,6 +144,24 @@ pub async fn create_merchant_account( }) .transpose()?; + let organization_id = if let Some(organization_id) = req.organization_id.as_ref() { + db.find_organization_by_org_id(organization_id) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "organization with the given id does not exist".to_string(), + })?; + organization_id.to_string() + } else { + let new_organization = api_models::organization::OrganizationNew::new(None); + let db_organization = ForeignFrom::foreign_from(new_organization); + let organization = db + .insert_organization(db_organization) + .await + .to_duplicate_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error when creating organization")?; + organization.org_id + }; + let mut merchant_account = async { Ok(domain::MerchantAccount { merchant_id: req.merchant_id, @@ -177,7 +193,7 @@ pub async fn create_merchant_account( intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), payout_routing_algorithm: req.payout_routing_algorithm, id: None, - organization_id: req.organization_id.unwrap_or(DEFAULT_ORG_ID.to_string()), + organization_id, is_recon_enabled: false, default_profile: None, recon_status: diesel_models::enums::ReconStatus::NotRequested, diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 4270d2ab6490..f5647b3d1778 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -17,6 +17,7 @@ pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; +pub mod organization; pub mod payment_link; pub mod payment_method; pub mod payout_attempt; @@ -75,6 +76,7 @@ pub trait StorageInterface: + payment_link::PaymentLinkInterface + RedisConnInterface + business_profile::BusinessProfileInterface + + organization::OrganizationInterface + 'static { fn get_scheduler_db(&self) -> Box; diff --git a/crates/router/src/db/organization.rs b/crates/router/src/db/organization.rs new file mode 100644 index 000000000000..ddb8d9f9d907 --- /dev/null +++ b/crates/router/src/db/organization.rs @@ -0,0 +1,131 @@ +use common_utils::errors::CustomResult; +use diesel_models::organization as storage; +use error_stack::IntoReport; + +use crate::{connection, core::errors, services::Store}; + +#[async_trait::async_trait] +pub trait OrganizationInterface { + async fn insert_organization( + &self, + organization: storage::OrganizationNew, + ) -> CustomResult; + + async fn find_organization_by_org_id( + &self, + org_id: &str, + ) -> CustomResult; + + async fn update_organization_by_org_id( + &self, + user_id: &str, + update: storage::OrganizationUpdate, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl OrganizationInterface for Store { + async fn insert_organization( + &self, + organization: storage::OrganizationNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + organization + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + } + + async fn find_organization_by_org_id( + &self, + org_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + storage::Organization::find_by_org_id(&conn, org_id.to_string()) + .await + .map_err(Into::into) + .into_report() + } + + async fn update_organization_by_org_id( + &self, + org_id: &str, + update: storage::OrganizationUpdate, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + + storage::Organization::update_by_org_id(&conn, org_id.to_string(), update) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl OrganizationInterface for super::MockDb { + async fn insert_organization( + &self, + organization: storage::OrganizationNew, + ) -> CustomResult { + let mut organizations = self.organizations.lock().await; + + if organizations + .iter() + .any(|org| org.org_id == organization.org_id) + { + Err(errors::StorageError::DuplicateValue { + entity: "org_id", + key: None, + })? + } + let org = storage::Organization { + org_id: organization.org_id.clone(), + org_name: organization.org_name, + }; + organizations.push(org.clone()); + Ok(org) + } + + async fn find_organization_by_org_id( + &self, + org_id: &str, + ) -> CustomResult { + let organizations = self.organizations.lock().await; + + organizations + .iter() + .find(|org| org.org_id == org_id) + .cloned() + .ok_or( + errors::StorageError::ValueNotFound(format!( + "No organization available for org_id = {org_id}" + )) + .into(), + ) + } + + async fn update_organization_by_org_id( + &self, + org_id: &str, + update: storage::OrganizationUpdate, + ) -> CustomResult { + let mut organizations = self.organizations.lock().await; + + organizations + .iter_mut() + .find(|org| org.org_id == org_id) + .map(|org| match &update { + storage::OrganizationUpdate::Update { org_name } => storage::Organization { + org_name: org_name.clone(), + ..org.to_owned() + }, + }) + .ok_or( + errors::StorageError::ValueNotFound(format!( + "No organization available for org_id = {org_id}" + )) + .into(), + ) + } +} diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 31ec31cfa988..d38497c7100a 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -861,3 +861,14 @@ impl From for payments::AddressDetails { } } } + +impl ForeignFrom + for diesel_models::organization::OrganizationNew +{ + fn foreign_from(item: api_models::organization::OrganizationNew) -> Self { + Self { + org_id: item.org_id, + org_name: item.org_name, + } + } +} diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index 8674f7615684..76bdb1160ccc 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -41,6 +41,7 @@ pub struct MockDb { pub business_profiles: Arc>>, pub reverse_lookups: Arc>>, pub payment_link: Arc>>, + pub organizations: Arc>>, } impl MockDb { @@ -74,6 +75,7 @@ impl MockDb { business_profiles: Default::default(), reverse_lookups: Default::default(), payment_link: Default::default(), + organizations: Default::default(), }) } } diff --git a/migrations/2023-10-23-101023_add_organization_table/down.sql b/migrations/2023-10-23-101023_add_organization_table/down.sql new file mode 100644 index 000000000000..9f267438f537 --- /dev/null +++ b/migrations/2023-10-23-101023_add_organization_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS ORGANIZATION; diff --git a/migrations/2023-10-23-101023_add_organization_table/up.sql b/migrations/2023-10-23-101023_add_organization_table/up.sql new file mode 100644 index 000000000000..6e8cf3cf5424 --- /dev/null +++ b/migrations/2023-10-23-101023_add_organization_table/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS ORGANIZATION ( + org_id VARCHAR(32) PRIMARY KEY NOT NULL, + org_name TEXT +); diff --git a/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - Create/event.test.js b/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - Create/event.test.js index 9c5a8900dc03..41eecccf83fc 100644 --- a/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - Create/event.test.js +++ b/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - Create/event.test.js @@ -67,3 +67,11 @@ if (jsonData?.merchant_id) { "INFO - Unable to assign variable {{organization_id}}, as jsonData.organization_id is undefined.", ); } + +// Response body should have "mandate_id" +pm.test( + "[POST]::/accounts - Organization id is generated", + function () { + pm.expect(typeof jsonData.organization_id !== "undefined").to.be.true; + }, +); From 577ef1ae1a4718aaf90175d49e2a786af255fd63 Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Mon, 30 Oct 2023 17:08:18 +0530 Subject: [PATCH 15/38] fix: make kv log extraction easier (#2666) --- crates/storage_impl/src/redis/kv_store.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 32fe21f758a3..52f58f6a7539 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -107,7 +107,7 @@ where let result = async { match op { KvOperation::Hset(value, sql) => { - logger::debug!("Operation: {operation} value: {value:?}"); + logger::debug!(kv_operation= %operation, value = ?value); redis_conn .set_hash_fields(key, value, Some(consts::KV_TTL)) @@ -133,7 +133,7 @@ where } KvOperation::HSetNx(field, value, sql) => { - logger::debug!("Operation: {operation} value: {value:?}"); + logger::debug!(kv_operation= %operation, value = ?value); let result = redis_conn .serialize_and_set_hash_field_if_not_exist( @@ -153,7 +153,7 @@ where } KvOperation::SetNx(value, sql) => { - logger::debug!("Operation: {operation} value: {value:?}"); + logger::debug!(kv_operation= %operation, value = ?value); let result = redis_conn .serialize_and_set_key_if_not_exist(key, value, Some(consts::KV_TTL.into())) @@ -178,14 +178,14 @@ where result .await .map(|result| { - logger::debug!("KvOperation {operation} succeeded"); + logger::debug!(kv_operation= %operation, status="success"); let keyvalue = router_env::opentelemetry::KeyValue::new("operation", operation.clone()); metrics::KV_OPERATION_SUCCESSFUL.add(&metrics::CONTEXT, 1, &[keyvalue]); result }) .map_err(|err| { - logger::error!("KvOperation for {operation} failed with {err:?}"); + logger::error!(kv_operation = %operation, status="error", error = ?err); let keyvalue = router_env::opentelemetry::KeyValue::new("operation", operation); metrics::KV_OPERATION_FAILED.add(&metrics::CONTEXT, 1, &[keyvalue]); From 838372ab3f6f3f35b8d884958810bab54cc17244 Mon Sep 17 00:00:00 2001 From: BallaNitesh <126162378+BallaNitesh@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:29:21 +0530 Subject: [PATCH 16/38] feat: add one-click deploy script for HyperSwitch on AWS (EC2, RDS, Redis) (#2730) Co-authored-by: Nishant Joshi Co-authored-by: Venkatesh --- aws/beta_schema.sql | 2252 ++++++++++++++++++++++++++++++ aws/hyperswitch_aws_setup.sh | 340 +++++ aws/hyperswitch_cleanup_setup.sh | 152 ++ 3 files changed, 2744 insertions(+) create mode 100644 aws/beta_schema.sql create mode 100644 aws/hyperswitch_aws_setup.sh create mode 100644 aws/hyperswitch_cleanup_setup.sh diff --git a/aws/beta_schema.sql b/aws/beta_schema.sql new file mode 100644 index 000000000000..439d1792231c --- /dev/null +++ b/aws/beta_schema.sql @@ -0,0 +1,2252 @@ +-- File: migrations/00000000000000_diesel_initial_setup/up.sql +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + + +-- File: migrations/2022-09-29-084920_create_initial_tables/up.sql +-- Types +CREATE TYPE "AttemptStatus" AS ENUM ( + 'started', + 'authentication_failed', + 'juspay_declined', + 'pending_vbv', + 'vbv_successful', + 'authorized', + 'authorization_failed', + 'charged', + 'authorizing', + 'cod_initiated', + 'voided', + 'void_initiated', + 'capture_initiated', + 'capture_failed', + 'void_failed', + 'auto_refunded', + 'partial_charged', + 'pending', + 'failure', + 'payment_method_awaited', + 'confirmation_awaited' +); + +CREATE TYPE "AuthenticationType" AS ENUM ('three_ds', 'no_three_ds'); + +CREATE TYPE "CaptureMethod" AS ENUM ('automatic', 'manual', 'scheduled'); + +CREATE TYPE "ConnectorType" AS ENUM ( + 'payment_processor', + 'payment_vas', + 'fin_operations', + 'fiz_operations', + 'networks', + 'banking_entities', + 'non_banking_finance' +); + +CREATE TYPE "Currency" AS ENUM ( + 'AED', + 'ALL', + 'AMD', + 'ARS', + 'AUD', + 'AWG', + 'AZN', + 'BBD', + 'BDT', + 'BHD', + 'BMD', + 'BND', + 'BOB', + 'BRL', + 'BSD', + 'BWP', + 'BZD', + 'CAD', + 'CHF', + 'CNY', + 'COP', + 'CRC', + 'CUP', + 'CZK', + 'DKK', + 'DOP', + 'DZD', + 'EGP', + 'ETB', + 'EUR', + 'FJD', + 'GBP', + 'GHS', + 'GIP', + 'GMD', + 'GTQ', + 'GYD', + 'HKD', + 'HNL', + 'HRK', + 'HTG', + 'HUF', + 'IDR', + 'ILS', + 'INR', + 'JMD', + 'JOD', + 'JPY', + 'KES', + 'KGS', + 'KHR', + 'KRW', + 'KWD', + 'KYD', + 'KZT', + 'LAK', + 'LBP', + 'LKR', + 'LRD', + 'LSL', + 'MAD', + 'MDL', + 'MKD', + 'MMK', + 'MNT', + 'MOP', + 'MUR', + 'MVR', + 'MWK', + 'MXN', + 'MYR', + 'NAD', + 'NGN', + 'NIO', + 'NOK', + 'NPR', + 'NZD', + 'OMR', + 'PEN', + 'PGK', + 'PHP', + 'PKR', + 'PLN', + 'QAR', + 'RUB', + 'SAR', + 'SCR', + 'SEK', + 'SGD', + 'SLL', + 'SOS', + 'SSP', + 'SVC', + 'SZL', + 'THB', + 'TTD', + 'TWD', + 'TZS', + 'USD', + 'UYU', + 'UZS', + 'YER', + 'ZAR' +); + +CREATE TYPE "EventClass" AS ENUM ('payments'); + +CREATE TYPE "EventObjectType" AS ENUM ('payment_details'); + +CREATE TYPE "EventType" AS ENUM ('payment_succeeded'); + +CREATE TYPE "FutureUsage" AS ENUM ('on_session', 'off_session'); + +CREATE TYPE "IntentStatus" AS ENUM ( + 'succeeded', + 'failed', + 'processing', + 'requires_customer_action', + 'requires_payment_method', + 'requires_confirmation' +); + +CREATE TYPE "MandateStatus" AS ENUM ( + 'active', + 'inactive', + 'pending', + 'revoked' +); + +CREATE TYPE "MandateType" AS ENUM ('single_use', 'multi_use'); + +CREATE TYPE "PaymentFlow" AS ENUM ( + 'vsc', + 'emi', + 'otp', + 'upi_intent', + 'upi_collect', + 'upi_scan_and_pay', + 'sdk' +); + +CREATE TYPE "PaymentMethodIssuerCode" AS ENUM ( + 'jp_hdfc', + 'jp_icici', + 'jp_googlepay', + 'jp_applepay', + 'jp_phonepe', + 'jp_wechat', + 'jp_sofort', + 'jp_giropay', + 'jp_sepa', + 'jp_bacs' +); + +CREATE TYPE "PaymentMethodSubType" AS ENUM ( + 'credit', + 'debit', + 'upi_intent', + 'upi_collect', + 'credit_card_installments', + 'pay_later_installments' +); + +CREATE TYPE "PaymentMethodType" AS ENUM ( + 'card', + 'bank_transfer', + 'netbanking', + 'upi', + 'open_banking', + 'consumer_finance', + 'wallet', + 'payment_container', + 'bank_debit', + 'pay_later' +); + +CREATE TYPE "ProcessTrackerStatus" AS ENUM ( + 'processing', + 'new', + 'pending', + 'process_started', + 'finish' +); + +CREATE TYPE "RefundStatus" AS ENUM ( + 'failure', + 'manual_review', + 'pending', + 'success', + 'transaction_failure' +); + +CREATE TYPE "RefundType" AS ENUM ( + 'instant_refund', + 'regular_refund', + 'retry_refund' +); + +CREATE TYPE "RoutingAlgorithm" AS ENUM ( + 'round_robin', + 'max_conversion', + 'min_cost', + 'custom' +); + +-- Tables +CREATE TABLE address ( + id SERIAL, + address_id VARCHAR(255) PRIMARY KEY, + city VARCHAR(255), + country VARCHAR(255), + line1 VARCHAR(255), + line2 VARCHAR(255), + line3 VARCHAR(255), + state VARCHAR(255), + zip VARCHAR(255), + first_name VARCHAR(255), + last_name VARCHAR(255), + phone_number VARCHAR(255), + country_code VARCHAR(255), + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP +); + +CREATE TABLE configs ( + id SERIAL, + key VARCHAR(255) NOT NULL, + config TEXT NOT NULL, + PRIMARY KEY (key) +); + +CREATE TABLE customers ( + id SERIAL, + customer_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + NAME VARCHAR(255), + email VARCHAR(255), + phone VARCHAR(255), + phone_country_code VARCHAR(255), + description VARCHAR(255), + address JSON, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + metadata JSON, + PRIMARY KEY (customer_id, merchant_id) +); + +CREATE TABLE events ( + id SERIAL PRIMARY KEY, + event_id VARCHAR(255) NOT NULL, + event_type "EventType" NOT NULL, + event_class "EventClass" NOT NULL, + is_webhook_notified BOOLEAN NOT NULL DEFAULT FALSE, + intent_reference_id VARCHAR(255), + primary_object_id VARCHAR(255) NOT NULL, + primary_object_type "EventObjectType" NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP +); + +CREATE TABLE locker_mock_up ( + id SERIAL PRIMARY KEY, + card_id VARCHAR(255) NOT NULL, + external_id VARCHAR(255) NOT NULL, + card_fingerprint VARCHAR(255) NOT NULL, + card_global_fingerprint VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + card_number VARCHAR(255) NOT NULL, + card_exp_year VARCHAR(255) NOT NULL, + card_exp_month VARCHAR(255) NOT NULL, + name_on_card VARCHAR(255), + nickname VARCHAR(255), + customer_id VARCHAR(255), + duplicate BOOLEAN +); + +CREATE TABLE mandate ( + id SERIAL PRIMARY KEY, + mandate_id VARCHAR(255) NOT NULL, + customer_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + payment_method_id VARCHAR(255) NOT NULL, + mandate_status "MandateStatus" NOT NULL, + mandate_type "MandateType" NOT NULL, + customer_accepted_at TIMESTAMP, + customer_ip_address VARCHAR(255), + customer_user_agent VARCHAR(255), + network_transaction_id VARCHAR(255), + previous_transaction_id VARCHAR(255), + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP +); + +CREATE TABLE merchant_account ( + id SERIAL PRIMARY KEY, + merchant_id VARCHAR(255) NOT NULL, + api_key VARCHAR(255), + return_url VARCHAR(255), + enable_payment_response_hash BOOLEAN NOT NULL DEFAULT FALSE, + payment_response_hash_key VARCHAR(255) DEFAULT NULL, + redirect_to_merchant_with_http_post BOOLEAN NOT NULL DEFAULT FALSE, + merchant_name VARCHAR(255), + merchant_details JSON, + webhook_details JSON, + routing_algorithm "RoutingAlgorithm", + custom_routing_rules JSON, + sub_merchants_enabled BOOLEAN DEFAULT FALSE, + parent_merchant_id VARCHAR(255), + publishable_key VARCHAR(255) +); + +CREATE TABLE merchant_connector_account ( + id SERIAL PRIMARY KEY, + merchant_id VARCHAR(255) NOT NULL, + connector_name VARCHAR(255) NOT NULL, + connector_account_details JSON NOT NULL, + test_mode BOOLEAN, + disabled BOOLEAN, + merchant_connector_id SERIAL NOT NULL, + payment_methods_enabled JSON [ ], + connector_type "ConnectorType" NOT NULL DEFAULT 'payment_processor'::"ConnectorType" +); + +CREATE TABLE payment_attempt ( + id SERIAL PRIMARY KEY, + payment_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + txn_id VARCHAR(255) NOT NULL, + status "AttemptStatus" NOT NULL, + amount INTEGER NOT NULL, + currency "Currency", + save_to_locker BOOLEAN, + connector VARCHAR(255) NOT NULL, + error_message TEXT, + offer_amount INTEGER, + surcharge_amount INTEGER, + tax_amount INTEGER, + payment_method_id VARCHAR(255), + payment_method "PaymentMethodType", + payment_flow "PaymentFlow", + redirect BOOLEAN, + connector_transaction_id VARCHAR(255), + capture_method "CaptureMethod", + capture_on TIMESTAMP, + confirm BOOLEAN NOT NULL, + authentication_type "AuthenticationType", + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + last_synced TIMESTAMP +); + +CREATE TABLE payment_intent ( + id SERIAL PRIMARY KEY, + payment_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + status "IntentStatus" NOT NULL, + amount INTEGER NOT NULL, + currency "Currency", + amount_captured INTEGER, + customer_id VARCHAR(255), + description VARCHAR(255), + return_url VARCHAR(255), + metadata JSONB DEFAULT '{}'::JSONB, + connector_id VARCHAR(255), + shipping_address_id VARCHAR(255), + billing_address_id VARCHAR(255), + statement_descriptor_name VARCHAR(255), + statement_descriptor_suffix VARCHAR(255), + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + last_synced TIMESTAMP, + setup_future_usage "FutureUsage", + off_session BOOLEAN, + client_secret VARCHAR(255) +); + +CREATE TABLE payment_methods ( + id SERIAL PRIMARY KEY, + customer_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + payment_method_id VARCHAR(255) NOT NULL, + accepted_currency "Currency" [ ], + scheme VARCHAR(255), + token VARCHAR(255), + cardholder_name VARCHAR(255), + issuer_name VARCHAR(255), + issuer_country VARCHAR(255), + payer_country TEXT [ ], + is_stored BOOLEAN, + swift_code VARCHAR(255), + direct_debit_token VARCHAR(255), + network_transaction_id VARCHAR(255), + created_at TIMESTAMP NOT NULL, + last_modified TIMESTAMP NOT NULL, + payment_method "PaymentMethodType" NOT NULL, + payment_method_type "PaymentMethodSubType", + payment_method_issuer VARCHAR(255), + payment_method_issuer_code "PaymentMethodIssuerCode" +); + +CREATE TABLE process_tracker ( + id VARCHAR(127) PRIMARY KEY, + NAME VARCHAR(255), + tag TEXT [ ] NOT NULL DEFAULT '{}'::TEXT [ ], + runner VARCHAR(255), + retry_count INTEGER NOT NULL, + schedule_time TIMESTAMP, + rule VARCHAR(255) NOT NULL, + tracking_data JSON NOT NULL, + business_status VARCHAR(255) NOT NULL, + status "ProcessTrackerStatus" NOT NULL, + event TEXT [ ] NOT NULL DEFAULT '{}'::TEXT [ ], + created_at TIMESTAMP NOT NULL DEFAULT now(), + updated_at TIMESTAMP NOT NULL DEFAULT now() +); + +CREATE TABLE refund ( + id SERIAL PRIMARY KEY, + internal_reference_id VARCHAR(255) NOT NULL, + refund_id VARCHAR(255) NOT NULL, + payment_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + transaction_id VARCHAR(255) NOT NULL, + connector VARCHAR(255) NOT NULL, + pg_refund_id VARCHAR(255), + external_reference_id VARCHAR(255), + refund_type "RefundType" NOT NULL, + total_amount INTEGER NOT NULL, + currency "Currency" NOT NULL, + refund_amount INTEGER NOT NULL, + refund_status "RefundStatus" NOT NULL, + sent_to_gateway BOOLEAN NOT NULL DEFAULT FALSE, + refund_error_message TEXT, + metadata JSON, + refund_arn VARCHAR(255), + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + description VARCHAR(255) +); + +CREATE TABLE temp_card ( + id SERIAL PRIMARY KEY, + date_created TIMESTAMP NOT NULL, + txn_id VARCHAR(255), + card_info JSON +); + +-- Indices +CREATE INDEX customers_created_at_index ON customers (created_at); + +CREATE UNIQUE INDEX merchant_account_api_key_index ON merchant_account (api_key); + +CREATE UNIQUE INDEX merchant_account_merchant_id_index ON merchant_account (merchant_id); + +CREATE UNIQUE INDEX merchant_account_publishable_key_index ON merchant_account (publishable_key); + +CREATE INDEX merchant_connector_account_connector_type_index ON merchant_connector_account (connector_type); + +CREATE INDEX merchant_connector_account_merchant_id_index ON merchant_connector_account (merchant_id); + +CREATE UNIQUE INDEX payment_attempt_payment_id_merchant_id_index ON payment_attempt (payment_id, merchant_id); + +CREATE UNIQUE INDEX payment_intent_payment_id_merchant_id_index ON payment_intent (payment_id, merchant_id); + +CREATE INDEX payment_methods_created_at_index ON payment_methods (created_at); + +CREATE INDEX payment_methods_customer_id_index ON payment_methods (customer_id); + +CREATE INDEX payment_methods_last_modified_index ON payment_methods (last_modified); + +CREATE INDEX payment_methods_payment_method_id_index ON payment_methods (payment_method_id); + +CREATE INDEX refund_internal_reference_id_index ON refund (internal_reference_id); + +CREATE INDEX refund_payment_id_merchant_id_index ON refund (payment_id, merchant_id); + +CREATE INDEX refund_refund_id_index ON refund (refund_id); + +CREATE UNIQUE INDEX refund_refund_id_merchant_id_index ON refund (refund_id, merchant_id); + +CREATE INDEX temp_card_txn_id_index ON temp_card (txn_id); + + + +-- File: migrations/2022-09-29-093314_create_seed_data/up.sql +INSERT INTO merchant_account ( + merchant_id, + api_key, + merchant_name, + merchant_details, + custom_routing_rules, + publishable_key + ) +VALUES ( + 'juspay_merchant', + 'MySecretApiKey', + 'Juspay Merchant', + '{ "primary_email": "merchant@juspay.in" }', + '[ { "connectors_pecking_order": [ "stripe" ] } ]', + 'pk_MyPublicApiKey' + ); + +INSERT INTO merchant_connector_account ( + merchant_id, + connector_name, + connector_account_details + ) +VALUES ( + 'juspay_merchant', + 'stripe', + '{ "auth_type": "HeaderKey", "api_key": "Basic MyStripeApiKey" }' + ); + + + +-- File: migrations/2022-10-20-100628_add_cancellation_reason/up.sql +ALTER TABLE payment_attempt +ADD COLUMN cancellation_reason VARCHAR(255); + + + +-- File: migrations/2022-10-26-101016_update_payment_attempt_status_intent_status/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD IF NOT EXISTS amount_to_capture INTEGER; +ALTER TYPE "CaptureMethod" ADD VALUE 'manual_multiple' AFTER 'manual'; +ALTER TYPE "IntentStatus" ADD VALUE 'requires_capture'; + + +-- File: migrations/2022-11-03-130214_create_connector_response_table/up.sql +-- Your SQL goes here +CREATE TABLE connector_response ( + id SERIAL PRIMARY KEY, + payment_id VARCHAR(255) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + txn_id VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + connector_name VARCHAR(32) NOT NULL, + connector_transaction_id VARCHAR(255), + authentication_data JSON, + encoded_data TEXT +); + +CREATE UNIQUE INDEX connector_response_id_index ON connector_response (payment_id, merchant_id, txn_id); + + +-- File: migrations/2022-11-08-101705_add_cancel_to_payment_intent_status/up.sql +-- Your SQL goes here +ALTER TYPE "IntentStatus" ADD VALUE 'cancelled' after 'failed'; + + + + +-- File: migrations/2022-11-21-133803_add_mandate_id_in_payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD IF NOT EXISTS mandate_id VARCHAR(255); + + + +-- File: migrations/2022-11-24-095709_add_browser_info_to_payment_attempt/up.sql +ALTER TABLE payment_attempt +ADD COLUMN browser_info JSONB DEFAULT NULL; + + + +-- File: migrations/2022-11-25-121143_add_paypal_pmt/up.sql +-- Your SQL goes here +ALTER TYPE "PaymentMethodType" ADD VALUE 'paypal' after 'pay_later'; + + + +-- File: migrations/2022-11-30-084736_update-index-in-mca/up.sql +CREATE UNIQUE INDEX merchant_connector_account_merchant_id_connector_name_index ON merchant_connector_account (merchant_id, connector_name); + + +-- File: migrations/2022-12-05-090521_single_use_mandate_fields/up.sql +-- Your SQL goes here +ALTER TABLE mandate +ADD IF NOT EXISTS single_use_amount INTEGER DEFAULT NULL, +ADD IF NOT EXISTS single_use_currency "Currency" DEFAULT NULL; + + + +-- File: migrations/2022-12-07-055441_add_use_kv_to_merchant_account/up.sql +-- Your SQL goes here + +CREATE TYPE "MerchantStorageScheme" AS ENUM ( + 'postgres_only', + 'redis_kv' +); + +ALTER TABLE merchant_account ADD COLUMN storage_scheme "MerchantStorageScheme" NOT NULL DEFAULT 'postgres_only'; + + + +-- File: migrations/2022-12-07-133736_make_connector_field_optional/up.sql +ALTER TABLE payment_attempt ALTER COLUMN connector DROP NOT NULL; +ALTER TABLE connector_response ALTER COLUMN connector_name DROP NOT NULL; + + +-- File: migrations/2022-12-09-102635_mandate-connector-and-amount/up.sql +-- Your SQL goes here +ALTER TABLE mandate +RENAME COLUMN single_use_amount TO mandate_amount; +ALTER TABLE mandate +RENAME COLUMN single_use_currency TO mandate_currency; +ALTER TABLE mandate +ADD IF NOT EXISTS amount_captured INTEGER DEFAULT NULL, +ADD IF NOT EXISTS connector VARCHAR(255) NOT NULL DEFAULT 'dummy', +ADD IF NOT EXISTS connector_mandate_id VARCHAR(255) DEFAULT NULL; + + +-- File: migrations/2022-12-10-123613_update_address_and_customer/up.sql +-- Your SQL goes here +ALTER TABLE address +ADD COLUMN customer_id VARCHAR(255) NOT NULL, +ADD COLUMN merchant_id VARCHAR(255) NOT NULL; + +CREATE INDEX address_customer_id_merchant_id_index ON address (customer_id, merchant_id); + +ALTER TABLE customers DROP COLUMN address; + + +-- File: migrations/2022-12-11-190755_update_mock_up/up.sql +-- Your SQL goes here +ALTER TABLE locker_mock_up +ADD COLUMN card_cvc VARCHAR(8); + + +-- File: migrations/2022-12-12-132936_reverse_lookup/up.sql +CREATE TABLE reverse_lookup ( + lookup_id VARCHAR(255) NOT NULL PRIMARY KEY, + sk_id VARCHAR(50) NOT NULL, + pk_id VARCHAR(255) NOT NULL, + source VARCHAR(30) NOT NULL +); + +CREATE INDEX lookup_id_index ON reverse_lookup (lookup_id); + + + +-- File: migrations/2022-12-13-170152_add_connector_metadata/up.sql +ALTER TABLE merchant_connector_account ADD COLUMN metadata JSONB DEFAULT NULL; + + + +-- File: migrations/2022-12-14-074547_error-code-in-payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD IF NOT EXISTS error_code VARCHAR(255) DEFAULT NULL; + + +-- File: migrations/2022-12-14-090419_add_payment_token_in_payment_attempt/up.sql +ALTER TABLE payment_attempt ADD COLUMN payment_token VARCHAR(255); + + +-- File: migrations/2022-12-14-092540_i32_to_i64/up.sql +-- Your SQL goes here +ALTER TABLE mandate + ALTER COLUMN mandate_amount TYPE bigint, + ALTER COLUMN amount_captured TYPE bigint; + +ALTER TABLE payment_attempt + ALTER COLUMN amount TYPE bigint, + ALTER COLUMN offer_amount TYPE bigint, + ALTER COLUMN surcharge_amount TYPE bigint, + ALTER COLUMN tax_amount TYPE bigint, + ALTER COLUMN amount_to_capture TYPE bigint; + +ALTER TABLE payment_intent + ALTER COLUMN amount TYPE bigint, + ALTER COLUMN amount_captured TYPE bigint; + +ALTER TABLE refund + ALTER COLUMN total_amount TYPE bigint, + ALTER COLUMN refund_amount TYPE bigint; + + + +-- File: migrations/2022-12-14-162701_update_payment_method/up.sql +-- Your SQL goes here +ALTER TABLE payment_methods +ADD COLUMN metadata JSON; + + +-- File: migrations/2022-12-19-085322_rename_txn_id_to_attempt_id/up.sql +ALTER TABLE payment_attempt +RENAME COLUMN txn_id to attempt_id; + + + +-- File: migrations/2022-12-19-085739_add_attempt_id_to_refund/up.sql +ALTER TABLE refund ADD COLUMN attempt_id VARCHAR(64) NOT NULL; + + + +-- File: migrations/2022-12-20-065945_reduce_size_of_varchar_columns/up.sql +ALTER TABLE address + ALTER COLUMN address_id TYPE VARCHAR(64), + ALTER COLUMN city TYPE VARCHAR(128), + ALTER COLUMN country TYPE VARCHAR(64), + ALTER COLUMN state TYPE VARCHAR(128), + ALTER COLUMN zip TYPE VARCHAR(16), + ALTER COLUMN phone_number TYPE VARCHAR(32), + ALTER COLUMN country_code TYPE VARCHAR(8), + ALTER COLUMN customer_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64); + +ALTER TABLE connector_response RENAME COLUMN txn_id TO attempt_id; + +ALTER TABLE connector_response + ALTER COLUMN payment_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN attempt_id TYPE VARCHAR(64), + ALTER COLUMN connector_name TYPE VARCHAR(64), + ALTER COLUMN connector_transaction_id TYPE VARCHAR(128); + +ALTER TABLE customers + ALTER COLUMN customer_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN phone TYPE VARCHAR(32), + ALTER COLUMN phone_country_code TYPE VARCHAR(8); + +ALTER TABLE events + ALTER COLUMN event_id TYPE VARCHAR(64), + ALTER COLUMN intent_reference_id TYPE VARCHAR(64), + ALTER COLUMN primary_object_id TYPE VARCHAR(64); + +ALTER TABLE mandate RENAME COLUMN previous_transaction_id to previous_attempt_id; + +ALTER TABLE mandate + ALTER COLUMN mandate_id TYPE VARCHAR(64), + ALTER COLUMN customer_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN payment_method_id TYPE VARCHAR(64), + ALTER COLUMN customer_ip_address TYPE VARCHAR(64), + ALTER COLUMN network_transaction_id TYPE VARCHAR(128), + ALTER COLUMN previous_attempt_id TYPE VARCHAR(64), + ALTER COLUMN connector TYPE VARCHAR(64), + ALTER COLUMN connector_mandate_id TYPE VARCHAR(128); + +ALTER TABLE merchant_account + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN api_key TYPE VARCHAR(128), + ALTER COLUMN merchant_name TYPE VARCHAR(128), + ALTER COLUMN parent_merchant_id TYPE VARCHAR(64), + ALTER COLUMN publishable_key TYPE VARCHAR(128); + +ALTER TABLE merchant_connector_account + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN connector_name TYPE VARCHAR(64); + +ALTER TABLE payment_attempt + ALTER COLUMN payment_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN attempt_id TYPE VARCHAR(64), + ALTER COLUMN connector TYPE VARCHAR(64), + ALTER COLUMN payment_method_id TYPE VARCHAR(64), + ALTER COLUMN connector_transaction_id TYPE VARCHAR(128), + ALTER COLUMN mandate_id TYPE VARCHAR(64), + ALTER COLUMN payment_token TYPE VARCHAR(128); + +ALTER TABLE payment_intent + ALTER COLUMN payment_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN customer_id TYPE VARCHAR(64), + ALTER COLUMN connector_id TYPE VARCHAR(64), + ALTER COLUMN shipping_address_id TYPE VARCHAR(64), + ALTER COLUMN billing_address_id TYPE VARCHAR(64), + ALTER COLUMN client_secret TYPE VARCHAR(128); + +ALTER TABLE payment_methods DROP COLUMN network_transaction_id; + +ALTER TABLE payment_methods + ALTER COLUMN customer_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN payment_method_id TYPE VARCHAR(64), + ALTER COLUMN scheme TYPE VARCHAR(32), + ALTER COLUMN token TYPE VARCHAR(128), + ALTER COLUMN issuer_name TYPE VARCHAR(64), + ALTER COLUMN issuer_country TYPE VARCHAR(64), + ALTER COLUMN swift_code TYPE VARCHAR(32), + ALTER COLUMN direct_debit_token TYPE VARCHAR(128), + ALTER COLUMN payment_method_issuer TYPE VARCHAR(128); + +ALTER TABLE process_tracker + ALTER COLUMN name TYPE VARCHAR(64), + ALTER COLUMN runner TYPE VARCHAR(64); + +ALTER TABLE refund RENAME COLUMN transaction_id to connector_transaction_id; +ALTER TABLE refund RENAME COLUMN pg_refund_id to connector_refund_id; + +ALTER TABLE refund + ALTER COLUMN internal_reference_id TYPE VARCHAR(64), + ALTER COLUMN refund_id TYPE VARCHAR(64), + ALTER COLUMN payment_id TYPE VARCHAR(64), + ALTER COLUMN merchant_id TYPE VARCHAR(64), + ALTER COLUMN connector_transaction_id TYPE VARCHAR(128), + ALTER COLUMN connector TYPE VARCHAR(64), + ALTER COLUMN connector_refund_id TYPE VARCHAR(128), + ALTER COLUMN external_reference_id TYPE VARCHAR(64), + ALTER COLUMN refund_arn TYPE VARCHAR(128); + +ALTER TABLE reverse_lookup + ALTER COLUMN lookup_id TYPE VARCHAR(128), + ALTER COLUMN sk_id TYPE VARCHAR(128), + ALTER COLUMN pk_id TYPE VARCHAR(128), + ALTER COLUMN source TYPE VARCHAR(128); + + + +-- File: migrations/2022-12-21-071825_add_refund_reason/up.sql +ALTER TABLE REFUND ADD COLUMN refund_reason VARCHAR(255) DEFAULT NULL; + + + +-- File: migrations/2022-12-21-124904_remove_metadata_default_as_null/up.sql +ALTER TABLE payment_intent ALTER COLUMN metadata DROP DEFAULT; + + +-- File: migrations/2022-12-22-091431_attempt_status_rename/up.sql +ALTER TYPE "AttemptStatus" RENAME VALUE 'juspay_declined' TO 'router_declined'; +ALTER TYPE "AttemptStatus" RENAME VALUE 'pending_vbv' TO 'authentication_successful'; +ALTER TYPE "AttemptStatus" RENAME VALUE 'vbv_successful' TO 'authentication_pending'; + + + +-- File: migrations/2022-12-27-172643_update_locker_mock_up/up.sql +-- Your SQL goes here +ALTER TABLE locker_mock_up +ADD COLUMN payment_method_id VARCHAR(64); + + +-- File: migrations/2023-01-03-122401_update_merchant_account/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account +ADD COLUMN locker_id VARCHAR(64); + + +-- File: migrations/2023-01-10-035412_connector-metadata-payment-attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN connector_metadata JSONB DEFAULT NULL; + + +-- File: migrations/2023-01-11-134448_add_metadata_to_merchant_account/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account ADD COLUMN metadata JSONB DEFAULT NULL; + + +-- File: migrations/2023-01-12-084710_update_merchant_routing_algorithm/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account DROP COLUMN routing_algorithm; +ALTER TABLE merchant_account DROP COLUMN custom_routing_rules; +ALTER TABLE merchant_account ADD COLUMN routing_algorithm JSON; +DROP TYPE "RoutingAlgorithm"; + + + +-- File: migrations/2023-01-12-140107_drop_temp_card/up.sql +DROP TABLE temp_card; + + + +-- File: migrations/2023-01-19-122511_add_refund_error_code/up.sql +ALTER TABLE refund +ADD IF NOT EXISTS refund_error_code TEXT DEFAULT NULL; + + + +-- File: migrations/2023-01-20-113235_add_attempt_id_to_payment_intent/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN active_attempt_id VARCHAR(64) NOT NULL DEFAULT 'xxx'; + +UPDATE payment_intent SET active_attempt_id = payment_attempt.attempt_id from payment_attempt where payment_intent.active_attempt_id = payment_attempt.payment_id; + +CREATE UNIQUE INDEX payment_attempt_payment_id_merchant_id_attempt_id_index ON payment_attempt (payment_id, merchant_id, attempt_id); + +-- Because payment_attempt table can have rows with same payment_id and merchant_id, this index is dropped. +DROP index payment_attempt_payment_id_merchant_id_index; + +CREATE INDEX payment_attempt_payment_id_merchant_id_index ON payment_attempt (payment_id, merchant_id); + + + +-- File: migrations/2023-02-01-135102_create_api_keys_table/up.sql +CREATE TABLE api_keys ( + key_id VARCHAR(64) NOT NULL PRIMARY KEY, + merchant_id VARCHAR(64) NOT NULL, + NAME VARCHAR(64) NOT NULL, + description VARCHAR(256) DEFAULT NULL, + hash_key VARCHAR(64) NOT NULL, + hashed_api_key VARCHAR(128) NOT NULL, + prefix VARCHAR(16) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + expires_at TIMESTAMP DEFAULT NULL, + last_used TIMESTAMP DEFAULT NULL +); + + + +-- File: migrations/2023-02-02-055700_add_payment_issuer_and_experience_in_payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS payment_issuer VARCHAR(50); + +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS payment_experience VARCHAR(50); + + + +-- File: migrations/2023-02-02-062215_remove_redirect_and_payment_flow_from_payment_attempt/up.sql +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS redirect; + +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS payment_flow; + +DROP TYPE IF EXISTS "PaymentFlow"; + + + +-- File: migrations/2023-02-07-070512_change_merchant_connector_id_data_type/up.sql +ALTER TABLE merchant_connector_account +ALTER COLUMN merchant_connector_id TYPE VARCHAR(128) USING merchant_connector_id::varchar; + + +ALTER TABLE merchant_connector_account +ALTER COLUMN merchant_connector_id DROP DEFAULT; + + + +-- File: migrations/2023-02-09-093400_add_bank_redirect/up.sql +-- Your SQL goes here +ALTER TYPE "PaymentMethodType" ADD VALUE 'bank_redirect' after 'paypal'; + + + +-- File: migrations/2023-02-10-083146_make_payment_method_type_as_text/up.sql +-- Your SQL goes here +ALTER TABLE payment_methods +ALTER COLUMN payment_method_type TYPE VARCHAR(64); + +ALTER TABLE payment_attempt +ADD COLUMN payment_method_type VARCHAR(64); + +DROP TYPE IF EXISTS "PaymentMethodSubType"; + + + +-- File: migrations/2023-02-20-101809_update_merchant_connector_account/up.sql +ALTER TABLE merchant_connector_account +ADD COLUMN connector_label VARCHAR(255), + ADD COLUMN business_country VARCHAR(2) DEFAULT 'US' NOT NULL, + ADD COLUMN business_label VARCHAR(255) DEFAULT 'default' NOT NULL; + +-- To backfill, use `US` as default country and `default` as the business_label +UPDATE merchant_connector_account AS m +SET connector_label = concat( + m.connector_name, + '_', + 'US', + '_', + 'default' + ); + +ALTER TABLE merchant_connector_account +ALTER COLUMN connector_label +SET NOT NULL, + ALTER COLUMN business_country DROP DEFAULT, + ALTER COLUMN business_label DROP DEFAULT; + +DROP INDEX merchant_connector_account_merchant_id_connector_name_index; + +CREATE UNIQUE INDEX merchant_connector_account_merchant_id_connector_label_index ON merchant_connector_account (merchant_id, connector_label); + + + +-- File: migrations/2023-02-21-065628_update_merchant_account/up.sql +ALTER TABLE merchant_account +ADD COLUMN IF NOT EXISTS primary_business_details JSON NOT NULL DEFAULT '{"country": ["US"], "business": ["default"]}'; + + + +-- File: migrations/2023-02-21-094019_api_keys_remove_hash_key/up.sql +ALTER TABLE api_keys DROP COLUMN hash_key; + +/* + Once we've dropped the `hash_key` column, we cannot use the existing API keys + from the `api_keys` table anymore, as the `hash_key` is a random string that + we no longer have. + */ +TRUNCATE TABLE api_keys; + +ALTER TABLE api_keys +ADD CONSTRAINT api_keys_hashed_api_key_key UNIQUE (hashed_api_key); + + + +-- File: migrations/2023-02-22-100331_rename_pm_type_enum/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ALTER COLUMN payment_method TYPE VARCHAR; + +ALTER TABLE payment_methods +ALTER COLUMN payment_method TYPE VARCHAR; + +ALTER TABLE payment_methods +ALTER COLUMN payment_method_type TYPE VARCHAR; + +ALTER TABLE payment_attempt DROP COLUMN payment_issuer; + +ALTER TABLE payment_attempt +ADD COLUMN payment_method_data JSONB; + +DROP TYPE "PaymentMethodType"; + + + +-- File: migrations/2023-02-28-072631_ang-currency/up.sql +-- Your SQL goes here +ALTER TYPE "Currency" ADD VALUE 'ANG' after 'AMD'; + + + +-- File: migrations/2023-02-28-112730_add_refund_webhook_types/up.sql +-- Your SQL goes here +ALTER TYPE "EventClass" ADD VALUE 'refunds'; + +ALTER TYPE "EventObjectType" ADD VALUE 'refund_details'; + +ALTER TYPE "EventType" ADD VALUE 'refund_succeeded'; + +ALTER TYPE "EventType" ADD VALUE 'refund_failed'; + + +-- File: migrations/2023-03-04-114058_remove_api_key_column_merchant_account_table/up.sql +ALTER TABLE merchant_account DROP COLUMN api_key; + + + +-- File: migrations/2023-03-07-141638_make_payment_attempt_connector_json/up.sql +-- Alter column type to json +-- as well as the connector. +ALTER TABLE payment_attempt +ALTER COLUMN connector TYPE JSONB +USING jsonb_build_object( + 'routed_through', connector, + 'algorithm', NULL +); + + + +-- File: migrations/2023-03-14-123541_add_cards_info_table/up.sql +-- Your SQL goes here +CREATE TABLE cards_info ( + card_iin VARCHAR(16) PRIMARY KEY, + card_issuer TEXT, + card_network TEXT, + card_type TEXT, + card_subtype TEXT, + card_issuing_country TEXT, + bank_code_id VARCHAR(32), + bank_code VARCHAR(32), + country_code VARCHAR(32), + date_created TIMESTAMP NOT NULL, + last_updated TIMESTAMP, + last_updated_provider TEXT +); + + + +-- File: migrations/2023-03-15-082312_add_connector_txn_id_merchant_id_index_in_payment_attempt/up.sql +-- Your SQL goes here +CREATE INDEX payment_attempt_connector_transaction_id_merchant_id_index ON payment_attempt (connector_transaction_id, merchant_id); + + + +-- File: migrations/2023-03-15-185959_add_dispute_table/up.sql +CREATE TYPE "DisputeStage" AS ENUM ('pre_dispute', 'dispute', 'pre_arbitration'); + +CREATE TYPE "DisputeStatus" AS ENUM ('dispute_opened', 'dispute_expired', 'dispute_accepted', 'dispute_cancelled', 'dispute_challenged', 'dispute_won', 'dispute_lost'); + +CREATE TABLE dispute ( + id SERIAL PRIMARY KEY, + dispute_id VARCHAR(64) NOT NULL, + amount VARCHAR(255) NOT NULL, + currency VARCHAR(255) NOT NULL, + dispute_stage "DisputeStage" NOT NULL, + dispute_status "DisputeStatus" NOT NULL, + payment_id VARCHAR(255) NOT NULL, + attempt_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + connector_status VARCHAR(255) NOT NULL, + connector_dispute_id VARCHAR(255) NOT NULL, + connector_reason VARCHAR(255), + connector_reason_code VARCHAR(255), + challenge_required_by VARCHAR(255), + dispute_created_at VARCHAR(255), + updated_at VARCHAR(255), + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP +); + +CREATE UNIQUE INDEX merchant_id_dispute_id_index ON dispute (merchant_id, dispute_id); + +CREATE UNIQUE INDEX merchant_id_payment_id_connector_dispute_id_index ON dispute (merchant_id, payment_id, connector_dispute_id); + +CREATE INDEX dispute_status_index ON dispute (dispute_status); + +CREATE INDEX dispute_stage_index ON dispute (dispute_stage); + +ALTER TYPE "EventClass" ADD VALUE 'disputes'; + +ALTER TYPE "EventObjectType" ADD VALUE 'dispute_details'; + +ALTER TYPE "EventType" ADD VALUE 'dispute_opened'; +ALTER TYPE "EventType" ADD VALUE 'dispute_expired'; +ALTER TYPE "EventType" ADD VALUE 'dispute_accepted'; +ALTER TYPE "EventType" ADD VALUE 'dispute_cancelled'; +ALTER TYPE "EventType" ADD VALUE 'dispute_challenged'; +ALTER TYPE "EventType" ADD VALUE 'dispute_won'; +ALTER TYPE "EventType" ADD VALUE 'dispute_lost'; + + + +-- File: migrations/2023-03-16-105114_add_data_collection_status/up.sql +ALTER TYPE "AttemptStatus" ADD VALUE IF NOT EXISTS 'device_data_collection_pending'; + + +-- File: migrations/2023-03-23-095309_add_business_sub_label_to_mca/up.sql +ALTER TABLE merchant_connector_account +ADD COLUMN IF NOT EXISTS business_sub_label VARCHAR(64) DEFAULT 'default'; + + + +-- File: migrations/2023-03-23-123920_add_business_label_and_country_to_pi/up.sql +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS business_country VARCHAR(2) NOT NULL DEFAULT 'US', + ADD COLUMN IF NOT EXISTS business_label VARCHAR(64) NOT NULL DEFAULT 'default'; + + + +-- File: migrations/2023-03-26-163105_add_unresolved_status/up.sql +ALTER TYPE "AttemptStatus" ADD VALUE IF NOT EXISTS 'unresolved'; +ALTER TYPE "IntentStatus" ADD VALUE IF NOT EXISTS 'requires_merchant_action' after 'requires_customer_action'; +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'action_required'; +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_processing'; + + + +-- File: migrations/2023-03-27-091611_change_country_to_enum/up.sql +CREATE TYPE "CountryCode" AS ENUM ( + 'AF', + 'AX', + 'AL', + 'DZ', + 'AS', + 'AD', + 'AO', + 'AI', + 'AQ', + 'AG', + 'AR', + 'AM', + 'AW', + 'AU', + 'AT', + 'AZ', + 'BS', + 'BH', + 'BD', + 'BB', + 'BY', + 'BE', + 'BZ', + 'BJ', + 'BM', + 'BT', + 'BO', + 'BQ', + 'BA', + 'BW', + 'BV', + 'BR', + 'IO', + 'BN', + 'BG', + 'BF', + 'BI', + 'KH', + 'CM', + 'CA', + 'CV', + 'KY', + 'CF', + 'TD', + 'CL', + 'CN', + 'CX', + 'CC', + 'CO', + 'KM', + 'CG', + 'CD', + 'CK', + 'CR', + 'CI', + 'HR', + 'CU', + 'CW', + 'CY', + 'CZ', + 'DK', + 'DJ', + 'DM', + 'DO', + 'EC', + 'EG', + 'SV', + 'GQ', + 'ER', + 'EE', + 'ET', + 'FK', + 'FO', + 'FJ', + 'FI', + 'FR', + 'GF', + 'PF', + 'TF', + 'GA', + 'GM', + 'GE', + 'DE', + 'GH', + 'GI', + 'GR', + 'GL', + 'GD', + 'GP', + 'GU', + 'GT', + 'GG', + 'GN', + 'GW', + 'GY', + 'HT', + 'HM', + 'VA', + 'HN', + 'HK', + 'HU', + 'IS', + 'IN', + 'ID', + 'IR', + 'IQ', + 'IE', + 'IM', + 'IL', + 'IT', + 'JM', + 'JP', + 'JE', + 'JO', + 'KZ', + 'KE', + 'KI', + 'KP', + 'KR', + 'KW', + 'KG', + 'LA', + 'LV', + 'LB', + 'LS', + 'LR', + 'LY', + 'LI', + 'LT', + 'LU', + 'MO', + 'MK', + 'MG', + 'MW', + 'MY', + 'MV', + 'ML', + 'MT', + 'MH', + 'MQ', + 'MR', + 'MU', + 'YT', + 'MX', + 'FM', + 'MD', + 'MC', + 'MN', + 'ME', + 'MS', + 'MA', + 'MZ', + 'MM', + 'NA', + 'NR', + 'NP', + 'NL', + 'NC', + 'NZ', + 'NI', + 'NE', + 'NG', + 'NU', + 'NF', + 'MP', + 'NO', + 'OM', + 'PK', + 'PW', + 'PS', + 'PA', + 'PG', + 'PY', + 'PE', + 'PH', + 'PN', + 'PL', + 'PT', + 'PR', + 'QA', + 'RE', + 'RO', + 'RU', + 'RW', + 'BL', + 'SH', + 'KN', + 'LC', + 'MF', + 'PM', + 'VC', + 'WS', + 'SM', + 'ST', + 'SA', + 'SN', + 'RS', + 'SC', + 'SL', + 'SG', + 'SX', + 'SK', + 'SI', + 'SB', + 'SO', + 'ZA', + 'GS', + 'SS', + 'ES', + 'LK', + 'SD', + 'SR', + 'SJ', + 'SZ', + 'SE', + 'CH', + 'SY', + 'TW', + 'TJ', + 'TZ', + 'TH', + 'TL', + 'TG', + 'TK', + 'TO', + 'TT', + 'TN', + 'TR', + 'TM', + 'TC', + 'TV', + 'UG', + 'UA', + 'AE', + 'GB', + 'US', + 'UM', + 'UY', + 'UZ', + 'VU', + 'VE', + 'VN', + 'VG', + 'VI', + 'WF', + 'EH', + 'YE', + 'ZM', + 'ZW' +); + +ALTER TABLE address +ALTER COLUMN country TYPE "CountryCode" USING country::"CountryCode"; + + + +-- File: migrations/2023-03-30-132338_add_start_end_date_for_mandates/up.sql +ALTER TABLE mandate +ADD IF NOT EXISTS start_date TIMESTAMP NULL, +ADD IF NOT EXISTS end_date TIMESTAMP NULL, +ADD COLUMN metadata JSONB DEFAULT NULL; + + +-- File: migrations/2023-04-03-082335_update_mca_frm_configs/up.sql +ALTER TABLE "merchant_connector_account" ADD COLUMN frm_configs jsonb; + + +-- File: migrations/2023-04-04-061926_add_dispute_api_schema/up.sql +-- Your SQL goes here +CREATE TABLE file_metadata ( + file_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(255) NOT NULL, + file_name VARCHAR(255), + file_size INTEGER NOT NULL, + file_type VARCHAR(255) NOT NULL, + provider_file_id VARCHAR(255), + file_upload_provider VARCHAR(255), + available BOOLEAN NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + PRIMARY KEY (file_id, merchant_id) +); + + + +-- File: migrations/2023-04-05-051523_add_business_sub_label_to_payment_attempt/up.sql +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS business_sub_label VARCHAR(64); + + + +-- File: migrations/2023-04-05-121040_alter_mca_change_country_to_enum/up.sql +ALTER TABLE merchant_connector_account +ALTER COLUMN business_country TYPE "CountryCode" USING business_country::"CountryCode"; + + + +-- File: migrations/2023-04-05-121047_alter_pi_change_country_to_enum/up.sql +ALTER TABLE payment_intent +ALTER COLUMN business_country DROP DEFAULT, + ALTER COLUMN business_country TYPE "CountryCode" USING business_country::"CountryCode"; + + + +-- File: migrations/2023-04-06-063047_add_connector_col_in_dispute/up.sql +-- Your SQL goes here +ALTER TABLE dispute +ADD COLUMN connector VARCHAR(255) NOT NULL; + + +-- File: migrations/2023-04-06-092008_create_merchant_ek/up.sql +CREATE TABLE merchant_key_store( + merchant_id VARCHAR(255) NOT NULL PRIMARY KEY, + key bytea NOT NULL, + created_at TIMESTAMP NOT NULL +); + +-- File: migrations/2023-04-11-084958_pii-migration/up.sql +-- Your SQL goes here +ALTER TABLE merchant_connector_account + ALTER COLUMN connector_account_details TYPE bytea + USING convert_to(connector_account_details::text, 'UTF8'); + +ALTER TABLE merchant_account + ALTER COLUMN merchant_name TYPE bytea USING convert_to(merchant_name, 'UTF8'), + ALTER merchant_details TYPE bytea USING convert_to(merchant_details::text, 'UTF8'); + +ALTER TABLE address + ALTER COLUMN line1 TYPE bytea USING convert_to(line1, 'UTF8'), + ALTER COLUMN line2 TYPE bytea USING convert_to(line2, 'UTF8'), + ALTER COLUMN line3 TYPE bytea USING convert_to(line3, 'UTF8'), + ALTER COLUMN state TYPE bytea USING convert_to(state, 'UTF8'), + ALTER COLUMN zip TYPE bytea USING convert_to(zip, 'UTF8'), + ALTER COLUMN first_name TYPE bytea USING convert_to(first_name, 'UTF8'), + ALTER COLUMN last_name TYPE bytea USING convert_to(last_name, 'UTF8'), + ALTER COLUMN phone_number TYPE bytea USING convert_to(phone_number, 'UTF8'); + +ALTER TABLE customers + ALTER COLUMN name TYPE bytea USING convert_to(name, 'UTF8'), + ALTER COLUMN email TYPE bytea USING convert_to(email, 'UTF8'), + ALTER COLUMN phone TYPE bytea USING convert_to(phone, 'UTF8'); + + + +-- File: migrations/2023-04-12-075449_separate_payment_attempt_algorithm_col/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN straight_through_algorithm JSONB; + +UPDATE payment_attempt SET straight_through_algorithm = connector->'algorithm' +WHERE connector->>'algorithm' IS NOT NULL; + +ALTER TABLE payment_attempt +ALTER COLUMN connector TYPE VARCHAR(64) +USING connector->>'routed_through'; + + + +-- File: migrations/2023-04-13-094917_change_primary_business_type/up.sql +-- This change will allow older merchant accounts to be used with new changes +UPDATE merchant_account +SET primary_business_details = '[{"country": "US", "business": "default"}]'; + +-- Since this field is optional, default is not required +ALTER TABLE merchant_connector_account +ALTER COLUMN business_sub_label DROP DEFAULT; + + + +-- File: migrations/2023-04-19-072152_merchant_account_add_intent_fulfilment_time/up.sql +ALTER TABLE merchant_account ADD COLUMN IF NOT EXISTS intent_fulfillment_time BIGINT; + + + +-- File: migrations/2023-04-19-120503_update_customer_connector_customer/up.sql +-- Your SQL goes here +ALTER TABLE customers +ADD COLUMN connector_customer JSONB; + + +-- File: migrations/2023-04-19-120735_add_time_for_tables/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account +ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT now(), +ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT now(); + +ALTER TABLE merchant_connector_account +ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT now(), +ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT now(); + + +ALTER TABLE customers +ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT now(); + + + +-- File: migrations/2023-04-20-073704_allow_multiple_mandate_ids/up.sql +ALTER TABLE mandate + ADD COLUMN connector_mandate_ids jsonb; +UPDATE mandate SET connector_mandate_ids = jsonb_build_object( + 'mandate_id', connector_mandate_id, + 'payment_method_id', NULL + ); + + +-- File: migrations/2023-04-20-162755_add_preprocessing_step_id_payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN preprocessing_step_id VARCHAR DEFAULT NULL; +CREATE INDEX preprocessing_step_id_index ON payment_attempt (preprocessing_step_id); + + + +-- File: migrations/2023-04-21-100150_create_index_for_api_keys/up.sql +CREATE UNIQUE INDEX api_keys_merchant_id_key_id_index ON api_keys (merchant_id, key_id); + + +-- File: migrations/2023-04-25-061159_rename_country_code_to_country_alpha2/up.sql +-- Your SQL goes here +ALTER TYPE "CountryCode" RENAME TO "CountryAlpha2"; + + +-- File: migrations/2023-04-25-091017_merchant_account_add_frm_routing_algorithm.sql/up.sql + +ALTER TABLE merchant_account +ADD COLUMN frm_routing_algorithm JSONB NULL; + + +-- File: migrations/2023-04-25-141011_add_connector_label_col_in_file_metadata/up.sql +-- Your SQL goes here +ALTER TABLE file_metadata +ADD COLUMN connector_label VARCHAR(255); + + +-- File: migrations/2023-04-26-062424_alter_dispute_table/up.sql +ALTER TABLE dispute +ALTER COLUMN challenge_required_by TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP, +ALTER COLUMN dispute_created_at TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP, +ALTER COLUMN updated_at TYPE TIMESTAMP USING dispute_created_at::TIMESTAMP; + + +-- File: migrations/2023-04-26-090005_remove_default_created_at_modified_at/up.sql +-- Merchant Account +ALTER TABLE merchant_account +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE merchant_account +ALTER COLUMN created_at DROP DEFAULT; + + +-- Merchant Connector Account +ALTER TABLE merchant_connector_account +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE merchant_connector_account +ALTER COLUMN created_at DROP DEFAULT; + +-- Customers +ALTER TABLE customers +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE customers +ALTER COLUMN created_at DROP DEFAULT; + +-- Address +ALTER TABLE address +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE address +ALTER COLUMN created_at DROP DEFAULT; + +-- Refunds +ALTER TABLE refund +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE refund +ALTER COLUMN created_at DROP DEFAULT; + +-- Connector Response +ALTER TABLE connector_response +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE connector_response +ALTER COLUMN created_at DROP DEFAULT; + +-- Payment methods +ALTER TABLE payment_methods +ALTER COLUMN created_at DROP DEFAULT; + +-- Payment Intent +ALTER TABLE payment_intent +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE payment_intent +ALTER COLUMN created_at DROP DEFAULT; + +--- Payment Attempt +ALTER TABLE payment_attempt +ALTER COLUMN modified_at DROP DEFAULT; + +ALTER TABLE payment_attempt +ALTER COLUMN created_at DROP DEFAULT; + + + +-- File: migrations/2023-04-27-120010_add_payment_failed_event_type/up.sql +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_failed'; + + +-- File: migrations/2023-05-02-102332_payout_create/up.sql +CREATE type "PayoutStatus" AS ENUM ( + 'success', + 'failed', + 'cancelled', + 'pending', + 'ineligible', + 'requires_creation', + 'requires_payout_method_data', + 'requires_fulfillment' +); + +CREATE type "PayoutType" AS ENUM ('card', 'bank'); + +CREATE TABLE + PAYOUT_ATTEMPT ( + payout_attempt_id VARCHAR (64) NOT NULL PRIMARY KEY, + payout_id VARCHAR (64) NOT NULL, + customer_id VARCHAR (64) NOT NULL, + merchant_id VARCHAR (64) NOT NULL, + address_id VARCHAR (64) NOT NULL, + connector VARCHAR (64) NOT NULL, + connector_payout_id VARCHAR (128) NOT NULL, + payout_token VARCHAR (64), + status "PayoutStatus" NOT NULL, + is_eligible BOOLEAN, + error_message TEXT, + error_code VARCHAR (64), + business_country "CountryAlpha2", + business_label VARCHAR(64), + created_at timestamp NOT NULL DEFAULT NOW():: timestamp, + last_modified_at timestamp NOT NULL DEFAULT NOW():: timestamp + ); + +CREATE TABLE + PAYOUTS ( + payout_id VARCHAR (64) NOT NULL PRIMARY KEY, + merchant_id VARCHAR (64) NOT NULL, + customer_id VARCHAR (64) NOT NULL, + address_id VARCHAR (64) NOT NULL, + payout_type "PayoutType" NOT NULL, + payout_method_id VARCHAR (64), + amount BIGINT NOT NULL, + destination_currency "Currency" NOT NULL, + source_currency "Currency" NOT NULL, + description VARCHAR (255), + recurring BOOLEAN NOT NULL, + auto_fulfill BOOLEAN NOT NULL, + return_url VARCHAR (255), + entity_type VARCHAR (64) NOT NULL, + metadata JSONB DEFAULT '{}':: JSONB, + created_at timestamp NOT NULL DEFAULT NOW():: timestamp, + last_modified_at timestamp NOT NULL DEFAULT NOW():: timestamp + ); + +CREATE UNIQUE INDEX payout_attempt_index ON PAYOUT_ATTEMPT ( + merchant_id, + payout_attempt_id, + payout_id +); + +CREATE UNIQUE INDEX payouts_index ON PAYOUTS (merchant_id, payout_id); + +-- Alterations + +ALTER TABLE merchant_account +ADD + COLUMN payout_routing_algorithm JSONB; + +ALTER TABLE locker_mock_up ADD COLUMN enc_card_data TEXT; + +ALTER TYPE "ConnectorType" ADD VALUE 'payout_processor'; + + +-- File: migrations/2023-05-03-121025_nest_straight_through_col_in_payment_attempt/up.sql +-- Your SQL goes here +UPDATE payment_attempt +SET straight_through_algorithm = jsonb_build_object('algorithm', straight_through_algorithm); + + + +-- File: migrations/2023-05-05-112013_add_evidence_col_in_dispute/up.sql +-- Your SQL goes here +ALTER TABLE dispute +ADD COLUMN evidence JSONB NOT NULL DEFAULT '{}'::JSONB; + + +-- File: migrations/2023-05-08-141907_rename_dispute_cols/up.sql +-- Your SQL goes here +ALTER TABLE dispute +RENAME COLUMN dispute_created_at TO connector_created_at; + +ALTER TABLE dispute +RENAME COLUMN updated_at TO connector_updated_at; + + + +-- File: migrations/2023-05-16-145008_mandate_data_in_pa/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN mandate_details JSONB; + + + +-- File: migrations/2023-05-29-094747_order-details-as-a-separate-column.sql/up.sql +ALTER TABLE payment_intent ADD COLUMN order_details jsonb[]; + + +-- File: migrations/2023-05-31-152153_add_connector_webhook_details_to_mca/up.sql +-- Your SQL goes here +ALTER TABLE merchant_connector_account +ADD COLUMN IF NOT EXISTS connector_webhook_details JSONB DEFAULT NULL; + + +-- File: migrations/2023-06-14-105035_add_reason_in_payment_attempt/up.sql +ALTER TABLE payment_attempt +ADD COLUMN error_reason TEXT; + + + +-- File: migrations/2023-06-16-073615_add_ron_currency_to_db/up.sql +-- Your SQL goes here +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'RON' AFTER 'QAR'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'TRY' AFTER 'TTD'; + + + +-- File: migrations/2023-06-18-042123_add_udf_column_in_payments/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN udf JSONB; + + + +-- File: migrations/2023-06-19-071300_merchant_key_store_shrink_merchant_id/up.sql +ALTER TABLE merchant_key_store +ALTER COLUMN merchant_id TYPE VARCHAR(64); + + + +-- File: migrations/2023-06-22-161131_change-type-of-frm-configs.sql/up.sql +UPDATE merchant_connector_account set frm_configs = null ; + +ALTER TABLE merchant_connector_account +ALTER COLUMN frm_configs TYPE jsonb[] +USING ARRAY[frm_configs]::jsonb[]; + +UPDATE merchant_connector_account set frm_configs = null ; + + + +-- File: migrations/2023-06-26-124254_add_vnd_to_currency_enum/up.sql +-- Your SQL goes here +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'VND' AFTER 'UZS'; + + +-- File: migrations/2023-06-29-094858_payment-intent-remove-udf-field/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent DROP COLUMN udf; + + + +-- File: migrations/2023-07-01-184850_payment-intent-add-metadata-fields/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent +ADD COLUMN allowed_payment_method_types JSON, +ADD COLUMN connector_metadata JSON, +ADD COLUMN feature_metadata JSON; + + + +-- File: migrations/2023-07-03-093552_add_attempt_count_in_payment_intent/up.sql +ALTER TABLE payment_intent ADD COLUMN attempt_count SMALLINT NOT NULL DEFAULT 1; + +UPDATE payment_intent +SET attempt_count = payment_id_count.count +FROM (SELECT payment_id, count(payment_id) FROM payment_attempt GROUP BY payment_id) as payment_id_count +WHERE payment_intent.payment_id = payment_id_count.payment_id; + + + +-- File: migrations/2023-07-04-131721_add_org_id_and_org_name/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account +ADD COLUMN IF NOT EXISTS organization_id VARCHAR(32); + + + +-- File: migrations/2023-07-07-091223_create_captures_table/up.sql + +CREATE TYPE "CaptureStatus" AS ENUM ( + 'started', + 'charged', + 'pending', + 'failed' +); +ALTER TYPE "IntentStatus" ADD VALUE If NOT EXISTS 'partially_captured' AFTER 'requires_capture'; +CREATE TABLE captures( + capture_id VARCHAR(64) NOT NULL PRIMARY KEY, + payment_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + status "CaptureStatus" NOT NULL, + amount BIGINT NOT NULL, + currency "Currency", + connector VARCHAR(255), + error_message VARCHAR(255), + error_code VARCHAR(255), + error_reason VARCHAR(255), + tax_amount BIGINT, + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + authorized_attempt_id VARCHAR(64) NOT NULL, + connector_transaction_id VARCHAR(128), + capture_sequence SMALLINT NOT NULL +); + +CREATE INDEX captures_merchant_id_payment_id_authorized_attempt_id_index ON captures ( + merchant_id, + payment_id, + authorized_attempt_id +); +CREATE INDEX captures_connector_transaction_id_index ON captures ( + connector_transaction_id +); + +ALTER TABLE payment_attempt +ADD COLUMN multiple_capture_count SMALLINT; --number of captures available for this payment attempt in captures table + + + +-- File: migrations/2023-07-08-134807_add_connector_response_reference_id_in_payment_intent/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS connector_response_reference_id VARCHAR(128); + + +-- File: migrations/2023-07-11-140347_add_is_recon_enabled_field_in_merchant_account/up.sql +-- Your SQL goes here +ALTER TABLE merchant_account ADD COLUMN "is_recon_enabled" BOOLEAN NOT NULL DEFAULT FALSE; + + +-- File: migrations/2023-07-17-111427_add-fraud-check-table.sql/up.sql +-- Your SQL goes here-- Your SQL goes here +CREATE TYPE "FraudCheckType" AS ENUM ( + 'pre_frm', + 'post_frm' +); + +CREATE TYPE "FraudCheckStatus" AS ENUM ( + 'fraud', + 'manual_review', + 'pending', + 'legit', + 'transaction_failure' +); + +CREATE TABLE fraud_check ( + frm_id VARCHAR(64) NOT NULL UNIQUE, + payment_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + attempt_id VARCHAR(64) NOT NULL UNIQUE, + created_at TIMESTAMP NOT NULL DEFAULT now(), + frm_name VARCHAR(255) NOT NULL, + frm_transaction_id VARCHAR(255) UNIQUE, + frm_transaction_type "FraudCheckType" NOT NULL, + frm_status "FraudCheckStatus" NOT NULL, + frm_score INTEGER, + frm_reason JSONB, + frm_error VARCHAR(255), + payment_details JSONB, + metadata JSONB, + modified_at TIMESTAMP NOT NULL DEFAULT now(), + + PRIMARY KEY (frm_id, attempt_id, payment_id, merchant_id) +); + +CREATE UNIQUE INDEX frm_id_index ON fraud_check (frm_id, attempt_id, payment_id, merchant_id); + + + +-- File: migrations/2023-07-19-081050_add_zero_decimal_currencies/up.sql +-- Your SQL goes here +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'BIF' AFTER 'BHD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'CLP' AFTER 'CHF'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'DJF' AFTER 'CZK'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'GNF' AFTER 'GMD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'KMF' AFTER 'KHR'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'MGA' AFTER 'MDL'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'PYG' AFTER 'PLN'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'RWF' AFTER 'RUB'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'UGX' AFTER 'TZS'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'VUV' AFTER 'VND'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'XAF' AFTER 'VUV'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'XOF' AFTER 'XAF'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'XPF' AFTER 'XOF'; + + + +-- File: migrations/2023-07-28-111829_update_columns_to_fix_db_diff/up.sql +ALTER TABLE dispute +ALTER COLUMN payment_id TYPE VARCHAR(64); + +ALTER TABLE payment_methods +ALTER COLUMN payment_method_type TYPE VARCHAR(64); + +ALTER TABLE merchant_account +ALTER COLUMN primary_business_details DROP DEFAULT; + + +-- File: migrations/2023-08-01-165717_make_event_id_unique_for_events_table/up.sql +-- Your SQL goes here +ALTER TABLE events +ADD CONSTRAINT event_id_unique UNIQUE (event_id); + + + +-- File: migrations/2023-08-08-144148_add_business_profile_table/up.sql +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS business_profile ( + profile_id VARCHAR(64) PRIMARY KEY, + merchant_id VARCHAR(64) NOT NULL, + profile_name VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + return_url TEXT, + enable_payment_response_hash BOOLEAN NOT NULL DEFAULT TRUE, + payment_response_hash_key VARCHAR(255) DEFAULT NULL, + redirect_to_merchant_with_http_post BOOLEAN NOT NULL DEFAULT FALSE, + webhook_details JSON, + metadata JSON, + routing_algorithm JSON, + intent_fulfillment_time BIGINT, + frm_routing_algorithm JSONB, + payout_routing_algorithm JSONB, + is_recon_enabled BOOLEAN NOT NULL DEFAULT FALSE +); + + + +-- File: migrations/2023-08-11-073905_add_frm_config_in_mca/up.sql +ALTER TABLE "merchant_connector_account" ADD COLUMN frm_config jsonb[]; +-- Do not run below migration in PROD as this only makes sandbox compatible to PROD version +ALTER TABLE merchant_connector_account +ALTER COLUMN frm_configs TYPE jsonb +USING frm_configs[1]::jsonb; + + +-- File: migrations/2023-08-16-080721_make_connector_field_mandatory_capture_table/up.sql +-- Your SQL goes here +ALTER TABLE captures ALTER COLUMN connector SET NOT NULL; +ALTER TABLE captures RENAME COLUMN connector_transaction_id TO connector_capture_id; +ALTER TABLE captures add COLUMN IF NOT EXISTS connector_response_reference_id VARCHAR(128); + + +-- File: migrations/2023-08-16-103806_add_last_executed_frm_step/up.sql +alter table fraud_check add column last_step VARCHAR(64) NOT NULL DEFAULT 'processing'; + + +-- File: migrations/2023-08-16-112847_add_profile_id_in_affected_tables/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + +ALTER TABLE merchant_connector_account +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + +ALTER TABLE merchant_account +ADD COLUMN IF NOT EXISTS default_profile VARCHAR(64); + +-- Profile id is needed in refunds for listing refunds by business profile +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + +-- For listing disputes by business profile +ALTER TABLE dispute +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + +-- For a similar use case as to payments +ALTER TABLE payout_attempt +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + + + +-- File: migrations/2023-08-23-090712_payment_attempt_perf_idx/up.sql +-- Your SQL goes here +CREATE INDEX payment_attempt_attempt_id_merchant_id_index ON payment_attempt (attempt_id, merchant_id); + + + + +-- File: migrations/2023-08-24-095037_add_profile_id_in_file_metadata/up.sql +-- Your SQL goes here +ALTER TABLE file_metadata +ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); + + + +-- File: migrations/2023-08-25-094551_add_recon_status_in_merchant_account/up.sql +-- Your SQL goes here +CREATE TYPE "ReconStatus" AS ENUM ('requested','active', 'disabled','not_requested'); +ALTER TABLE merchant_account ADD recon_status "ReconStatus" NOT NULL DEFAULT "ReconStatus"('not_requested'); + + +-- File: migrations/2023-08-28-131238_make_business_details_optional/up.sql +-- Your SQL goes here +ALTER TABLE payment_intent +ALTER COLUMN business_country DROP NOT NULL; + +ALTER TABLE payment_intent +ALTER COLUMN business_label DROP NOT NULL; + +ALTER TABLE merchant_connector_account +ALTER COLUMN business_country DROP NOT NULL; + +ALTER TABLE merchant_connector_account +ALTER COLUMN business_label DROP NOT NULL; + +ALTER TABLE merchant_connector_account +ALTER COLUMN connector_label DROP NOT NULL; + +DROP INDEX IF EXISTS merchant_connector_account_merchant_id_connector_label_index; + +CREATE UNIQUE INDEX IF NOT EXISTS merchant_connector_account_profile_id_connector_id_index ON merchant_connector_account(profile_id, connector_name); + +CREATE UNIQUE INDEX IF NOT EXISTS business_profile_merchant_id_profile_name_index ON business_profile(merchant_id, profile_name); + + + +-- File: migrations/2023-08-31-093852_add_merchant_decision/up.sql +alter table payment_intent add column merchant_decision VARCHAR(64); + + +-- File: migrations/2023-09-06-101704_payment_method_data_in_payment_methods/up.sql +-- Your SQL goes here +ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS payment_method_data BYTEA DEFAULT NULL; + + +-- File: migrations/2023-09-07-113914_add_amount_capturable_field_payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS amount_capturable BIGINT NOT NULL DEFAULT 0; + + +-- File: migrations/2023-09-08-112817_applepay_verified_domains_in_business_profile/up.sql +ALTER TABLE business_profile +ADD COLUMN IF NOT EXISTS applepay_verified_domains text[]; + + + + +-- File: migrations/2023-09-08-134514_add_payment_confirm_source_in_payment_intent/up.sql +-- Your SQL goes here +CREATE TYPE "PaymentSource" AS ENUM ( + 'merchant_server', + 'postman', + 'dashboard', + 'sdk' +); + +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS payment_confirm_source "PaymentSource"; + + +-- File: migrations/2023-09-13-075226_applepay_verified_domains_in_mca/up.sql +ALTER TABLE merchant_connector_account +ADD COLUMN IF NOT EXISTS applepay_verified_domains text[]; + + + +-- File: migrations/2023-09-14-032447_add_payment_id_in_address/up.sql +-- Your SQL goes here +ALTER TABLE address ADD COLUMN payment_id VARCHAR(64); +ALTER TABLE customers ADD COLUMN address_id VARCHAR(64); + + +-- File: migrations/2023-09-17-152010_make_id_not_null_address/up.sql +-- Your SQL goes here +ALTER TABLE address ALTER COLUMN id DROP NOT NULL; + + +-- File: migrations/2023-09-18-104900_add_pm_auth_config_mca/up.sql +-- Your SQL goes here +ALTER TABLE merchant_connector_account ADD COLUMN IF NOT EXISTS pm_auth_config JSONB DEFAULT NULL; +ALTER TYPE "ConnectorType" ADD VALUE 'payment_method_auth'; + + +-- File: migrations/2023-09-25-125007_add_surcharge_metadata_payment_attempt/up.sql +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS surcharge_metadata JSONB DEFAULT NULL; + + +-- File: migrations/2023-10-05-085859_make_org_id_mandatory_in_ma/up.sql +-- Your SQL goes here +UPDATE merchant_account +SET organization_id = 'org_abcdefghijklmn' +WHERE organization_id IS NULL; + +ALTER TABLE merchant_account +ALTER COLUMN organization_id +SET NOT NULL; + + + +-- File: migrations/2023-10-05-114138_add_payment_id_in_mandate/up.sql +-- Your SQL goes here +ALTER TABLE mandate ADD COLUMN original_payment_id VARCHAR(64); + + +-- File: migrations/2023-10-05-130917_add_mandate_webhook_types/up.sql +-- Your SQL goes here +ALTER TYPE "EventClass" ADD VALUE 'mandates'; + +ALTER TYPE "EventObjectType" ADD VALUE 'mandate_details'; + +ALTER TYPE "EventType" ADD VALUE 'mandate_active'; + +ALTER TYPE "EventType" ADD VALUE 'mandate_revoked'; + + diff --git a/aws/hyperswitch_aws_setup.sh b/aws/hyperswitch_aws_setup.sh new file mode 100644 index 000000000000..dd71b698e93e --- /dev/null +++ b/aws/hyperswitch_aws_setup.sh @@ -0,0 +1,340 @@ +#!/bin/bash + +command_discovery() { + type $1 > /dev/null 2> /dev/null + if [[ $? != 0 ]]; then + echo "\`$1\` command not found" + exit 1 + fi +} + +command_discovery curl +command_discovery aws +command_discovery psql + +echo "Please enter the AWS region (us-east-2): " +read REGION < /dev/tty + +if [ -z "$REGION" ]; then + echo "Using default region: us-east-2" + REGION="us-east-2" +fi + +while [[ -z "$MASTER_DB_PASSWORD" ]]; do + echo "Please enter the password for your RDS instance: " + echo "Minimum length: 8 Characters [A-Z][a-z][0-9]" + read MASTER_DB_PASSWORD < /dev/tty +done + +while [[ -z "$ADMIN_API_KEY" ]]; do + echo "Please configure the Admin api key: (Required to access Hyperswitch APIs)" + read ADMIN_API_KEY < /dev/tty +done + +############# APPLICATION ################## +# CREATE SECURITY GROUP FOR APPLICATION + +echo "Creating Security Group for Application..." + +export EC2_SG="application-sg" + +echo `(aws ec2 create-security-group \ +--region $REGION \ +--group-name $EC2_SG \ +--description "Security Group for Hyperswitch EC2 instance" \ +--tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ +)` + +export APP_SG_ID=$(aws ec2 describe-security-groups --group-names $EC2_SG --region $REGION --output text --query 'SecurityGroups[0].GroupId') + +echo "Security Group for Application CREATED.\n" + +echo "Creating Security Group ingress for port 80..." + +echo `aws ec2 authorize-security-group-ingress \ +--group-id $APP_SG_ID \ +--protocol tcp \ +--port 80 \ +--cidr 0.0.0.0/0 \ +--region $REGION` + +echo "Security Group ingress for port 80 CREATED.\n" + + +echo "Creating Security Group ingress for port 22..." + +echo `aws ec2 authorize-security-group-ingress \ +--group-id $APP_SG_ID \ +--protocol tcp \ +--port 22 \ +--cidr 0.0.0.0/0 \ +--region $REGION` + +echo "Security Group ingress for port 22 CREATED.\n" + +############# REDIS ################## +# CREATE SECURITY GROUP FOR ELASTICACHE + +echo "Creating Security Group for Elasticache..." + +export REDIS_GROUP_NAME=redis-sg +echo `aws ec2 create-security-group \ +--group-name $REDIS_GROUP_NAME \ +--description "SG attached to elasticache" \ +--tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ +--region $REGION` + +echo "Security Group for Elasticache CREATED.\n" + +echo "Creating Inbound rules for Redis..." + +export REDIS_SG_ID=$(aws ec2 describe-security-groups --group-names $REDIS_GROUP_NAME --region $REGION --output text --query 'SecurityGroups[0].GroupId') + +# CREATE INBOUND RULES +echo `aws ec2 authorize-security-group-ingress \ +--group-id $REDIS_SG_ID \ +--protocol tcp \ +--port 6379 \ +--source-group $EC2_SG \ +--region $REGION` + +echo "Inbound rules for Redis CREATED.\n" + +############# DB ################## + +echo "Creating Security Group for RDS..." + +export RDS_GROUP_NAME=rds-sg +echo `aws ec2 create-security-group \ +--group-name $RDS_GROUP_NAME \ +--description "SG attached to RDS" \ +--tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ +--region $REGION` + +echo "Security Group for RDS CREATED.\n" + +echo "Creating Inbound rules for RDS..." + +export RDS_SG_ID=$(aws ec2 describe-security-groups --group-names $RDS_GROUP_NAME --region $REGION --output text --query 'SecurityGroups[0].GroupId') + +# CREATE INBOUND RULES +echo `aws ec2 authorize-security-group-ingress \ +--group-id $RDS_SG_ID \ +--protocol tcp \ +--port 5432 \ +--source-group $EC2_SG \ +--region $REGION` + +echo "Inbound rules for RDS CREATED.\n" + +echo `aws ec2 authorize-security-group-ingress \ + --group-id $RDS_SG_ID \ + --protocol tcp \ + --port 5432 \ + --cidr 0.0.0.0/0 \ + --region $REGION` + +echo "Inbound rules for RDS (from any IP) CREATED.\n" + +echo "Creating Elasticache with Redis engine..." + +export CACHE_CLUSTER_ID=hyperswitch-cluster + +echo `aws elasticache create-cache-cluster \ +--cache-cluster-id $CACHE_CLUSTER_ID \ +--cache-node-type cache.t3.medium \ +--engine redis \ +--num-cache-nodes 1 \ +--security-group-ids $REDIS_SG_ID \ +--engine-version 7.0 \ +--tags "Key=ManagedBy,Value=hyperswitch" \ +--region $REGION` + +echo "Elasticache with Redis engine CREATED.\n" + +echo "Creating RDS with PSQL..." + +export DB_INSTANCE_ID=hyperswitch-db +echo `aws rds create-db-instance \ + --db-instance-identifier $DB_INSTANCE_ID\ + --db-instance-class db.t3.micro \ + --engine postgres \ + --allocated-storage 20 \ + --master-username hyperswitch \ + --master-user-password $MASTER_DB_PASSWORD \ + --backup-retention-period 7 \ + --region $REGION \ + --db-name hyperswitch_db \ + --tags "Key=ManagedBy,Value=hyperswitch" \ + --vpc-security-group-ids $RDS_SG_ID` + +echo "RDS with PSQL CREATED.\n" + +echo "Downloading Hyperswitch PSQL Schema..." + +curl https://raw.githubusercontent.com/juspay/hyperswitch/main/aws/beta_schema.sql > schema.sql + +echo "Schema.sql downloaded.\n" + +echo "Awaiting RDS Initialization..." + +export RDS_STATUS=$(aws rds describe-db-instances \ +--db-instance-identifier $DB_INSTANCE_ID \ +--region $REGION \ +--query "DBInstances[0].DBInstanceStatus" \ +--output text) + +while [[ $RDS_STATUS != 'available' ]]; do + echo $RDS_STATUS + sleep 10 + + export RDS_STATUS=$(aws rds describe-db-instances \ + --db-instance-identifier $DB_INSTANCE_ID \ + --region $REGION \ + --query "DBInstances[0].DBInstanceStatus" \ + --output text) +done + +echo "RDS Initialized.\n" + +echo "Retrieving RDS Endpoint..." + +export RDS_ENDPOINT=$(aws rds describe-db-instances --db-instance-identifier $DB_INSTANCE_ID --region $REGION --query "DBInstances[0].Endpoint.Address" --output text) + +echo "RDS Endpoint retrieved.\n" + +echo "Applying Schema to DB..." + +psql -d postgresql://hyperswitch:$MASTER_DB_PASSWORD@$RDS_ENDPOINT/hyperswitch_db -a -f schema.sql > /dev/null + +echo "Schema applied to DB.\n" + +cat << EOF > user_data.sh +#!/bin/bash + +sudo yum update -y +sudo amazon-linux-extras install docker +sudo service docker start +sudo usermod -a -G docker ec2-user + +docker pull juspaydotin/hyperswitch-router:beta + +curl https://raw.githubusercontent.com/juspay/hyperswitch/v1.55.0/config/development.toml > production.toml + +EOF + +echo "Awaiting Redis Initialization..." + +export redis_status=$(aws elasticache describe-cache-clusters \ + --region $REGION \ + --cache-cluster-id $CACHE_CLUSTER_ID \ + --query 'CacheClusters[0].CacheClusterStatus' \ + --output text) + +while [ $redis_status != 'available' ] +do + echo "$redis_status" + sleep 10 + export redis_status=$(aws elasticache describe-cache-clusters \ + --region $REGION \ + --cache-cluster-id $CACHE_CLUSTER_ID \ + --query 'CacheClusters[0].CacheClusterStatus' \ + --output text) +done + +echo "Redis Initialized.\n" + +echo "Retrieving Redis Endpoint..." + +export REDIS_ENDPOINT=$(aws elasticache describe-cache-clusters \ + --region $REGION \ + --cache-cluster-id $CACHE_CLUSTER_ID \ + --show-cache-node-info \ + --query 'CacheClusters[0].CacheNodes[].Endpoint.Address' \ + --output text) + +echo "Redis Endpoint retrieved.\n" + +echo "\n# Add redis and DB configs.\n" >> user_data.sh +echo "cat << EOF >> .env" >> user_data.sh +echo "ROUTER__REDIS__HOST=$REDIS_ENDPOINT" >> user_data.sh +echo "ROUTER__MASTER_DATABASE__HOST=$RDS_ENDPOINT" >> user_data.sh +echo "ROUTER__REPLICA_DATABASE__HOST=$RDS_ENDPOINT" >> user_data.sh +echo "ROUTER__SERVER__HOST=0.0.0.0" >> user_data.sh +echo "ROUTER__MASTER_DATABASE__USERNAME=hyperswitch" >> user_data.sh +echo "ROUTER__MASTER_DATABASE__PASSWORD=$MASTER_DB_PASSWORD" >> user_data.sh +echo "ROUTER__SERVER__BASE_URL=\$(curl ifconfig.me)" >> user_data.sh +echo "ROUTER__SECRETS__ADMIN_API_KEY=$ADMIN_API_KEY" >> user_data.sh +echo "EOF" >> user_data.sh + +echo "docker run --env-file .env -p 80:8080 -v \`pwd\`/:/local/config juspaydotin/hyperswitch-router:beta ./router -f /local/config/production.toml +" >> user_data.sh + +echo "Retrieving AWS AMI ID..." + +export AWS_AMI_ID=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*" --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text --region $REGION) + +echo "AWS AMI ID retrieved.\n" + +echo "Creating EC2 Keypair..." + +rm -rf hyperswitch-keypair.pem + +aws ec2 create-key-pair \ + --key-name hyperswitch-ec2-keypair \ + --query 'KeyMaterial' \ + --tag-specifications "ResourceType=key-pair,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ + --region $REGION \ + --output text > hyperswitch-keypair.pem + +echo "Keypair created and saved to hyperswitch-keypair.pem.\n" + +chmod 400 hyperswitch-keypair.pem + +echo "Launching EC2 Instance..." + +export HYPERSWITCH_INSTANCE_ID=$(aws ec2 run-instances \ + --image-id $AWS_AMI_ID \ + --instance-type t3.medium \ + --key-name hyperswitch-ec2-keypair \ + --monitoring "Enabled=false" \ + --security-group-ids $APP_SG_ID \ + --user-data file://./user_data.sh \ + --query 'Instances[0].InstanceId' \ + --output text \ + --region $REGION) + +echo "EC2 instance launched.\n" + +echo "Add Tags to EC2 instance..." + +echo `aws ec2 create-tags \ +--resources $HYPERSWITCH_INSTANCE_ID \ +--tags "Key=Name,Value=hyperswitch-router" \ +--region $REGION` + +echo "Tag added to EC2 instance.\n" + +echo `aws ec2 create-tags \ +--resources $HYPERSWITCH_INSTANCE_ID \ +--tags "Key=ManagedBy,Value=hyperswitch" \ +--region $REGION` + +echo "ManagedBy tag added to EC2 instance.\n" + +echo "Retrieving the Public IP of Hyperswitch EC2 Instance..." +export PUBLIC_HYPERSWITCH_IP=$(aws ec2 describe-instances \ +--instance-ids $HYPERSWITCH_INSTANCE_ID \ +--query "Reservations[*].Instances[*].PublicIpAddress" \ +--output=text \ +--region $REGION) + +health_status=null +while [[ $health_status != 'health is good' ]] +do + health_status=$(curl http://$PUBLIC_HYPERSWITCH_IP/health) + sleep 10 +done + +echo "Hurray! You can try using hyperswitch at http://$PUBLIC_HYPERSWITCH_IP" +echo "Health endpoint: http://$PUBLIC_HYPERSWITCH_IP/health" diff --git a/aws/hyperswitch_cleanup_setup.sh b/aws/hyperswitch_cleanup_setup.sh new file mode 100644 index 000000000000..383f23d5bd00 --- /dev/null +++ b/aws/hyperswitch_cleanup_setup.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +command_discovery() { + type $1 > /dev/null 2> /dev/null + if [[ $? != 0 ]]; then + echo "\`$1\` command not found" + exit 1 + fi +} + +yes_or_no() { + read response < /dev/tty + case $response in + [Yy]* ) return 0 ;; + [Nn]* ) return 1 ;; + * ) return 1 ;; + esac +} + +command_discovery aws +command_discovery jq + +echo -n "Please enter the AWS region (us-east-2): " +read REGION < /dev/tty + +if [ -z "$REGION" ]; then + echo "Using default region: us-east-2" + REGION="us-east-2" +fi + +export ALL_ELASTIC_CACHE=($(aws elasticache describe-cache-clusters \ + --region $REGION \ + --query "CacheClusters[*].ARN" --output text)) + +for cluster_arn in $ALL_ELASTIC_CACHE; do + cluster_id=${cluster_arn##*:} + + aws elasticache list-tags-for-resource \ + --resource-name $cluster_arn \ + --region $REGION \ + --output json | jq \ + '.TagList[] | select( [ .Key == "ManagedBy" and .Value == "hyperswitch" ] | any)' \ + -e > /dev/null + + if [[ $? -eq 0 ]]; then + echo -n "Delete $cluster_id (Y/n)? " + if yes_or_no; then + echo `aws elasticache delete-cache-cluster --region $REGION --cache-cluster-id $cluster_id` + fi + fi +done + +export ALL_KEY_PAIRS=($(aws ec2 describe-key-pairs \ + --filters "Name=tag:ManagedBy,Values=hyperswitch" \ +--region $REGION \ + --query 'KeyPairs[*].KeyPairId' --output text)) + +echo -n "Deleting ( $ALL_KEY_PAIRS ) key pairs? (Y/n)?" + +if yes_or_no; then + for KEY_ID in $ALL_KEY_PAIRS; do + echo `aws ec2 delete-key-pair --key-pair-id $KEY_ID --region $REGION` + done +fi + +export ALL_INSTANCES=$(aws ec2 describe-instances \ + --filters 'Name=tag:ManagedBy,Values=hyperswitch' 'Name=instance-state-name,Values=running' \ +--region $REGION \ + --query 'Reservations[*].Instances[*].InstanceId' --output text) + +export ALL_EBS=$(aws ec2 describe-instances \ + --filters 'Name=tag:ManagedBy,Values=hyperswitch' \ +--region $REGION \ + --query 'Reservations[*].Instances[*].BlockDeviceMappings[*].Ebs.VolumeId' \ + --output text) + +echo -n "Terminating ( $ALL_INSTANCES ) instances? (Y/n)?" + +if yes_or_no; then + for INSTANCE_ID in $ALL_INSTANCES; do + echo `aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION` + done +fi + +export ALL_DB_RESOURCES=($(aws rds describe-db-instances \ +--region $REGION \ + --query 'DBInstances[*].DBInstanceArn' --output text)) + +for resource_id in $ALL_DB_RESOURCES; do + aws rds list-tags-for-resource \ + --resource-name $resource_id \ + --region $REGION \ + --output json | jq \ + '.TagList[] | select( [ .Key == "ManagedBy" and .Value == "hyperswitch" ] | any )' \ + -e > /dev/null + + if [[ $? -eq 0 ]]; then + echo -n "Delete $resource_id (Y/n)? " + if yes_or_no; then + export DB_INSTANCE_ID=$(aws rds describe-db-instances \ + --region $REGION \ + --filters "Name=db-instance-id,Values=$resource_id" \ + --query 'DBInstances[*].DBInstanceIdentifier' --output text) + + + echo -n "Create a snapshot before deleting ( $DB_INSTANCE_ID ) the database (Y/n)? " + if yes_or_no; then + echo `aws rds delete-db-instance \ + --db-instance-identifier $DB_INSTANCE_ID \ + --region $REGION \ + --final-db-snapshot-identifier hyperswitch-db-snapshot-`date +%s`` + else + echo `aws rds delete-db-instance \ + --region $REGION \ + --db-instance-identifier $DB_INSTANCE_ID \ + --skip-final-snapshot` + fi + fi + fi +done + + +export ALL_SECURITY_GROUPS=$(aws ec2 describe-security-groups \ + --filters 'Name=tag:ManagedBy,Values=hyperswitch' \ +--region $REGION \ +--query 'SecurityGroups[*].GroupId' --output json | jq .[] --raw-output) + +echo -n "Deleting ( $ALL_SECURITY_GROUPS ) security groups? (Y/n)?" + +if yes_or_no; then + export do_it=true + + while $do_it; do + export do_it=false + for GROUP_ID in $ALL_SECURITY_GROUPS; do + aws ec2 delete-security-group --group-id $GROUP_ID --region $REGION + if [[ $? != 0 ]]; then + export do_it=true + fi + done + + if $do_it; then + echo -n "Retry deleting the security group (Y/n)? " + + if yes_or_no; then + export do_it=true + else + export do_it=false + fi + fi + done +fi From 8b1499e121678c5df3ca0197e2ec14074fd96eb5 Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Mon, 30 Oct 2023 19:15:57 +0530 Subject: [PATCH 17/38] chore(env): add ttl as env variable (#2653) --- config/config.example.toml | 7 ++++++- config/development.toml | 5 ++++- config/docker_compose.toml | 5 ++++- crates/router/Cargo.toml | 2 +- crates/router/src/configs/defaults.rs | 7 +++++++ crates/router/src/configs/settings.rs | 7 +++++++ crates/router/src/services.rs | 1 + crates/storage_impl/src/consts.rs | 2 -- crates/storage_impl/src/lib.rs | 9 ++++++--- crates/storage_impl/src/redis/kv_store.rs | 17 ++++++----------- loadtest/config/development.toml | 3 +++ 11 files changed, 45 insertions(+), 20 deletions(-) delete mode 100644 crates/storage_impl/src/consts.rs diff --git a/config/config.example.toml b/config/config.example.toml index 5d95566f1689..59083d6c71d3 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -435,4 +435,9 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private [payment_link] -sdk_url = "http://localhost:9090/dist/HyperLoader.js" \ No newline at end of file +sdk_url = "http://localhost:9090/dist/HyperLoader.js" + +# Config for KV setup +[kv_config] +# TTL for KV in seconds +ttl = 900 diff --git a/config/development.toml b/config/development.toml index 7cb2d0f86991..5e74eafcb467 100644 --- a/config/development.toml +++ b/config/development.toml @@ -448,4 +448,7 @@ sdk_url = "http://localhost:9090/dist/HyperLoader.js" [lock_settings] redis_lock_expiry_seconds = 180 # 3 * 60 seconds -delay_between_retries_in_milliseconds = 500 \ No newline at end of file +delay_between_retries_in_milliseconds = 500 + +[kv_config] +ttl = 900 # 15 * 60 seconds diff --git a/config/docker_compose.toml b/config/docker_compose.toml index c01888ea5bba..20ca175ceb84 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -315,4 +315,7 @@ supported_connectors = "braintree" [lock_settings] redis_lock_expiry_seconds = 180 # 3 * 60 seconds -delay_between_retries_in_milliseconds = 500 \ No newline at end of file +delay_between_retries_in_milliseconds = 500 + +[kv_config] +ttl = 900 # 15 * 60 seconds diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index fd8b21520b5c..81b23314ffb8 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -15,7 +15,7 @@ kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config"] basilisk = ["kms"] stripe = ["dep:serde_qs"] -release = ["kms", "stripe", "basilisk", "s3", "email","accounts_cache"] +release = ["kms", "stripe","basilisk","s3", "email","accounts_cache","kv_store"] olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap"] oltp = ["data_models/oltp", "storage_impl/oltp"] kv_store = ["scheduler/kv_store"] diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 3bb1c31d180b..8d58037343e0 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -103,6 +103,13 @@ impl Default for super::settings::DrainerSettings { } } +#[cfg(feature = "kv_store")] +impl Default for super::settings::KvConfig { + fn default() -> Self { + Self { ttl: 900 } + } +} + use super::settings::{ Mandates, SupportedConnectorsForMandate, SupportedPaymentMethodTypesForMandate, SupportedPaymentMethodsForMandate, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index b2c27ed398d2..204060b37aa0 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -101,6 +101,13 @@ pub struct Settings { pub lock_settings: LockSettings, pub temp_locker_enable_config: TempLockerEnableConfig, pub payment_link: PaymentLink, + #[cfg(feature = "kv_store")] + pub kv_config: KvConfig, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct KvConfig { + pub ttl: u32, } #[derive(Debug, Deserialize, Clone, Default)] diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index d216d6164276..631e9a5c189d 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -90,6 +90,7 @@ pub async fn get_store( store, config.drainer.stream_name.clone(), config.drainer.num_partitions, + config.kv_config.ttl, ); Ok(store) diff --git a/crates/storage_impl/src/consts.rs b/crates/storage_impl/src/consts.rs deleted file mode 100644 index 04eab6176f94..000000000000 --- a/crates/storage_impl/src/consts.rs +++ /dev/null @@ -1,2 +0,0 @@ -// TTL for KV setup -pub(crate) const KV_TTL: u32 = 300; diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index dd8d71fc701e..17d432c7932b 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -9,7 +9,6 @@ mod address; pub mod config; pub mod connection; mod connector_response; -mod consts; pub mod database; pub mod errors; mod lookup; @@ -138,6 +137,7 @@ pub struct KVRouterStore { router_store: RouterStore, drainer_stream_name: String, drainer_num_partitions: u8, + ttl_for_kv: u32, } #[async_trait::async_trait] @@ -146,13 +146,14 @@ where RouterStore: DatabaseStore, T: DatabaseStore, { - type Config = (RouterStore, String, u8); + type Config = (RouterStore, String, u8, u32); async fn new(config: Self::Config, _test_transaction: bool) -> StorageResult { - let (router_store, drainer_stream_name, drainer_num_partitions) = config; + let (router_store, drainer_stream_name, drainer_num_partitions, ttl_for_kv) = config; Ok(Self::from_store( router_store, drainer_stream_name, drainer_num_partitions, + ttl_for_kv, )) } fn get_master_pool(&self) -> &PgPool { @@ -176,11 +177,13 @@ impl KVRouterStore { store: RouterStore, drainer_stream_name: String, drainer_num_partitions: u8, + ttl_for_kv: u32, ) -> Self { Self { router_store: store, drainer_stream_name, drainer_num_partitions, + ttl_for_kv, } } diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 52f58f6a7539..0c615d74f89a 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -6,7 +6,7 @@ use router_derive::TryGetEnumVariant; use router_env::logger; use serde::de; -use crate::{consts, metrics, store::kv::TypedSql, KVRouterStore}; +use crate::{metrics, store::kv::TypedSql, KVRouterStore}; pub trait KvStorePartition { fn partition_number(key: PartitionKey<'_>, num_partitions: u8) -> u32 { @@ -102,6 +102,8 @@ where let type_name = std::any::type_name::(); let operation = op.to_string(); + let ttl = store.ttl_for_kv; + let partition_key = PartitionKey::MerchantIdPaymentIdCombination { combination: key }; let result = async { @@ -109,9 +111,7 @@ where KvOperation::Hset(value, sql) => { logger::debug!(kv_operation= %operation, value = ?value); - redis_conn - .set_hash_fields(key, value, Some(consts::KV_TTL)) - .await?; + redis_conn.set_hash_fields(key, value, Some(ttl)).await?; store .push_to_drainer_stream::(sql, partition_key) @@ -136,12 +136,7 @@ where logger::debug!(kv_operation= %operation, value = ?value); let result = redis_conn - .serialize_and_set_hash_field_if_not_exist( - key, - field, - value, - Some(consts::KV_TTL), - ) + .serialize_and_set_hash_field_if_not_exist(key, field, value, Some(ttl)) .await?; if matches!(result, redis_interface::HsetnxReply::KeySet) { @@ -156,7 +151,7 @@ where logger::debug!(kv_operation= %operation, value = ?value); let result = redis_conn - .serialize_and_set_key_if_not_exist(key, value, Some(consts::KV_TTL.into())) + .serialize_and_set_key_if_not_exist(key, value, Some(ttl.into())) .await?; if matches!(result, redis_interface::SetnxReply::KeySet) { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index c2ecd68dc841..7cdbc8dd6fdd 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -234,3 +234,6 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay, bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} + +[kv_config] +ttl = 300 # 5 * 60 seconds From 452090d56d713a5cc5c8fae3cc2f9f3d26e27a53 Mon Sep 17 00:00:00 2001 From: Seemebadnekai <51400137+SagarDevAchar@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:20:27 +0530 Subject: [PATCH 18/38] refactor(connector): [Noon] Remove Default Case Handling (#2677) --- .../router/src/connector/noon/transformers.rs | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index cde6de2e43b6..4a2128f7ec64 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -245,13 +245,51 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { return_url: item.request.get_router_return_url()?, })) } - _ => Err(errors::ConnectorError::NotImplemented( - "Wallets".to_string(), - )), + api_models::payments::WalletData::AliPayQr(_) + | api_models::payments::WalletData::AliPayRedirect(_) + | api_models::payments::WalletData::AliPayHkRedirect(_) + | api_models::payments::WalletData::MomoRedirect(_) + | api_models::payments::WalletData::KakaoPayRedirect(_) + | api_models::payments::WalletData::GoPayRedirect(_) + | api_models::payments::WalletData::GcashRedirect(_) + | api_models::payments::WalletData::ApplePayRedirect(_) + | api_models::payments::WalletData::ApplePayThirdPartySdk(_) + | api_models::payments::WalletData::DanaRedirect {} + | api_models::payments::WalletData::GooglePayRedirect(_) + | api_models::payments::WalletData::GooglePayThirdPartySdk(_) + | api_models::payments::WalletData::MbWayRedirect(_) + | api_models::payments::WalletData::MobilePayRedirect(_) + | api_models::payments::WalletData::PaypalSdk(_) + | api_models::payments::WalletData::SamsungPay(_) + | api_models::payments::WalletData::TwintRedirect {} + | api_models::payments::WalletData::VippsRedirect {} + | api_models::payments::WalletData::TouchNGoRedirect(_) + | api_models::payments::WalletData::WeChatPayRedirect(_) + | api_models::payments::WalletData::WeChatPayQr(_) + | api_models::payments::WalletData::CashappQr(_) + | api_models::payments::WalletData::SwishQr(_) => { + Err(errors::ConnectorError::NotSupported { + message: conn_utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Noon", + }) + } }, - _ => Err(errors::ConnectorError::NotImplemented( - "Payment methods".to_string(), - )), + api::PaymentMethodData::CardRedirect(_) + | api::PaymentMethodData::PayLater(_) + | api::PaymentMethodData::BankRedirect(_) + | api::PaymentMethodData::BankDebit(_) + | api::PaymentMethodData::BankTransfer(_) + | api::PaymentMethodData::Crypto(_) + | api::PaymentMethodData::MandatePayment {} + | api::PaymentMethodData::Reward {} + | api::PaymentMethodData::Upi(_) + | api::PaymentMethodData::Voucher(_) + | api::PaymentMethodData::GiftCard(_) => { + Err(errors::ConnectorError::NotSupported { + message: conn_utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Noon", + }) + } }?, Some(item.request.currency), item.request.order_category.clone(), From 8984627d1cfd1a773e931617a3351884b12399a5 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:33:45 +0530 Subject: [PATCH 19/38] fix(typo): add commit id to allowed typos (#2733) --- .typos.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.typos.toml b/.typos.toml index 8ed1c84a5bb5..0d6e6fd8e38c 100644 --- a/.typos.toml +++ b/.typos.toml @@ -34,6 +34,7 @@ unsuccess = "unsuccess" # Used in cryptopay request ba = "ba" # ignore minor commit conversions ede = "ede" # ignore minor commit conversions daa = "daa" # Commit id +afe = "afe" # Commit id [files] extend-exclude = [ From 9d9fc2a8c5e9e30ed7ed4eeb2417365fc06be711 Mon Sep 17 00:00:00 2001 From: Abhishek Marrivagu <68317979+Abhicodes-crypto@users.noreply.github.com> Date: Tue, 31 Oct 2023 00:04:53 +0530 Subject: [PATCH 20/38] refactor(db): migrate to payment_attempt from connector_response (#2656) --- .../src/payments/payment_attempt.rs | 11 + crates/diesel_models/src/payment_attempt.rs | 29 +++ .../src/query/connector_response.rs | 191 +++++++++++++++--- crates/diesel_models/src/schema.rs | 2 + crates/router/src/core/payments/helpers.rs | 2 + .../src/mock_db/payment_attempt.rs | 2 + .../src/payments/payment_attempt.rs | 39 +++- .../down.sql | 2 + .../up.sql | 11 + 9 files changed, 259 insertions(+), 30 deletions(-) create mode 100644 migrations/2023-10-19-102636_back_fill_n_remove_connector_response/down.sql create mode 100644 migrations/2023-10-19-102636_back_fill_n_remove_connector_response/up.sql diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index a3ddbe890604..734de8fe4a55 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -142,6 +142,8 @@ pub struct PaymentAttempt { pub connector_response_reference_id: Option, pub amount_capturable: i64, pub updated_by: String, + pub authentication_data: Option, + pub encoded_data: Option, pub merchant_connector_id: Option, } @@ -202,6 +204,8 @@ pub struct PaymentAttemptNew { pub multiple_capture_count: Option, pub amount_capturable: i64, pub updated_by: String, + pub authentication_data: Option, + pub encoded_data: Option, pub merchant_connector_id: Option, } @@ -325,6 +329,13 @@ pub enum PaymentAttemptUpdate { connector_response_reference_id: Option, updated_by: String, }, + ConnectorResponse { + authentication_data: Option, + encoded_data: Option, + connector_transaction_id: Option, + connector: Option, + updated_by: String, + }, } impl ForeignIDRef for PaymentAttempt { diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 8603aee5cb5c..058086106111 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -59,6 +59,8 @@ pub struct PaymentAttempt { pub amount_capturable: i64, pub updated_by: String, pub merchant_connector_id: Option, + pub authentication_data: Option, + pub encoded_data: Option, } #[derive(Clone, Debug, Eq, PartialEq, Queryable, Serialize, Deserialize)] @@ -120,6 +122,8 @@ pub struct PaymentAttemptNew { pub amount_capturable: i64, pub updated_by: String, pub merchant_connector_id: Option, + pub authentication_data: Option, + pub encoded_data: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -242,6 +246,13 @@ pub enum PaymentAttemptUpdate { connector_response_reference_id: Option, updated_by: String, }, + ConnectorResponse { + authentication_data: Option, + encoded_data: Option, + connector_transaction_id: Option, + connector: Option, + updated_by: String, + }, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -279,6 +290,8 @@ pub struct PaymentAttemptUpdateInternal { amount_capturable: Option, updated_by: String, merchant_connector_id: Option, + authentication_data: Option, + encoded_data: Option, } impl PaymentAttemptUpdate { @@ -331,6 +344,8 @@ impl PaymentAttemptUpdate { .unwrap_or(source.amount_capturable), updated_by: pa_update.updated_by, merchant_connector_id: pa_update.merchant_connector_id, + authentication_data: pa_update.authentication_data.or(source.authentication_data), + encoded_data: pa_update.encoded_data.or(source.encoded_data), ..source } } @@ -581,6 +596,20 @@ impl From for PaymentAttemptUpdateInternal { updated_by, ..Default::default() }, + PaymentAttemptUpdate::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + } => Self { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + ..Default::default() + }, } } } diff --git a/crates/diesel_models/src/query/connector_response.rs b/crates/diesel_models/src/query/connector_response.rs index eea7e779d8d5..952db945ae38 100644 --- a/crates/diesel_models/src/query/connector_response.rs +++ b/crates/diesel_models/src/query/connector_response.rs @@ -1,5 +1,5 @@ use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; -use router_env::{instrument, tracing}; +use router_env::{instrument, logger, tracing}; use super::generics; use crate::{ @@ -8,13 +8,44 @@ use crate::{ ConnectorResponseUpdateInternal, }, errors, - schema::connector_response::dsl, + payment_attempt::{PaymentAttempt, PaymentAttemptUpdate, PaymentAttemptUpdateInternal}, + schema::{connector_response::dsl, payment_attempt::dsl as pa_dsl}, PgPooledConn, StorageResult, }; impl ConnectorResponseNew { #[instrument(skip(conn))] pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + let payment_attempt_update = PaymentAttemptUpdate::ConnectorResponse { + authentication_data: self.authentication_data.clone(), + encoded_data: self.encoded_data.clone(), + connector_transaction_id: self.connector_transaction_id.clone(), + connector: self.connector_name.clone(), + updated_by: self.updated_by.clone(), + }; + + let _payment_attempt: Result = + generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + pa_dsl::attempt_id + .eq(self.attempt_id.to_owned()) + .and(pa_dsl::merchant_id.eq(self.merchant_id.to_owned())), + PaymentAttemptUpdateInternal::from(payment_attempt_update), + ) + .await + .map_err(|err| { + logger::error!( + "Error while updating payment attempt in connector_response flow {:?}", + err + ); + err + }); + generics::generic_insert(conn, self).await } } @@ -26,27 +57,78 @@ impl ConnectorResponse { conn: &PgPooledConn, connector_response: ConnectorResponseUpdate, ) -> StorageResult { - match generics::generic_update_with_unique_predicate_get_result::< - ::Table, - _, - _, - _, - >( - conn, - dsl::merchant_id - .eq(self.merchant_id.clone()) - .and(dsl::payment_id.eq(self.payment_id.clone())) - .and(dsl::attempt_id.eq(self.attempt_id.clone())), - ConnectorResponseUpdateInternal::from(connector_response), - ) - .await - { - Err(error) => match error.current_context() { - errors::DatabaseError::NoFieldsToUpdate => Ok(self), - _ => Err(error), + let payment_attempt_update = match connector_response.clone() { + ConnectorResponseUpdate::ResponseUpdate { + connector_transaction_id, + authentication_data, + encoded_data, + connector_name, + updated_by, + } => PaymentAttemptUpdate::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector: connector_name, + updated_by, }, - result => result, - } + ConnectorResponseUpdate::ErrorUpdate { + connector_name, + updated_by, + } => PaymentAttemptUpdate::ConnectorResponse { + authentication_data: None, + encoded_data: None, + connector_transaction_id: None, + connector: connector_name, + updated_by, + }, + }; + + let _payment_attempt: Result = + generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + pa_dsl::attempt_id + .eq(self.attempt_id.to_owned()) + .and(pa_dsl::merchant_id.eq(self.merchant_id.to_owned())), + PaymentAttemptUpdateInternal::from(payment_attempt_update), + ) + .await + .map_err(|err| { + logger::error!( + "Error while updating payment attempt in connector_response flow {:?}", + err + ); + err + }); + + let connector_response_result = + match generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + dsl::merchant_id + .eq(self.merchant_id.clone()) + .and(dsl::payment_id.eq(self.payment_id.clone())) + .and(dsl::attempt_id.eq(self.attempt_id.clone())), + ConnectorResponseUpdateInternal::from(connector_response), + ) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NoFieldsToUpdate => Ok(self), + _ => Err(error), + }, + result => result, + }; + + connector_response_result } #[instrument(skip(conn))] @@ -56,14 +138,69 @@ impl ConnectorResponse { merchant_id: &str, attempt_id: &str, ) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( + let connector_response: Self = + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id.eq(merchant_id.to_owned()).and( + dsl::payment_id + .eq(payment_id.to_owned()) + .and(dsl::attempt_id.eq(attempt_id.to_owned())), + ), + ) + .await?; + + match generics::generic_find_one::<::Table, _, _>( conn, - dsl::merchant_id.eq(merchant_id.to_owned()).and( - dsl::payment_id - .eq(payment_id.to_owned()) - .and(dsl::attempt_id.eq(attempt_id.to_owned())), + pa_dsl::payment_id.eq(payment_id.to_owned()).and( + pa_dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(pa_dsl::attempt_id.eq(attempt_id.to_owned())), ), ) .await + { + Ok::(payment_attempt) => { + if payment_attempt.authentication_data != connector_response.authentication_data { + logger::error!( + "Not Equal pa_authentication_data : {:?}, cr_authentication_data: {:?} ", + payment_attempt.authentication_data, + connector_response.authentication_data + ); + } + + if payment_attempt.encoded_data != connector_response.encoded_data { + logger::error!( + "Not Equal pa_encoded_data : {:?}, cr_encoded_data: {:?} ", + payment_attempt.encoded_data, + connector_response.encoded_data + ); + } + + if payment_attempt.connector_transaction_id + != connector_response.connector_transaction_id + { + logger::error!( + "Not Equal pa_connector_transaction_id : {:?}, cr_connector_transaction_id: {:?} ", + payment_attempt.connector_transaction_id, + connector_response.connector_transaction_id + ); + } + if payment_attempt.connector != connector_response.connector_name { + logger::error!( + "Not Equal pa_connector : {:?}, cr_connector_name: {:?} ", + payment_attempt.connector, + connector_response.connector_name + ); + } + } + Err(err) => { + logger::error!( + "Error while finding payment attempt in connector_response flow {:?}", + err + ); + } + } + + Ok(connector_response) } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 926b31c0ceb4..02abfb842b8d 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -580,6 +580,8 @@ diesel::table! { updated_by -> Varchar, #[max_length = 32] merchant_connector_id -> Nullable, + authentication_data -> Nullable, + encoded_data -> Nullable, } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4deac54c7010..f78e37ac7d12 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2933,6 +2933,8 @@ impl AttemptType { connector_response_reference_id: None, amount_capturable: old_payment_attempt.amount, updated_by: storage_scheme.to_string(), + authentication_data: None, + encoded_data: None, merchant_connector_id: None, } } diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index a9f9b9d3c0fa..cb2f81daa797 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -141,6 +141,8 @@ impl PaymentAttemptInterface for MockDb { connector_response_reference_id: None, amount_capturable: payment_attempt.amount_capturable, updated_by: storage_scheme.to_string(), + authentication_data: payment_attempt.authentication_data, + encoded_data: payment_attempt.encoded_data, merchant_connector_id: payment_attempt.merchant_connector_id, }; payment_attempts.push(payment_attempt.clone()); diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index ae756c38c0ce..e3047221b6f5 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -361,6 +361,8 @@ impl PaymentAttemptInterface for KVRouterStore { connector_response_reference_id: None, amount_capturable: payment_attempt.amount_capturable, updated_by: storage_scheme.to_string(), + authentication_data: payment_attempt.authentication_data.clone(), + encoded_data: payment_attempt.encoded_data.clone(), merchant_connector_id: payment_attempt.merchant_connector_id.clone(), }; @@ -956,8 +958,9 @@ impl DataModelExt for PaymentAttempt { multiple_capture_count: self.multiple_capture_count, connector_response_reference_id: self.connector_response_reference_id, amount_capturable: self.amount_capturable, - updated_by: self.updated_by, + authentication_data: self.authentication_data, + encoded_data: self.encoded_data, merchant_connector_id: self.merchant_connector_id, } } @@ -1007,8 +1010,9 @@ impl DataModelExt for PaymentAttempt { multiple_capture_count: storage_model.multiple_capture_count, connector_response_reference_id: storage_model.connector_response_reference_id, amount_capturable: storage_model.amount_capturable, - updated_by: storage_model.updated_by, + authentication_data: storage_model.authentication_data, + encoded_data: storage_model.encoded_data, merchant_connector_id: storage_model.merchant_connector_id, } } @@ -1060,6 +1064,8 @@ impl DataModelExt for PaymentAttemptNew { amount_capturable: self.amount_capturable, updated_by: self.updated_by, + authentication_data: self.authentication_data, + encoded_data: self.encoded_data, merchant_connector_id: self.merchant_connector_id, } } @@ -1107,8 +1113,9 @@ impl DataModelExt for PaymentAttemptNew { connector_response_reference_id: storage_model.connector_response_reference_id, multiple_capture_count: storage_model.multiple_capture_count, amount_capturable: storage_model.amount_capturable, - updated_by: storage_model.updated_by, + authentication_data: storage_model.authentication_data, + encoded_data: storage_model.encoded_data, merchant_connector_id: storage_model.merchant_connector_id, } } @@ -1338,6 +1345,19 @@ impl DataModelExt for PaymentAttemptUpdate { amount_capturable, updated_by, }, + Self::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + } => DieselPaymentAttemptUpdate::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + }, } } @@ -1562,6 +1582,19 @@ impl DataModelExt for PaymentAttemptUpdate { amount_capturable, updated_by, }, + DieselPaymentAttemptUpdate::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + } => Self::ConnectorResponse { + authentication_data, + encoded_data, + connector_transaction_id, + connector, + updated_by, + }, } } } diff --git a/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/down.sql b/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/down.sql new file mode 100644 index 000000000000..7d14e420e90c --- /dev/null +++ b/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/down.sql @@ -0,0 +1,2 @@ + +ALTER TABLE payment_attempt DROP COLUMN authentication_data, DROP COLUMN encoded_data; diff --git a/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/up.sql b/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/up.sql new file mode 100644 index 000000000000..e80d4db1b4c0 --- /dev/null +++ b/migrations/2023-10-19-102636_back_fill_n_remove_connector_response/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN authentication_data JSON, +ADD COLUMN encoded_data TEXT; + +UPDATE payment_attempt +SET (authentication_data, encoded_data) = (connector_response.authentication_data, connector_response.encoded_data) +from connector_response +where payment_attempt.payment_id = connector_response.payment_id + and payment_attempt.attempt_id = connector_response.attempt_id + and payment_attempt.merchant_id = connector_response.merchant_id; From 8c85173ecdd13db5ec7c4c0fe18456a31c8ee57e Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:14:45 +0530 Subject: [PATCH 21/38] refactor(core): use `business_profile` to read merchant configs (#2729) --- crates/router/src/core/admin.rs | 74 ++++++++++++++++++++- crates/router/src/core/payments.rs | 25 +++++-- crates/router/src/core/payments/helpers.rs | 20 +++--- crates/router/src/core/webhooks.rs | 50 ++++++++++++-- crates/router/src/utils.rs | 2 + crates/router/src/workflows/payment_sync.rs | 18 +++++ 6 files changed, 163 insertions(+), 26 deletions(-) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 2e10aeaaf288..cd1243dd3688 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1,4 +1,7 @@ -use api_models::{admin as admin_types, enums as api_enums}; +use api_models::{ + admin::{self as admin_types}, + enums as api_enums, +}; use common_utils::{ crypto::{generate_cryptographically_secure_random_string, OptionalSecretValue}, date_time, @@ -6,6 +9,7 @@ use common_utils::{ pii, }; use error_stack::{report, FutureExt, ResultExt}; +use futures::future::try_join_all; use masking::{PeekInterface, Secret}; use uuid::Uuid; @@ -379,6 +383,66 @@ pub async fn create_business_profile_from_business_labels( Ok(()) } + +/// For backwards compatibility +/// If any of the fields of merchant account are updated, then update these fields in business profiles +pub async fn update_business_profile_cascade( + state: AppState, + merchant_account_update: api::MerchantAccountUpdate, + merchant_id: String, +) -> RouterResult<()> { + if merchant_account_update.return_url.is_some() + || merchant_account_update.webhook_details.is_some() + || merchant_account_update + .enable_payment_response_hash + .is_some() + || merchant_account_update + .redirect_to_merchant_with_http_post + .is_some() + { + // Update these fields in all the business profiles + let business_profiles = state + .store + .list_business_profile_by_merchant_id(&merchant_id) + .await + .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { + id: merchant_id.to_string(), + })?; + + let business_profile_update = admin_types::BusinessProfileUpdate { + profile_name: None, + return_url: merchant_account_update.return_url, + enable_payment_response_hash: merchant_account_update.enable_payment_response_hash, + payment_response_hash_key: merchant_account_update.payment_response_hash_key, + redirect_to_merchant_with_http_post: merchant_account_update + .redirect_to_merchant_with_http_post, + webhook_details: merchant_account_update.webhook_details, + metadata: None, + routing_algorithm: None, + intent_fulfillment_time: None, + frm_routing_algorithm: None, + payout_routing_algorithm: None, + applepay_verified_domains: None, + }; + + let update_futures = business_profiles.iter().map(|business_profile| async { + let profile_id = &business_profile.profile_id; + + update_business_profile( + state.clone(), + profile_id, + &merchant_id, + business_profile_update.clone(), + ) + .await + }); + + try_join_all(update_futures).await?; + } + + Ok(()) +} + pub async fn merchant_account_update( state: AppState, merchant_id: &String, @@ -431,6 +495,7 @@ pub async fn merchant_account_update( // In order to support backwards compatibility, if a business_labels are passed in the update // call, then create new business_profiles with the profile_name as business_label req.primary_business_details + .clone() .async_map(|primary_business_details| async { let _ = create_business_profile_from_business_labels( db, @@ -444,10 +509,10 @@ pub async fn merchant_account_update( let key = key_store.key.get_inner().peek(); - let business_profile_id_update = if let Some(profile_id) = req.default_profile { + let business_profile_id_update = if let Some(ref profile_id) = req.default_profile { if !profile_id.is_empty_after_trim() { // Validate whether profile_id passed in request is valid and is linked to the merchant - core_utils::validate_and_get_business_profile(db, Some(&profile_id), merchant_id) + core_utils::validate_and_get_business_profile(db, Some(profile_id), merchant_id) .await? .map(|business_profile| Some(business_profile.profile_id)) } else { @@ -458,6 +523,9 @@ pub async fn merchant_account_update( None }; + // Update the business profile, This is for backwards compatibility + update_business_profile_cascade(state.clone(), req.clone(), merchant_id.to_string()).await?; + let updated_merchant_account = storage::MerchantAccountUpdate::Update { merchant_name: req .merchant_name diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index ab56a4b35afe..f26b91479ece 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -345,7 +345,7 @@ pub trait PaymentRedirectFlow: Sync { fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: router_types::domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, payment_id: String, connector: String, ) -> RouterResult; @@ -418,8 +418,21 @@ pub trait PaymentRedirectFlow: Sync { .attach_printable("Failed to get the response in json"), }?; + let profile_id = payments_response + .profile_id + .as_ref() + .get_required_value("profile_id")?; + + let business_profile = state + .store + .find_business_profile_by_profile_id(profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.to_string(), + })?; + let result = - self.generate_response(payments_response, merchant_account, resource_id, connector)?; + self.generate_response(payments_response, business_profile, resource_id, connector)?; Ok(services::ApplicationResponse::JsonForRedirection(result)) } @@ -469,7 +482,7 @@ impl PaymentRedirectFlow for PaymentRedirectCom fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: router_types::domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, payment_id: String, connector: String, ) -> RouterResult { @@ -506,7 +519,7 @@ impl PaymentRedirectFlow for PaymentRedirectCom | api_models::enums::IntentStatus::Failed | api_models::enums::IntentStatus::Cancelled | api_models::enums::IntentStatus::RequiresCapture| api_models::enums::IntentStatus::Processing=> helpers::get_handle_response_url( payment_id, - &merchant_account, + &business_profile, payments_response, connector, ), @@ -560,13 +573,13 @@ impl PaymentRedirectFlow for PaymentRedirectSyn fn generate_response( &self, payments_response: api_models::payments::PaymentsResponse, - merchant_account: router_types::domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, payment_id: String, connector: String, ) -> RouterResult { helpers::get_handle_response_url( payment_id, - &merchant_account, + &business_profile, payments_response, connector, ) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index f78e37ac7d12..af67d30ec6c3 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1891,7 +1891,7 @@ pub(super) fn validate_payment_list_request_for_joins( pub fn get_handle_response_url( payment_id: String, - merchant_account: &domain::MerchantAccount, + business_profile: &diesel_models::business_profile::BusinessProfile, response: api::PaymentsResponse, connector: String, ) -> RouterResult { @@ -1900,7 +1900,7 @@ pub fn get_handle_response_url( let redirection_response = make_pg_redirect_response(payment_id, &response, connector); let return_url = make_merchant_url_with_response( - merchant_account, + business_profile, redirection_response, payments_return_url, response.client_secret.as_ref(), @@ -1908,11 +1908,11 @@ pub fn get_handle_response_url( ) .attach_printable("Failed to make merchant url with response")?; - make_url_with_signature(&return_url, merchant_account) + make_url_with_signature(&return_url, business_profile) } pub fn make_merchant_url_with_response( - merchant_account: &domain::MerchantAccount, + business_profile: &diesel_models::business_profile::BusinessProfile, redirection_response: api::PgRedirectResponse, request_return_url: Option<&String>, client_secret: Option<&masking::Secret>, @@ -1920,7 +1920,7 @@ pub fn make_merchant_url_with_response( ) -> RouterResult { // take return url if provided in the request else use merchant return url let url = request_return_url - .or(merchant_account.return_url.as_ref()) + .or(business_profile.return_url.as_ref()) .get_required_value("return_url")?; let status_check = redirection_response.status; @@ -1930,7 +1930,7 @@ pub fn make_merchant_url_with_response( .into_report() .attach_printable("Expected client secret to be `Some`")?; - let merchant_url_with_response = if merchant_account.redirect_to_merchant_with_http_post { + let merchant_url_with_response = if business_profile.redirect_to_merchant_with_http_post { url::Url::parse_with_params( url, &[ @@ -2024,7 +2024,7 @@ pub fn make_pg_redirect_response( pub fn make_url_with_signature( redirect_url: &str, - merchant_account: &domain::MerchantAccount, + business_profile: &diesel_models::business_profile::BusinessProfile, ) -> RouterResult { let mut url = url::Url::parse(redirect_url) .into_report() @@ -2034,8 +2034,8 @@ pub fn make_url_with_signature( let mut base_url = url.clone(); base_url.query_pairs_mut().clear(); - let url = if merchant_account.enable_payment_response_hash { - let key = merchant_account + let url = if business_profile.enable_payment_response_hash { + let key = business_profile .payment_response_hash_key .as_ref() .get_required_value("payment_response_hash_key")?; @@ -2063,7 +2063,7 @@ pub fn make_url_with_signature( return_url: base_url.to_string(), params: parameters, return_url_with_query_params: url.to_string(), - http_method: if merchant_account.redirect_to_merchant_with_http_post { + http_method: if business_profile.redirect_to_merchant_with_http_post { services::Method::Post.to_string() } else { services::Method::Get.to_string() diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 45503274a675..eb2e19081ff3 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -46,6 +46,7 @@ pub async fn payments_incoming_webhook_flow< >( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, key_store: domain::MerchantKeyStore, webhook_details: api::IncomingWebhookDetails, source_verified: bool, @@ -156,6 +157,7 @@ pub async fn payments_incoming_webhook_flow< create_event_and_trigger_outgoing_webhook::( state, merchant_account, + business_profile, outgoing_event_type, enums::EventClass::Payments, None, @@ -178,9 +180,11 @@ pub async fn payments_incoming_webhook_flow< } #[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] pub async fn refunds_incoming_webhook_flow( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, key_store: domain::MerchantKeyStore, webhook_details: api::IncomingWebhookDetails, connector_name: &str, @@ -269,6 +273,7 @@ pub async fn refunds_incoming_webhook_flow( create_event_and_trigger_outgoing_webhook::( state, merchant_account, + business_profile, outgoing_event_type, enums::EventClass::Refunds, None, @@ -404,6 +409,7 @@ pub async fn get_or_update_dispute_object( pub async fn mandates_incoming_webhook_flow( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, webhook_details: api::IncomingWebhookDetails, source_verified: bool, event_type: api_models::webhooks::IncomingWebhookEvent, @@ -455,6 +461,7 @@ pub async fn mandates_incoming_webhook_flow( create_event_and_trigger_outgoing_webhook::( state, merchant_account, + business_profile, outgoing_event_type, enums::EventClass::Mandates, None, @@ -474,10 +481,12 @@ pub async fn mandates_incoming_webhook_flow( } } +#[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub async fn disputes_incoming_webhook_flow( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, webhook_details: api::IncomingWebhookDetails, source_verified: bool, connector: &(dyn api::Connector + Sync), @@ -518,6 +527,7 @@ pub async fn disputes_incoming_webhook_flow( create_event_and_trigger_outgoing_webhook::( state, merchant_account, + business_profile, event_type, enums::EventClass::Disputes, None, @@ -541,6 +551,7 @@ pub async fn disputes_incoming_webhook_flow( async fn bank_transfer_webhook_flow( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, key_store: domain::MerchantKeyStore, webhook_details: api::IncomingWebhookDetails, source_verified: bool, @@ -594,6 +605,7 @@ async fn bank_transfer_webhook_flow( state, merchant_account, + business_profile, outgoing_event_type, enums::EventClass::Payments, None, @@ -618,6 +630,7 @@ async fn bank_transfer_webhook_flow, @@ -631,6 +644,7 @@ pub async fn create_event_and_trigger_appropriate_outgoing_webhook( create_event_and_trigger_outgoing_webhook::( state.clone(), merchant_account, + business_profile, event_type, event_class, intent_reference_id, @@ -644,6 +658,7 @@ pub async fn create_event_and_trigger_appropriate_outgoing_webhook( create_event_and_trigger_outgoing_webhook::( state.clone(), merchant_account, + business_profile, event_type, event_class, intent_reference_id, @@ -661,6 +676,7 @@ pub async fn create_event_and_trigger_appropriate_outgoing_webhook( pub async fn create_event_and_trigger_outgoing_webhook( state: AppState, merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, event_type: enums::EventType, event_class: enums::EventClass, intent_reference_id: Option, @@ -709,7 +725,7 @@ pub async fn create_event_and_trigger_outgoing_webhook(merchant_account, outgoing_webhook, &state).await; + trigger_webhook_to_merchant::(business_profile, outgoing_webhook, &state).await; if let Err(e) = result { logger::error!(?e); @@ -721,11 +737,11 @@ pub async fn create_event_and_trigger_outgoing_webhook( - merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, webhook: api::OutgoingWebhook, state: &AppState, ) -> CustomResult<(), errors::WebhooksFlowError> { - let webhook_details_json = merchant_account + let webhook_details_json = business_profile .webhook_details .get_required_value("webhook_details") .change_context(errors::WebhooksFlowError::MerchantWebhookDetailsNotFound)?; @@ -746,7 +762,7 @@ pub async fn trigger_webhook_to_merchant( let transformed_outgoing_webhook = W::from(webhook); let outgoing_webhooks_signature = transformed_outgoing_webhook - .get_outgoing_webhooks_signature(merchant_account.payment_response_hash_key.clone())?; + .get_outgoing_webhooks_signature(business_profile.payment_response_hash_key.clone())?; let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( &transformed_outgoing_webhook, @@ -782,7 +798,7 @@ pub async fn trigger_webhook_to_merchant( 1, &[metrics::KeyValue::new( MERCHANT_ID, - merchant_account.merchant_id.clone(), + business_profile.merchant_id.clone(), )], ); logger::debug!(outgoing_webhook_response=?response); @@ -799,7 +815,7 @@ pub async fn trigger_webhook_to_merchant( 1, &[metrics::KeyValue::new( MERCHANT_ID, - merchant_account.merchant_id.clone(), + business_profile.merchant_id.clone(), )], ); let update_event = storage::EventUpdate::UpdateWebhookNotified { @@ -816,7 +832,7 @@ pub async fn trigger_webhook_to_merchant( 1, &[metrics::KeyValue::new( MERCHANT_ID, - merchant_account.merchant_id.clone(), + business_profile.merchant_id.clone(), )], ); // [#217]: Schedule webhook for retry. @@ -1048,10 +1064,26 @@ pub async fn webhooks_core payments_incoming_webhook_flow::( state.clone(), merchant_account, + business_profile, key_store, webhook_details, source_verified, @@ -1062,6 +1094,7 @@ pub async fn webhooks_core refunds_incoming_webhook_flow::( state.clone(), merchant_account, + business_profile, key_store, webhook_details, connector_name.as_str(), @@ -1074,6 +1107,7 @@ pub async fn webhooks_core disputes_incoming_webhook_flow::( state.clone(), merchant_account, + business_profile, webhook_details, source_verified, *connector, @@ -1086,6 +1120,7 @@ pub async fn webhooks_core bank_transfer_webhook_flow::( state.clone(), merchant_account, + business_profile, key_store, webhook_details, source_verified, @@ -1098,6 +1133,7 @@ pub async fn webhooks_core mandates_incoming_webhook_flow::( state.clone(), merchant_account, + business_profile, webhook_details, source_verified, event_type, diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 0f5fbeb46553..84a75d397e31 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -699,6 +699,7 @@ impl ForeignTryFrom for enums::EventType { pub async fn trigger_payments_webhook( merchant_account: domain::MerchantAccount, + business_profile: diesel_models::business_profile::BusinessProfile, payment_data: crate::core::payments::PaymentData, req: Option, customer: Option, @@ -753,6 +754,7 @@ where webhooks_core::create_event_and_trigger_appropriate_outgoing_webhook( state.clone(), merchant_account, + business_profile, event_type, diesel_models::enums::EventClass::Payments, None, diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 1e77089e0a63..540f2d68dd61 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -149,10 +149,28 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let profile_id = payment_data + .payment_intent + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not find profile_id in payment intent")?; + + let business_profile = db + .find_business_profile_by_profile_id(profile_id) + .await + .to_not_found_response( + errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.to_string(), + }, + )?; + // Trigger the outgoing webhook to notify the merchant about failed payment let operation = operations::PaymentStatus; utils::trigger_payments_webhook::<_, api_models::payments::PaymentsRequest, _>( merchant_account, + business_profile, payment_data, None, customer, From ca77c7ce3c6b806ff60e83725cc4e50855422037 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:42:23 +0530 Subject: [PATCH 22/38] ci(postman): Add Volt Postman collection JSON file (#2739) --- .../volt.postman_collection.json | 2319 +++++++++++++++++ 1 file changed, 2319 insertions(+) create mode 100644 postman/collection-json/volt.postman_collection.json diff --git a/postman/collection-json/volt.postman_collection.json b/postman/collection-json/volt.postman_collection.json new file mode 100644 index 000000000000..78d4e9d6ea41 --- /dev/null +++ b/postman/collection-json/volt.postman_collection.json @@ -0,0 +1,2319 @@ +{ + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"[LOG]::payment_id - \" + jsonData.payment_id);", + "}", + "", + "console.log(\"[LOG]::x-request-id - \" + pm.response.headers.get(\"x-request-id\"));", + "" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "name": "Health check", + "item": [ + { + "name": "New Request", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/accounts - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "x-feature", + "value": "router-custom", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{baseUrl}}/health", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Flow Testcases", + "item": [ + { + "name": "QuickStart", + "item": [ + { + "name": "Merchant Account - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/accounts - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/accounts - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set merchant_id as variable for jsonData.merchant_id", + "if (jsonData?.merchant_id) {", + " pm.collectionVariables.set(\"merchant_id\", jsonData.merchant_id);", + " console.log(", + " \"- use {{merchant_id}} as collection variable for value\",", + " jsonData.merchant_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{merchant_id}}, as jsonData.merchant_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set api_key as variable for jsonData.api_key", + "if (jsonData?.api_key) {", + " pm.collectionVariables.set(\"api_key\", jsonData.api_key);", + " console.log(", + " \"- use {{api_key}} as collection variable for value\",", + " jsonData.api_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set publishable_key as variable for jsonData.publishable_key", + "if (jsonData?.publishable_key) {", + " pm.collectionVariables.set(\"publishable_key\", jsonData.publishable_key);", + " console.log(", + " \"- use {{publishable_key}} as collection variable for value\",", + " jsonData.publishable_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{publishable_key}}, as jsonData.publishable_key is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"merchant_id\":\"postman_merchant_GHAction_{{$guid}}\",\"locker_id\":\"m0010\",\"merchant_name\":\"NewAge Retailer\",\"merchant_details\":{\"primary_contact_person\":\"John Test\",\"primary_email\":\"JohnTest@test.com\",\"primary_phone\":\"sunt laborum\",\"secondary_contact_person\":\"John Test2\",\"secondary_email\":\"JohnTest2@test.com\",\"secondary_phone\":\"cillum do dolor id\",\"website\":\"www.example.com\",\"about_business\":\"Online Retail with a wide selection of organic products for North America\",\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\"}},\"return_url\":\"https://duck.com\",\"webhook_details\":{\"webhook_version\":\"1.0.1\",\"webhook_username\":\"ekart_retail\",\"webhook_password\":\"password_ekart@123\",\"payment_created_enabled\":true,\"payment_succeeded_enabled\":true,\"payment_failed_enabled\":true},\"sub_merchants_enabled\":false,\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"},\"primary_business_details\":[{\"country\":\"US\",\"business\":\"default\"}]}" + }, + "url": { + "raw": "{{baseUrl}}/accounts", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "accounts" + ] + }, + "description": "Create a new account for a merchant. The merchant could be a seller or retailer or client who likes to receive and send payments." + }, + "response": [] + }, + { + "name": "API Key - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/api_keys/:merchant_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/api_keys/:merchant_id - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id", + "if (jsonData?.key_id) {", + " pm.collectionVariables.set(\"api_key_id\", jsonData.key_id);", + " console.log(", + " \"- use {{api_key_id}} as collection variable for value\",", + " jsonData.key_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set api_key as variable for jsonData.api_key", + "if (jsonData?.api_key) {", + " pm.collectionVariables.set(\"api_key\", jsonData.api_key);", + " console.log(", + " \"- use {{api_key}} as collection variable for value\",", + " jsonData.api_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\":\"API Key 1\",\"description\":null,\"expiration\":\"2099-09-23T01:02:03.000Z\"}" + }, + "url": { + "raw": "{{baseUrl}}/api_keys/:merchant_id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api_keys", + ":merchant_id" + ], + "variable": [ + { + "key": "merchant_id", + "value": "{{merchant_id}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Payment Connector - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(", + " \"[POST]::/account/:account_id/connectors - Status code is 2xx\",", + " function () {", + " pm.response.to.be.success;", + " },", + ");", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/account/:account_id/connectors - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id", + "if (jsonData?.merchant_connector_id) {", + " pm.collectionVariables.set(", + " \"merchant_connector_id\",", + " jsonData.merchant_connector_id,", + " );", + " console.log(", + " \"- use {{merchant_connector_id}} as collection variable for value\",", + " jsonData.merchant_connector_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"volt\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"MultiAuthKey\",\"api_key\":\"{{connector_api_key}}\",\"api_secret\":\"{{connector_api_secret}}\",\"key1\":\"{{connector_key1}}\",\"key2\":\"{{connector_key2}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"open_banking_uk\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}]}" + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors" + ], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}", + "description": "(Required) The unique identifier for the merchant account" + } + ] + }, + "description": "Create a new Payment Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialised services like Fraud / Accounting etc." + }, + "response": [] + }, + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"EUR\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://google.com\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"open_banking_uk\",\"payment_method_data\":{\"bank_redirect\":{\"open_banking_uk\":{\"issuer\":\"citi\",\"country\":\"GB\"}}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"volt\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Happy Cases", + "item": [ + { + "name": "Scenario1-Create payment with confirm true", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"EUR\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://google.com\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"open_banking_uk\",\"payment_method_data\":{\"bank_redirect\":{\"open_banking_uk\":{\"issuer\":\"citi\",\"country\":\"GB\"}}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"volt\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario2-Create payment with confirm false", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_confirmation\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://google.com\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"open_banking_uk\",\"payment_method_data\":{\"bank_redirect\":{\"open_banking_uk\":{\"issuer\":\"citi\",\"country\":\"GB\"}}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"volt\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount_capturable\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 6540'\",", + " function () {", + " pm.expect(jsonData.amount_capturable).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"client_secret\":\"{{client_secret}}\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount_capturable\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 6540'\",", + " function () {", + " pm.expect(jsonData.amount_capturable).to.eql(6540);", + " },", + " );", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario3-Create payment without PMD", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"singh\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"volt\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"open_banking_uk\",\"payment_method_data\":{\"bank_redirect\":{\"open_banking_uk\":{\"issuer\":\"citi\",\"country\":\"GB\"}}},\"client_secret\":\"{{client_secret}}\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario4-Bank Redirect-open_banking_uk", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"singh\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"volt\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "", + "// Response body should have \"next_action.redirect_to_url\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", + " function () {", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"open_banking_uk\" for \"payment_method_type\"", + "if (jsonData?.payment_method_type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'open_banking_uk'\",", + " function () {", + " pm.expect(jsonData.payment_method_type).to.eql(\"open_banking_uk\");", + " },", + " );", + "}", + "", + "// Response body should have value \"volt\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'volt'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"volt\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"open_banking_uk\",\"payment_method_data\":{\"bank_redirect\":{\"open_banking_uk\":{\"issuer\":\"citi\",\"country\":\"GB\"}}},\"client_secret\":\"{{client_secret}}\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + } + ] + } + ] + } + ], + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "info": { + "_postman_id": "ac5a089e-b4a3-43b2-8938-b2e44056e455", + "name": "volt", + "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "27008363" + }, + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + }, + { + "key": "admin_api_key", + "value": "", + "type": "string" + }, + { + "key": "api_key", + "value": "", + "type": "string" + }, + { + "key": "merchant_id", + "value": "" + }, + { + "key": "payment_id", + "value": "" + }, + { + "key": "customer_id", + "value": "" + }, + { + "key": "mandate_id", + "value": "" + }, + { + "key": "payment_method_id", + "value": "" + }, + { + "key": "refund_id", + "value": "" + }, + { + "key": "merchant_connector_id", + "value": "" + }, + { + "key": "client_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_api_key", + "value": "", + "type": "string" + }, + { + "key": "connector_api_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_key1", + "value": "", + "type": "string" + }, + { + "key": "connector_key2", + "value": "", + "type": "string" + }, + { + "key": "publishable_key", + "value": "", + "type": "string" + }, + { + "key": "api_key_id", + "value": "", + "type": "string" + }, + { + "key": "payment_token", + "value": "" + }, + { + "key": "gateway_merchant_id", + "value": "", + "type": "string" + }, + { + "key": "certificate", + "value": "", + "type": "string" + }, + { + "key": "certificate_keys", + "value": "", + "type": "string" + } + ] +} From 94947bdb33ca4eb91daad13b2a427592d3b69851 Mon Sep 17 00:00:00 2001 From: Rutam Prita Mishra Date: Tue, 31 Oct 2023 15:20:47 +0530 Subject: [PATCH 23/38] refactor(connector): [Payme] Remove Default Case Handling (#2719) --- crates/router/src/connector/payme/transformers.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 4ced1a9bcda3..1b7ce27439b3 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -651,7 +651,20 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest { language: LANGUAGE.to_string(), }) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + api::PaymentMethodData::CardRedirect(_) + | api::PaymentMethodData::Wallet(_) + | api::PaymentMethodData::PayLater(_) + | api::PaymentMethodData::BankRedirect(_) + | api::PaymentMethodData::BankDebit(_) + | api::PaymentMethodData::BankTransfer(_) + | api::PaymentMethodData::Crypto(_) + | api::PaymentMethodData::MandatePayment + | api::PaymentMethodData::Reward + | api::PaymentMethodData::Upi(_) + | api::PaymentMethodData::Voucher(_) + | api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("payme"), + ))?, } } } From aad3f0f6fafdb08f1c5f1feb2588d6d0fb9162ff Mon Sep 17 00:00:00 2001 From: Sahal Saad Date: Tue, 31 Oct 2023 18:28:15 +0800 Subject: [PATCH 24/38] feat(connector): [NMI] add orderid to PaymentRequest (#2727) --- crates/router/src/connector/nmi/transformers.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 3f64ff9eaca2..582bb9f73675 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -49,6 +49,7 @@ pub struct NmiPaymentsRequest { currency: enums::Currency, #[serde(flatten)] payment_method: PaymentMethod, + orderid: String, } #[derive(Debug, Serialize)] @@ -94,6 +95,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NmiPaymentsRequest { amount, currency: item.request.currency, payment_method, + orderid: item.connector_request_reference_id.clone(), }) } } @@ -206,6 +208,7 @@ impl TryFrom<&types::SetupMandateRouterData> for NmiPaymentsRequest { amount: 0.0, currency: item.request.currency, payment_method, + orderid: item.connector_request_reference_id.clone(), }) } } From a261f1a2fce84354b3741429b629928d1bd06aab Mon Sep 17 00:00:00 2001 From: Kumar Harshwardhan <113055707+Harshwardhan9431@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:33:50 +0530 Subject: [PATCH 25/38] feat(connector): Worldline Use `connector_response_reference_id` as reference to merchant (#2721) --- crates/router/src/connector/worldline/transformers.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs index 80378215a45c..6cb8862f69b1 100644 --- a/crates/router/src/connector/worldline/transformers.rs +++ b/crates/router/src/connector/worldline/transformers.rs @@ -588,12 +588,12 @@ impl TryFrom TryFrom Date: Tue, 31 Oct 2023 16:49:52 +0530 Subject: [PATCH 26/38] feat(connector): [Authorizedotnet] Use connector_request_reference_id as reference to the connector (#2593) Co-authored-by: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> --- crates/router/src/connector/authorizedotnet/transformers.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 20d78729a1be..561723be46cf 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -261,6 +261,7 @@ struct TransactionVoidOrCaptureRequest { pub struct AuthorizedotnetPaymentsRequest { merchant_authentication: AuthorizedotnetAuthType, transaction_request: TransactionRequest, + ref_id: String, } #[derive(Debug, Serialize)] @@ -332,6 +333,7 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsAuthorizeRouterData>> create_transaction_request: AuthorizedotnetPaymentsRequest { merchant_authentication, transaction_request, + ref_id: item.router_data.connector_request_reference_id.clone(), }, }) } From 42b13f737a53143057ab23867f32017ea8c17780 Mon Sep 17 00:00:00 2001 From: Shivam Jain <90458496+shivam-jainn@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:58:20 +0530 Subject: [PATCH 27/38] feat(connector): [Multisafepay] Currency Unit Conversion (#2679) --- crates/router/src/connector/multisafepay.rs | 23 +++++- .../connector/multisafepay/transformers.rs | 81 ++++++++++++++----- 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 120ea23d7ca6..1ea5029fd003 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -45,6 +45,10 @@ impl ConnectorCommon for Multisafepay { "multisafepay" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -257,7 +261,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = multisafepay::MultisafepayPaymentsRequest::try_from(req)?; + let connector_router_data = multisafepay::MultisafepayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = multisafepay::MultisafepayPaymentsRequest::try_from(&connector_router_data)?; let multisafepay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -351,9 +361,16 @@ impl ConnectorIntegration, ) -> CustomResult, errors::ConnectorError> { - let connector_req = multisafepay::MultisafepayRefundRequest::try_from(req)?; + let connector_req = multisafepay::MultisafepayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = multisafepay::MultisafepayRefundRequest::try_from(&connector_req)?; + let multisafepay_req = types::RequestBody::log_and_get_request_body( - &connector_req, + &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/router/src/connector/multisafepay/transformers.rs index 385c85e0aa61..2fa53135be53 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/router/src/connector/multisafepay/transformers.rs @@ -13,6 +13,37 @@ use crate::{ types::{self, api, storage::enums}, }; +#[derive(Debug, Serialize)] +pub struct MultisafepayRouterData { + amount: i64, + router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for MultisafepayRouterData +{ + type Error = error_stack::Report; + + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount, + router_data: item, + }) + } +} + #[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum Type { @@ -240,10 +271,14 @@ impl TryFrom for Gateway { } } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsRequest { +impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> + for MultisafepayPaymentsRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - let payment_type = match item.request.payment_method_data { + fn try_from( + item: &MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let payment_type = match item.router_data.request.payment_method_data { api::PaymentMethodData::Card(ref _ccard) => Type::Direct, api::PaymentMethodData::MandatePayment => Type::Direct, api::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { @@ -280,7 +315,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques _ => Type::Redirect, }; - let gateway = match item.request.payment_method_data { + let gateway = match item.router_data.request.payment_method_data { api::PaymentMethodData::Card(ref ccard) => { Some(Gateway::try_from(ccard.get_card_issuer()?)?) } @@ -334,11 +369,11 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques utils::get_unimplemented_payment_method_error_message("multisafepay"), ))?, }; - let description = item.get_description()?; + let description = item.router_data.get_description()?; let payment_options = PaymentOptions { notification_url: None, - redirect_url: item.request.get_router_return_url()?, - cancel_url: item.request.get_router_return_url()?, + redirect_url: item.router_data.request.get_router_return_url()?, + cancel_url: item.router_data.request.get_router_return_url()?, close_window: None, notification_method: None, settings: None, @@ -363,13 +398,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques state: None, country: None, phone: None, - email: item.request.email.clone(), + email: item.router_data.request.email.clone(), user_agent: None, referrer: None, - reference: Some(item.connector_request_reference_id.clone()), + reference: Some(item.router_data.connector_request_reference_id.clone()), }; let billing_address = item + .router_data .get_billing()? .address .as_ref() @@ -384,7 +420,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques country: billing_address.get_country()?.to_owned(), }; - let gateway_info = match item.request.payment_method_data { + let gateway_info = match item.router_data.request.payment_method_data { api::PaymentMethodData::Card(ref ccard) => Some(GatewayInfo::Card(CardInfo { card_number: Some(ccard.card_number.clone()), card_expiry_date: Some( @@ -481,9 +517,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques Ok(Self { payment_type, gateway, - order_id: item.connector_request_reference_id.to_string(), - currency: item.request.currency.to_string(), - amount: item.request.amount, + order_id: item.router_data.connector_request_reference_id.to_string(), + currency: item.router_data.request.currency.to_string(), + amount: item.amount, description, payment_options: Some(payment_options), customer: Some(customer), @@ -493,12 +529,13 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques shopping_cart: None, capture: None, items: None, - recurring_model: if item.request.is_mandate_payment() { + recurring_model: if item.router_data.request.is_mandate_payment() { Some(MandateType::Unscheduled) } else { None }, recurring_id: item + .router_data .request .mandate_id .clone() @@ -664,14 +701,18 @@ pub struct MultisafepayRefundRequest { pub checkout_data: Option, } -impl TryFrom<&types::RefundsRouterData> for MultisafepayRefundRequest { +impl TryFrom<&MultisafepayRouterData<&types::RefundsRouterData>> + for MultisafepayRefundRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: &MultisafepayRouterData<&types::RefundsRouterData>, + ) -> Result { Ok(Self { - currency: item.request.currency, - amount: item.request.refund_amount, - description: item.description.clone(), - refund_order_id: Some(item.request.refund_id.clone()), + currency: item.router_data.request.currency, + amount: item.amount, + description: item.router_data.description.clone(), + refund_order_id: Some(item.router_data.request.refund_id.clone()), checkout_data: None, }) } From ceed76fb2e67771048e563a13703eb801eeaae08 Mon Sep 17 00:00:00 2001 From: HeetVekariya <91054457+HeetVekariya@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:01:06 +0530 Subject: [PATCH 28/38] refactor(connector): [Payeezy] remove default case handling (#2712) Co-authored-by: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> Co-authored-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com> --- .../src/connector/payeezy/transformers.rs | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/router/src/connector/payeezy/transformers.rs index 98e8ea12c00d..efcd1b36d5bb 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/router/src/connector/payeezy/transformers.rs @@ -37,11 +37,14 @@ impl TryFrom for PayeezyCardType { utils::CardIssuer::Master => Ok(Self::Mastercard), utils::CardIssuer::Discover => Ok(Self::Discover), utils::CardIssuer::Visa => Ok(Self::Visa), - _ => Err(errors::ConnectorError::NotSupported { - message: issuer.to_string(), - connector: "Payeezy", + + utils::CardIssuer::Maestro | utils::CardIssuer::DinersClub | utils::CardIssuer::JCB => { + Err(errors::ConnectorError::NotSupported { + message: utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Payeezy", + } + .into()) } - .into()), } } } @@ -97,7 +100,20 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayeezyPaymentsRequest { fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { match item.payment_method { diesel_models::enums::PaymentMethod::Card => get_card_specific_payment_data(item), - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + + diesel_models::enums::PaymentMethod::CardRedirect + | diesel_models::enums::PaymentMethod::PayLater + | diesel_models::enums::PaymentMethod::Wallet + | diesel_models::enums::PaymentMethod::BankRedirect + | diesel_models::enums::PaymentMethod::BankTransfer + | diesel_models::enums::PaymentMethod::Crypto + | diesel_models::enums::PaymentMethod::BankDebit + | diesel_models::enums::PaymentMethod::Reward + | diesel_models::enums::PaymentMethod::Upi + | diesel_models::enums::PaymentMethod::Voucher + | diesel_models::enums::PaymentMethod::GiftCard => { + Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) + } } } } @@ -165,7 +181,10 @@ fn get_transaction_type_and_stored_creds( Some(diesel_models::enums::CaptureMethod::Automatic) => { Ok((PayeezyTransactionType::Purchase, None)) } - _ => Err(errors::ConnectorError::FlowNotSupported { + + Some(diesel_models::enums::CaptureMethod::ManualMultiple) + | Some(diesel_models::enums::CaptureMethod::Scheduled) + | None => Err(errors::ConnectorError::FlowNotSupported { flow: item.request.capture_method.unwrap_or_default().to_string(), connector: "Payeezy".to_string(), }), @@ -196,7 +215,23 @@ fn get_payment_method_data( }; Ok(PayeezyPaymentMethod::PayeezyCard(payeezy_card)) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + + api::PaymentMethodData::CardRedirect(_) + | api::PaymentMethodData::Wallet(_) + | api::PaymentMethodData::PayLater(_) + | api::PaymentMethodData::BankRedirect(_) + | api::PaymentMethodData::BankDebit(_) + | api::PaymentMethodData::BankTransfer(_) + | api::PaymentMethodData::Crypto(_) + | api::PaymentMethodData::MandatePayment + | api::PaymentMethodData::Reward + | api::PaymentMethodData::Upi(_) + | api::PaymentMethodData::Voucher(_) + | api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotSupported { + message: utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "Payeezy", + } + .into()), } } @@ -383,7 +418,7 @@ impl ForeignFrom<(PayeezyPaymentStatus, PayeezyTransactionType)> for enums::Atte | PayeezyTransactionType::Purchase | PayeezyTransactionType::Recurring => Self::Charged, PayeezyTransactionType::Void => Self::Voided, - _ => Self::Pending, + PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => Self::Pending, }, PayeezyPaymentStatus::Declined | PayeezyPaymentStatus::NotProcessed => match method { PayeezyTransactionType::Capture => Self::CaptureFailed, @@ -391,7 +426,7 @@ impl ForeignFrom<(PayeezyPaymentStatus, PayeezyTransactionType)> for enums::Atte | PayeezyTransactionType::Purchase | PayeezyTransactionType::Recurring => Self::AuthorizationFailed, PayeezyTransactionType::Void => Self::VoidFailed, - _ => Self::Pending, + PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => Self::Pending, }, } } From 0f5406c620e9cdd20841898e9451a35f434f5b8a Mon Sep 17 00:00:00 2001 From: SomeYoGuy <59766344+SoumyoPurkayastha@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:05:56 +0530 Subject: [PATCH 29/38] feat(connector): [Iatapay] currency unit conversion (#2592) Co-authored-by: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> --- crates/router/src/connector/iatapay.rs | 20 +++- .../src/connector/iatapay/transformers.rs | 96 ++++++++++++++----- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index 3a813c50cf6b..4db2faa2d422 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -90,6 +90,10 @@ impl ConnectorCommon for Iatapay { "application/json" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { connectors.iatapay.base_url.as_ref() } @@ -271,7 +275,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = iatapay::IatapayPaymentsRequest::try_from(req)?; + let connector_router_data = iatapay::IatapayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = iatapay::IatapayPaymentsRequest::try_from(&connector_router_data)?; let iatapay_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, @@ -451,7 +461,13 @@ impl ConnectorIntegration, ) -> CustomResult, errors::ConnectorError> { - let req_obj = iatapay::IatapayRefundRequest::try_from(req)?; + let connector_router_data = iatapay::IatapayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.payment_amount, + req, + ))?; + let req_obj = iatapay::IatapayRefundRequest::try_from(&connector_router_data)?; let iatapay_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index d4731b024c86..7cdfafc858b6 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -8,7 +8,7 @@ use crate::{ connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData}, core::errors, services, - types::{self, api, storage::enums}, + types::{self, api, storage::enums, PaymentsAuthorizeData}, }; // Every access token will be valid for 5 minutes. It contains grant_type and scope for different type of access, but for our usecases it should be only 'client_credentials' and 'payment' resp(as per doc) for all type of api call. @@ -26,7 +26,34 @@ impl TryFrom<&types::RefreshTokenRouterData> for IatapayAuthUpdateRequest { }) } } - +#[derive(Debug, Serialize)] +pub struct IatapayRouterData { + amount: f64, + router_data: T, +} +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for IatapayRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, _amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount: utils::to_currency_base_unit_asf64(_amount, _currency)?, + router_data: item, + }) + } +} #[derive(Debug, Deserialize)] pub struct IatapayAuthUpdateResponse { pub access_token: Secret, @@ -80,10 +107,29 @@ pub struct IatapayPaymentsRequest { payer_info: Option, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest { +impl + TryFrom< + &IatapayRouterData< + &types::RouterData< + types::api::payments::Authorize, + PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + >, + > for IatapayPaymentsRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - let payment_method = item.payment_method; + + fn try_from( + item: &IatapayRouterData< + &types::RouterData< + types::api::payments::Authorize, + PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + >, + ) -> Result { + let payment_method = item.router_data.payment_method; let country = match payment_method { PaymentMethod::Upi => "IN".to_string(), @@ -97,27 +143,26 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest { | PaymentMethod::BankDebit | PaymentMethod::Reward | PaymentMethod::Voucher - | PaymentMethod::GiftCard => item.get_billing_country()?.to_string(), + | PaymentMethod::GiftCard => item.router_data.get_billing_country()?.to_string(), }; - let return_url = item.get_return_url()?; - let payer_info = match item.request.payment_method_data.clone() { + let return_url = item.router_data.get_return_url()?; + let payer_info = match item.router_data.request.payment_method_data.clone() { api::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo { token_id: id.switch_strategy(), }), _ => None, }; - let amount = - utils::to_currency_base_unit_asf64(item.request.amount, item.request.currency)?; let payload = Self { - merchant_id: IatapayAuthType::try_from(&item.connector_auth_type)?.merchant_id, - merchant_payment_id: Some(item.connector_request_reference_id.clone()), - amount, - currency: item.request.currency.to_string(), + merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)? + .merchant_id, + merchant_payment_id: Some(item.router_data.connector_request_reference_id.clone()), + amount: item.amount, + currency: item.router_data.request.currency.to_string(), country: country.clone(), locale: format!("en-{}", country), redirect_urls: get_redirect_url(return_url), payer_info, - notification_url: item.request.get_webhook_url()?, + notification_url: item.router_data.request.get_webhook_url()?, }; Ok(payload) } @@ -275,18 +320,19 @@ pub struct IatapayRefundRequest { pub notification_url: String, } -impl TryFrom<&types::RefundsRouterData> for IatapayRefundRequest { +impl TryFrom<&IatapayRouterData<&types::RefundsRouterData>> for IatapayRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { - let amount = - utils::to_currency_base_unit_asf64(item.request.refund_amount, item.request.currency)?; + fn try_from( + item: &IatapayRouterData<&types::RefundsRouterData>, + ) -> Result { Ok(Self { - amount, - merchant_id: IatapayAuthType::try_from(&item.connector_auth_type)?.merchant_id, - merchant_refund_id: Some(item.request.refund_id.clone()), - currency: item.request.currency.to_string(), - bank_transfer_description: item.request.reason.clone(), - notification_url: item.request.get_webhook_url()?, + amount: item.amount, + merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)? + .merchant_id, + merchant_refund_id: Some(item.router_data.request.refund_id.clone()), + currency: item.router_data.request.currency.to_string(), + bank_transfer_description: item.router_data.request.reason.clone(), + notification_url: item.router_data.request.get_webhook_url()?, }) } } From 15a6b5a855def5650e16b96e6529ad7fa0845e6b Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:08:46 +0530 Subject: [PATCH 30/38] feat: implement list_merchant_connector_accounts_by_merchant_id_connector_name function (#2742) --- .../src/query/merchant_connector_account.rs | 9 +- .../src/db/merchant_connector_account.rs | 100 ++++++------------ 2 files changed, 33 insertions(+), 76 deletions(-) diff --git a/crates/diesel_models/src/query/merchant_connector_account.rs b/crates/diesel_models/src/query/merchant_connector_account.rs index 17e34b2a866d..e9ef4eabff0e 100644 --- a/crates/diesel_models/src/query/merchant_connector_account.rs +++ b/crates/diesel_models/src/query/merchant_connector_account.rs @@ -130,17 +130,12 @@ impl MerchantConnectorAccount { get_disabled: bool, ) -> StorageResult> { if get_disabled { - generics::generic_filter::< - ::Table, - _, - <::Table as Table>::PrimaryKey, - _, - >( + generics::generic_filter::<::Table, _, _, _>( conn, dsl::merchant_id.eq(merchant_id.to_owned()), None, None, - None, + Some(dsl::created_at.asc()), ) .await } else { diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 491e5b788103..9ff3f5121082 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1,7 +1,4 @@ -use std::cmp::Ordering; - use common_utils::ext_traits::{AsyncExt, ByteSliceExt, Encode}; -use diesel_models::errors as storage_errors; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "accounts_cache")] use storage_impl::redis::cache; @@ -130,7 +127,7 @@ where merchant_id: &str, connector_name: &str, key_store: &domain::MerchantKeyStore, - ) -> CustomResult; + ) -> CustomResult, errors::StorageError>; async fn insert_merchant_connector_account( &self, @@ -263,46 +260,28 @@ impl MerchantConnectorAccountInterface for Store { merchant_id: &str, connector_name: &str, key_store: &domain::MerchantKeyStore, - ) -> CustomResult { - let find_call = || async { - let conn = connection::pg_connection_read(self).await?; - storage::MerchantConnectorAccount::find_by_merchant_id_connector_name( - &conn, - merchant_id, - connector_name, - ) - .await - .map_err(Into::into) - .into_report() - }; - let mca_list = find_call().await?; - match mca_list.len().cmp(&1) { - Ordering::Less => { - Err(errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into()) - .attach_printable(format!( - "No records found for {} and {}", - merchant_id, connector_name - )) + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + storage::MerchantConnectorAccount::find_by_merchant_id_connector_name( + &conn, + merchant_id, + connector_name, + ) + .await + .map_err(Into::into) + .into_report() + .async_and_then(|items| async { + let mut output = Vec::with_capacity(items.len()); + for item in items.into_iter() { + output.push( + item.convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DecryptionError)?, + ) } - Ordering::Greater => Err(errors::StorageError::DatabaseError( - storage_errors::DatabaseError::Others.into(), - )) - .into_report() - .attach_printable(format!( - "Found multiple records for {} and {}", - merchant_id, connector_name - )), - Ordering::Equal => match mca_list.first() { - Some(mca) => mca - .to_owned() - .convert(key_store.key.get_inner()) - .await - .change_context(errors::StorageError::DeserializationFailed), - None => Err( - errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into(), - ), - }, - } + Ok(output) + }) + .await } async fn find_by_merchant_connector_account_merchant_id_merchant_connector_id( @@ -514,8 +493,8 @@ impl MerchantConnectorAccountInterface for MockDb { merchant_id: &str, connector_name: &str, key_store: &domain::MerchantKeyStore, - ) -> CustomResult { - let mca_list = self + ) -> CustomResult, errors::StorageError> { + let accounts = self .merchant_connector_accounts .lock() .await @@ -525,33 +504,16 @@ impl MerchantConnectorAccountInterface for MockDb { }) .cloned() .collect::>(); - match mca_list.len().cmp(&1) { - Ordering::Less => { - Err(errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into()) - .attach_printable(format!( - "No records found for {} and {}", - merchant_id, connector_name - )) - } - Ordering::Greater => Err(errors::StorageError::DatabaseError( - storage_errors::DatabaseError::Others.into(), - )) - .into_report() - .attach_printable(format!( - "Found multiple records for {} and {}", - merchant_id, connector_name - )), - Ordering::Equal => match mca_list.first() { - Some(mca) => mca - .to_owned() + let mut output = Vec::with_capacity(accounts.len()); + for account in accounts.into_iter() { + output.push( + account .convert(key_store.key.get_inner()) .await - .change_context(errors::StorageError::DeserializationFailed), - None => Err( - errors::StorageError::ValueNotFound("MerchantConnectorAccount".into()).into(), - ), - }, + .change_context(errors::StorageError::DecryptionError)?, + ) } + Ok(output) } async fn find_merchant_connector_account_by_profile_id_connector_name( From 0a44f5699ed7b0c0ea0352b67c65df496ebe61f3 Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:37:04 +0530 Subject: [PATCH 31/38] fix(connector): [Stripe] add decline_code in error_reason (#2735) --- crates/router/src/connector/stripe.rs | 130 ++++++++++++++++-- .../src/connector/stripe/transformers.rs | 13 +- .../.meta.json | 3 + .../Payments - Create/.event.meta.json | 3 + .../Payments - Create/event.test.js | 100 ++++++++++++++ .../Payments - Create/request.json | 94 +++++++++++++ .../Payments - Create/response.json | 1 + .../Payments - Retrieve/.event.meta.json | 3 + .../Payments - Retrieve/event.test.js | 100 ++++++++++++++ .../Payments - Retrieve/request.json | 28 ++++ .../Payments - Retrieve/response.json | 1 + 11 files changed, 462 insertions(+), 14 deletions(-) create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json create mode 100644 postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 59700ecf353a..e3551306e673 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -216,7 +216,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -334,7 +342,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -448,7 +464,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -570,7 +594,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -702,7 +734,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -848,7 +888,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -959,7 +1007,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -1105,7 +1161,15 @@ impl .error .code .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), - reason: response.error.message, + reason: response.error.message.map(|message| { + response + .error + .decline_code + .map(|decline_code| { + format!("message - {}, decline_code - {}", message, decline_code) + }) + .unwrap_or(message) + }), }) } } @@ -1214,7 +1278,15 @@ impl services::ConnectorIntegration, } #[derive(Deserialize, Debug)] @@ -2463,7 +2464,16 @@ impl .map(|error| types::ErrorResponse { code: error.code.to_owned(), message: error.code.to_owned(), - reason: Some(error.message.to_owned()), + reason: error + .decline_code + .clone() + .map(|decline_code| { + format!( + "message - {}, decline_code - {}", + error.message, decline_code + ) + }) + .or(Some(error.message.clone())), status_code: item.http_code, }); @@ -2772,6 +2782,7 @@ pub struct ErrorDetails { pub error_type: Option, pub message: Option, pub param: Option, + pub decline_code: Option, } #[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json new file mode 100644 index 000000000000..69b505c6d863 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json @@ -0,0 +1,3 @@ +{ + "childrenOrder": ["Payments - Create", "Payments - Retrieve"] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js new file mode 100644 index 000000000000..2be75ec0088b --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js @@ -0,0 +1,100 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'failed'", + function () { + pm.expect(jsonData.status).to.eql("failed"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); + +// Response body should have value "card_declined" for "error_code" +if (jsonData?.error_code) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'", + function () { + pm.expect(jsonData.error_code).to.eql("card_declined"); + }, + ); +} + +// Response body should have value "message - Your card has insufficient funds., decline_code - insufficient_funds" for "error_message" +if (jsonData?.error_message) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'", + function () { + pm.expect(jsonData.error_message).to.eql("message - Your card has insufficient funds., decline_code - insufficient_funds"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json new file mode 100644 index 000000000000..150139b8e104 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json @@ -0,0 +1,94 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "business_country": "US", + "business_label": "default", + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 1, + "customer_id": "bernard123", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+65", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://duck.com", + "setup_future_usage": "on_session", + "payment_method": "card", + "payment_method_type": "debit", + "payment_method_data": { + "card": { + "card_number": "4000000000009995", + "card_exp_month": "01", + "card_exp_year": "24", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "sundari" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "sundari" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "stripe" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js new file mode 100644 index 000000000000..f8825a335742 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js @@ -0,0 +1,100 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "Succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id - Content check if value for 'status' matches 'failed'", + function () { + pm.expect(jsonData.status).to.eql("failed"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); + +// Response body should have value "card_declined" for "error_code" +if (jsonData?.error_code) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'", + function () { + pm.expect(jsonData.error_code).to.eql("card_declined"); + }, + ); +} + +// Response body should have value "message - Your card has insufficient funds., decline_code - insufficient_funds" for "error_message" +if (jsonData?.error_message) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'", + function () { + pm.expect(jsonData.error_message).to.eql("message - Your card has insufficient funds., decline_code - insufficient_funds"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json new file mode 100644 index 000000000000..6cd4b7d96c52 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json @@ -0,0 +1,28 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": ["{{baseUrl}}"], + "path": ["payments", ":id"], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] From e377279d9cc872238fcfd8de324b44b0249b95c2 Mon Sep 17 00:00:00 2001 From: Adarsh Jha <132337675+adarsh-jha-dev@users.noreply.github.com> Date: Tue, 31 Oct 2023 19:28:58 +0530 Subject: [PATCH 32/38] feat(connector): [BitPay] Currency Unit Conversion (#2736) --- crates/router/src/connector/bitpay.rs | 12 +++- .../src/connector/bitpay/transformers.rs | 57 +++++++++++++++---- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/crates/router/src/connector/bitpay.rs b/crates/router/src/connector/bitpay.rs index 2dc634426f3e..e8826e933905 100644 --- a/crates/router/src/connector/bitpay.rs +++ b/crates/router/src/connector/bitpay.rs @@ -82,6 +82,10 @@ impl ConnectorCommon for Bitpay { "bitpay" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -169,7 +173,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = bitpay::BitpayPaymentsRequest::try_from(req)?; + let connector_router_data = bitpay::BitpayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?; let bitpay_req = types::RequestBody::log_and_get_request_body( &req_obj, diff --git a/crates/router/src/connector/bitpay/transformers.rs b/crates/router/src/connector/bitpay/transformers.rs index f99729da16d6..c5c20608a754 100644 --- a/crates/router/src/connector/bitpay/transformers.rs +++ b/crates/router/src/connector/bitpay/transformers.rs @@ -9,6 +9,37 @@ use crate::{ types::{self, api, storage::enums, ConnectorAuthType}, }; +#[derive(Debug, Serialize)] +pub struct BitpayRouterData { + pub amount: i64, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for BitpayRouterData +{ + type Error = error_stack::Report; + + fn try_from( + (_currency_unit, _currency, amount, router_data): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount, + router_data, + }) + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum TransactionSpeed { @@ -31,9 +62,11 @@ pub struct BitpayPaymentsRequest { token: Secret, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for BitpayPaymentsRequest { +impl TryFrom<&BitpayRouterData<&types::PaymentsAuthorizeRouterData>> for BitpayPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { get_crypto_specific_payment_data(item) } } @@ -152,11 +185,13 @@ pub struct BitpayRefundRequest { pub amount: i64, } -impl TryFrom<&types::RefundsRouterData> for BitpayRefundRequest { +impl TryFrom<&BitpayRouterData<&types::RefundsRouterData>> for BitpayRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: &BitpayRouterData<&types::RefundsRouterData>, + ) -> Result { Ok(Self { - amount: item.request.refund_amount, + amount: item.router_data.request.refund_amount, }) } } @@ -232,14 +267,14 @@ pub struct BitpayErrorResponse { } fn get_crypto_specific_payment_data( - item: &types::PaymentsAuthorizeRouterData, + item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result> { - let price = item.request.amount; - let currency = item.request.currency.to_string(); - let redirect_url = item.request.get_return_url()?; - let notification_url = item.request.get_webhook_url()?; + let price = item.amount; + let currency = item.router_data.request.currency.to_string(); + let redirect_url = item.router_data.request.get_return_url()?; + let notification_url = item.router_data.request.get_webhook_url()?; let transaction_speed = TransactionSpeed::Medium; - let auth_type = item.connector_auth_type.clone(); + let auth_type = item.router_data.connector_auth_type.clone(); let token = match auth_type { ConnectorAuthType::HeaderKey { api_key } => api_key, _ => String::default().into(), From db8f58b145feef371c958086a1ec02128680d018 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:32:54 +0000 Subject: [PATCH 33/38] test(postman): update postman collection files --- .../stripe.postman_collection.json | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index 461c62473182..393b2979289b 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -268,6 +268,14 @@ " \"INFO - Unable to assign variable {{organization_id}}, as jsonData.organization_id is undefined.\",", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/accounts - Organization id is generated\",", + " function () {", + " pm.expect(typeof jsonData.organization_id !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -8361,6 +8369,306 @@ } ] }, + { + "name": "Scenario27-Create a failure card payment with confirm true", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"failed\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"card_declined\" for \"error_code\"", + "if (jsonData?.error_code) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", + " function () {", + " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", + " },", + " );", + "}", + "", + "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", + "if (jsonData?.error_message) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " function () {", + " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1,\"customer_id\":\"bernard123\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000009995\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"Succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'failed'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"failed\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"card_declined\" for \"error_code\"", + "if (jsonData?.error_code) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", + " function () {", + " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", + " },", + " );", + "}", + "", + "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", + "if (jsonData?.error_message) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " function () {", + " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, { "name": "Scenario1-Create payment with confirm true", "item": [ From 09c3e170d1fa9122808db2bb53f5b1d3577f1b17 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:32:55 +0000 Subject: [PATCH 34/38] chore(version): v1.69.0 --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b424379a8586..aaf1cc629d8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,50 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.69.0 (2023-10-31) + +### Features + +- **connector:** + - [VOLT] Implement payment flows and bank redirect payment method ([#2582](https://github.com/juspay/hyperswitch/pull/2582)) ([`23bd364`](https://github.com/juspay/hyperswitch/commit/23bd364a7819a48c3f5f89ff5b71cc237d6e2d46)) + - [NMI] add orderid to PaymentRequest ([#2727](https://github.com/juspay/hyperswitch/pull/2727)) ([`aad3f0f`](https://github.com/juspay/hyperswitch/commit/aad3f0f6fafdb08f1c5f1feb2588d6d0fb9162ff)) + - Worldline Use `connector_response_reference_id` as reference to merchant ([#2721](https://github.com/juspay/hyperswitch/pull/2721)) ([`a261f1a`](https://github.com/juspay/hyperswitch/commit/a261f1a2fce84354b3741429b629928d1bd06aab)) + - [Authorizedotnet] Use connector_request_reference_id as reference to the connector ([#2593](https://github.com/juspay/hyperswitch/pull/2593)) ([`3d7c6b0`](https://github.com/juspay/hyperswitch/commit/3d7c6b004d5f6399858925b40c3010fca486bbd5)) + - [Multisafepay] Currency Unit Conversion ([#2679](https://github.com/juspay/hyperswitch/pull/2679)) ([`42b13f7`](https://github.com/juspay/hyperswitch/commit/42b13f737a53143057ab23867f32017ea8c17780)) + - [Iatapay] currency unit conversion ([#2592](https://github.com/juspay/hyperswitch/pull/2592)) ([`0f5406c`](https://github.com/juspay/hyperswitch/commit/0f5406c620e9cdd20841898e9451a35f434f5b8a)) + - [BitPay] Currency Unit Conversion ([#2736](https://github.com/juspay/hyperswitch/pull/2736)) ([`e377279`](https://github.com/juspay/hyperswitch/commit/e377279d9cc872238fcfd8de324b44b0249b95c2)) +- **organization:** Add organization table ([#2669](https://github.com/juspay/hyperswitch/pull/2669)) ([`d682471`](https://github.com/juspay/hyperswitch/commit/d6824710015b134a50986b3e85d3840902322711)) +- Add one-click deploy script for HyperSwitch on AWS (EC2, RDS, Redis) ([#2730](https://github.com/juspay/hyperswitch/pull/2730)) ([`838372a`](https://github.com/juspay/hyperswitch/commit/838372ab3f6f3f35b8d884958810bab54cc17244)) +- Implement list_merchant_connector_accounts_by_merchant_id_connector_name function ([#2742](https://github.com/juspay/hyperswitch/pull/2742)) ([`15a6b5a`](https://github.com/juspay/hyperswitch/commit/15a6b5a855def5650e16b96e6529ad7fa0845e6b)) + +### Bug Fixes + +- **connector:** [Stripe] add decline_code in error_reason ([#2735](https://github.com/juspay/hyperswitch/pull/2735)) ([`0a44f56`](https://github.com/juspay/hyperswitch/commit/0a44f5699ed7b0c0ea0352b67c65df496ebe61f3)) +- **typo:** Add commit id to allowed typos ([#2733](https://github.com/juspay/hyperswitch/pull/2733)) ([`8984627`](https://github.com/juspay/hyperswitch/commit/8984627d1cfd1a773e931617a3351884b12399a5)) +- Make kv log extraction easier ([#2666](https://github.com/juspay/hyperswitch/pull/2666)) ([`577ef1a`](https://github.com/juspay/hyperswitch/commit/577ef1ae1a4718aaf90175d49e2a786af255fd63)) + +### Refactors + +- **connector:** + - [Noon] Remove Default Case Handling ([#2677](https://github.com/juspay/hyperswitch/pull/2677)) ([`452090d`](https://github.com/juspay/hyperswitch/commit/452090d56d713a5cc5c8fae3cc2f9f3d26e27a53)) + - [Payme] Remove Default Case Handling ([#2719](https://github.com/juspay/hyperswitch/pull/2719)) ([`94947bd`](https://github.com/juspay/hyperswitch/commit/94947bdb33ca4eb91daad13b2a427592d3b69851)) + - [Payeezy] remove default case handling ([#2712](https://github.com/juspay/hyperswitch/pull/2712)) ([`ceed76f`](https://github.com/juspay/hyperswitch/commit/ceed76fb2e67771048e563a13703eb801eeaae08)) +- **core:** Use `business_profile` to read merchant configs ([#2729](https://github.com/juspay/hyperswitch/pull/2729)) ([`8c85173`](https://github.com/juspay/hyperswitch/commit/8c85173ecdd13db5ec7c4c0fe18456a31c8ee57e)) +- **db:** Migrate to payment_attempt from connector_response ([#2656](https://github.com/juspay/hyperswitch/pull/2656)) ([`9d9fc2a`](https://github.com/juspay/hyperswitch/commit/9d9fc2a8c5e9e30ed7ed4eeb2417365fc06be711)) + +### Testing + +- **postman:** Update postman collection files ([`db8f58b`](https://github.com/juspay/hyperswitch/commit/db8f58b145feef371c958086a1ec02128680d018)) + +### Miscellaneous Tasks + +- **env:** Add ttl as env variable ([#2653](https://github.com/juspay/hyperswitch/pull/2653)) ([`8b1499e`](https://github.com/juspay/hyperswitch/commit/8b1499e121678c5df3ca0197e2ec14074fd96eb5)) + +**Full Changelog:** [`v1.68.0...v1.69.0`](https://github.com/juspay/hyperswitch/compare/v1.68.0...v1.69.0) + +- - - + + ## 1.68.0 (2023-10-29) ### Features From 42261a5306bb99d3e20eb3aa734a895e589b1d94 Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Tue, 31 Oct 2023 19:56:45 +0530 Subject: [PATCH 35/38] fix: null fields in payments respose (#2745) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../src/payments/payment_intent.rs | 29 ------------------- .../src/mock_db/payment_intent.rs | 7 ++++- .../src/payments/payment_intent.rs | 14 +++++---- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index 5b41c74bd697..2c5914f5b37f 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -215,35 +215,6 @@ pub struct PaymentIntentUpdateInternal { pub surcharge_applicable: Option, } -impl PaymentIntentUpdate { - pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { - let internal_update: PaymentIntentUpdateInternal = self.into(); - PaymentIntent { - amount: internal_update.amount.unwrap_or(source.amount), - currency: internal_update.currency.or(source.currency), - status: internal_update.status.unwrap_or(source.status), - amount_captured: internal_update.amount_captured.or(source.amount_captured), - customer_id: internal_update.customer_id.or(source.customer_id), - return_url: internal_update.return_url.or(source.return_url), - setup_future_usage: internal_update - .setup_future_usage - .or(source.setup_future_usage), - off_session: internal_update.off_session.or(source.off_session), - metadata: internal_update.metadata.or(source.metadata), - billing_address_id: internal_update - .billing_address_id - .or(source.billing_address_id), - shipping_address_id: internal_update - .shipping_address_id - .or(source.shipping_address_id), - modified_at: common_utils::date_time::now(), - order_details: internal_update.order_details.or(source.order_details), - updated_by: internal_update.updated_by, - ..source - } - } -} - impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { match payment_intent_update { diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index d1979cba01de..08a4a2aabeaa 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -11,6 +11,7 @@ use diesel_models::enums as storage_enums; use error_stack::{IntoReport, ResultExt}; use super::MockDb; +use crate::DataModelExt; #[async_trait::async_trait] impl PaymentIntentInterface for MockDb { @@ -123,7 +124,11 @@ impl PaymentIntentInterface for MockDb { .iter_mut() .find(|item| item.id == this.id) .unwrap(); - *payment_intent = update.apply_changeset(this); + *payment_intent = PaymentIntent::from_storage_model( + update + .to_storage_model() + .apply_changeset(this.to_storage_model()), + ); Ok(payment_intent.clone()) } diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 7c0939414aeb..2dc5cdd1c026 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -146,8 +146,12 @@ impl PaymentIntentInterface for KVRouterStore { let key = format!("mid_{}_pid_{}", this.merchant_id, this.payment_id); let field = format!("pi_{}", this.payment_id); - let updated_intent = payment_intent_update.clone().apply_changeset(this.clone()); - let diesel_intent = updated_intent.clone().to_storage_model(); + let diesel_intent_update = payment_intent_update.to_storage_model(); + let origin_diesel_intent = this.to_storage_model(); + + let diesel_intent = diesel_intent_update + .clone() + .apply_changeset(origin_diesel_intent.clone()); // Check for database presence as well Maybe use a read replica here ? let redis_value = @@ -158,8 +162,8 @@ impl PaymentIntentInterface for KVRouterStore { op: kv::DBOperation::Update { updatable: kv::Updateable::PaymentIntentUpdate( kv::PaymentIntentUpdateMems { - orig: this.to_storage_model(), - update_data: payment_intent_update.to_storage_model(), + orig: origin_diesel_intent, + update_data: diesel_intent_update, }, ), }, @@ -175,7 +179,7 @@ impl PaymentIntentInterface for KVRouterStore { .try_into_hset() .change_context(StorageError::KVError)?; - Ok(updated_intent) + Ok(PaymentIntent::from_storage_model(diesel_intent)) } } } From b3c846d637dd32a2d6d7044c118abbb2616642f0 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:47:25 +0530 Subject: [PATCH 36/38] feat(connector): [Multisafepay] add error handling (#2595) Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Co-authored-by: Arjun Karthik --- crates/router/src/connector/multisafepay.rs | 16 +- .../connector/multisafepay/transformers.rs | 189 +++++++++++------- .../QuickStart/Payments - Create/request.json | 4 + .../.event.meta.json | 3 + .../Payment Connector - Update/event.test.js | 39 ++++ .../Payment Connector - Update/request.json | 122 +++++++++++ .../Payment Connector - Update/response.json | 1 + .../Payments - Create/.event.meta.json | 3 + .../Payments - Create/event.test.js | 56 ++++++ .../Payments - Create/request.json | 90 +++++++++ .../Payments - Create/response.json | 1 + 11 files changed, 448 insertions(+), 76 deletions(-) create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json create mode 100644 postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 1ea5029fd003..e09f242abc8a 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -200,10 +200,11 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::MultisafepayPaymentsResponse = res + let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("multisafepay PaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), @@ -301,10 +302,11 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::MultisafepayPaymentsResponse = res + let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("MultisafepayPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::ResponseRouterData { response, data: data.clone(), @@ -399,17 +401,17 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: multisafepay::RefundResponse = res + let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, } .try_into() - .change_context(errors::ConnectorError::ResponseHandlingFailed) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) } fn get_error_response( @@ -472,7 +474,7 @@ impl ConnectorIntegration CustomResult { - let response: multisafepay::RefundResponse = res + let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -482,7 +484,7 @@ impl ConnectorIntegration - TryFrom< - types::ResponseRouterData, - > for types::RouterData + TryFrom> + for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, - MultisafepayPaymentsResponse, + MultisafepayAuthResponse, T, types::PaymentsResponseData, >, ) -> Result { - let redirection_data = item - .response - .data - .payment_url - .clone() - .map(|url| services::RedirectForm::from((url, services::Method::Get))); - - let default_status = if item.response.success { - MultisafepayPaymentStatus::Initialized - } else { - MultisafepayPaymentStatus::Declined - }; - - let status = item.response.data.status.unwrap_or(default_status); - - Ok(Self { - status: enums::AttemptStatus::from(status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.data.order_id.clone(), - ), - redirection_data, - mandate_reference: item - .response + match item.response { + MultisafepayAuthResponse::PaymentResponse(payment_response) => { + let redirection_data = payment_response .data - .payment_details - .and_then(|payment_details| payment_details.recurring_id) - .map(|id| types::MandateReference { - connector_mandate_id: Some(id), - payment_method_id: None, + .payment_url + .clone() + .map(|url| services::RedirectForm::from((url, services::Method::Get))); + + let default_status = if payment_response.success { + MultisafepayPaymentStatus::Initialized + } else { + MultisafepayPaymentStatus::Declined + }; + + let status = payment_response.data.status.unwrap_or(default_status); + + Ok(Self { + status: enums::AttemptStatus::from(status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + payment_response.data.order_id.clone(), + ), + redirection_data, + mandate_reference: payment_response + .data + .payment_details + .and_then(|payment_details| payment_details.recurring_id) + .map(|id| types::MandateReference { + connector_mandate_id: Some(id), + payment_method_id: None, + }), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + payment_response.data.order_id.clone(), + ), }), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some(item.response.data.order_id.clone()), + ..item.data + }) + } + MultisafepayAuthResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } @@ -747,61 +766,93 @@ pub struct RefundData { pub error_code: Option, pub error_info: Option, } -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundResponse { pub success: bool, pub data: RefundData, } -impl TryFrom> +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MultisafepayRefundResponse { + ErrorResponse(MultisafepayErrorResponse), + RefundResponse(RefundResponse), +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - let refund_stat = if item.response.success { - RefundStatus::Succeeded - } else { - RefundStatus::Failed - }; - - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.data.refund_id.to_string(), - refund_status: enums::RefundStatus::from(refund_stat), + match item.response { + MultisafepayRefundResponse::RefundResponse(refund_data) => { + let refund_status = if refund_data.success { + RefundStatus::Succeeded + } else { + RefundStatus::Failed + }; + + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: refund_data.data.refund_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } + MultisafepayRefundResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } -impl TryFrom> +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - let refund_status = if item.response.success { - RefundStatus::Succeeded - } else { - RefundStatus::Failed - }; - - Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.data.refund_id.to_string(), - refund_status: enums::RefundStatus::from(refund_status), + match item.response { + MultisafepayRefundResponse::RefundResponse(refund_data) => { + let refund_status = if refund_data.success { + RefundStatus::Succeeded + } else { + RefundStatus::Failed + }; + + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: refund_data.data.refund_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } + MultisafepayRefundResponse::ErrorResponse(error_response) => Ok(Self { + response: Err(types::ErrorResponse { + code: error_response.error_code.to_string(), + message: error_response.error_info.clone(), + reason: Some(error_response.error_info), + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct MultisafepayErrorResponse { - pub success: bool, pub error_code: i32, pub error_info: String, } diff --git a/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json index 289e780a72cb..a4e816ad17d2 100644 --- a/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/multisafepay/Flow Testcases/QuickStart/Payments - Create/request.json @@ -55,6 +55,10 @@ "last_name": "happy" } }, + "routing": { + "type": "single", + "data": "multisafepay" + }, "shipping": { "address": { "line1": "1467", diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js new file mode 100644 index 000000000000..d7259b6a840b --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/event.test.js @@ -0,0 +1,39 @@ +// Validate status 2xx +pm.test( + "[POST]::/account/:account_id/connectors/:connector_id - Status code is 2xx", + function () { + pm.response.to.be.success; + }, +); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/account/:account_id/connectors/:connector_id - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id +if (jsonData?.merchant_connector_id) { + pm.collectionVariables.set( + "merchant_connector_id", + jsonData.merchant_connector_id, + ); + console.log( + "- use {{merchant_connector_id}} as collection variable for value", + jsonData.merchant_connector_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.", + ); +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json new file mode 100644 index 000000000000..5328f86fbc92 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/request.json @@ -0,0 +1,122 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "connector_type": "fiz_operations", + "connector_account_details": { + "auth_type": "HeaderKey", + "api_key": "wrongAPIKey" + }, + "test_mode": false, + "disabled": false, + "payment_methods_enabled": [ + { + "payment_method": "card", + "payment_method_types": [ + { + "payment_method_type": "credit", + "card_networks": ["Visa", "Mastercard"], + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "debit", + "card_networks": ["Visa", "Mastercard"], + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "payment_method_type": "klarna", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "affirm", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "afterpay_clearpay", + "payment_experience": "redirect_to_url", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + } + ], + "metadata": { + "city": "NY", + "unit": "245" + } + } + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors/:connector_id", + "host": ["{{baseUrl}}"], + "path": ["account", ":account_id", "connectors", ":connector_id"], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}" + }, + { + "key": "connector_id", + "value": "{{merchant_connector_id}}" + } + ] + }, + "description": "To update an existing Payment Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc" +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payment Connector - Update/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js new file mode 100644 index 000000000000..e75cb69a9249 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/event.test.js @@ -0,0 +1,56 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + + + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// Validate error message in the JSON Body +pm.test("[POST]::/payments - Validate error message", function () { + pm.expect(jsonData.error_message).to.not.be.null +}); + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json new file mode 100644 index 000000000000..525eaa739e83 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/request.json @@ -0,0 +1,90 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://duck.com", + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "routing": { + "type": "single", + "data": "multisafepay" + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "someone", + "last_name": "happy" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "someone", + "last_name": "happy" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/multisafepay/Flow Testcases/Variation Cases/Scenario6- Create payment with Invalid Merchant ID/Payments - Create/response.json @@ -0,0 +1 @@ +[] From b06f104f7cf11b3e1a33a57841ce831669cb8c9e Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Thu, 2 Nov 2023 14:46:22 +0530 Subject: [PATCH 37/38] add a new line --- crates/router/src/connector/volt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index ba70cb200179..7cf992fccc37 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -542,3 +542,4 @@ impl api::IncomingWebhook for Volt { Err(errors::ConnectorError::WebhooksNotImplemented).into_report() } } + From 2a3c2b883eee2ea5711c68c6aff4af337868e12d Mon Sep 17 00:00:00 2001 From: AkshayaFoiger Date: Thu, 2 Nov 2023 14:50:10 +0530 Subject: [PATCH 38/38] resolve merge conflicts --- crates/router/src/connector/volt.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 2e7d6ac31a92..c02d4de728e2 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -173,6 +173,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { let req_obj = volt::VoltAuthUpdateRequest::try_from(req)?; let volt_req = types::RequestBody::log_and_get_request_body( @@ -195,7 +196,9 @@ impl ConnectorIntegration