From 36805411772da00719a716d05c650f10ca990d49 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:00:21 +0530 Subject: [PATCH 01/21] refactor(connector): [Cryptopay] Update PSync with connector_request_reference_id (#2388) --- crates/router/src/connector/cryptopay.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index fd324fcc68b8..2387a9bf4019 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -295,16 +295,10 @@ impl ConnectorIntegration CustomResult { - let connector_id = req - .request - .connector_transaction_id - .get_connector_transaction_id() - .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; - + let custom_id = req.connector_request_reference_id.clone(); Ok(format!( - "{}/api/invoices/{}", - self.base_url(connectors), - connector_id + "{}/api/invoices/custom_id/{custom_id}", + self.base_url(connectors) )) } From e5ad9c5c35f386486afedded90c46793196a17d0 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:14:23 +0530 Subject: [PATCH 02/21] refactor(router): add `#[cfg(not(feature = "kms"))]` feature flag to test the simplified apple pay flow locally (#2200) --- crates/router/src/consts.rs | 1 - crates/router/src/core/payments.rs | 2 -- crates/router/src/core/payments/helpers.rs | 23 ++++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index adde413730cb..228d02e1ddac 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -41,7 +41,6 @@ pub(crate) const APPLEPAY_VALIDATION_URL: &str = pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; // OID (Object Identifier) for the merchant ID field extension. -#[cfg(feature = "kms")] pub(crate) const MERCHANT_ID_FIELD_EXTENSION_ID: &str = "1.2.840.113635.100.6.32"; pub(crate) const METRICS_HOST_TAG_NAME: &str = "host"; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index cb2b24c4158c..708e20d20504 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -15,7 +15,6 @@ use data_models::mandates::MandateData; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{IntoReport, ResultExt}; use futures::future::join_all; -#[cfg(feature = "kms")] use helpers::ApplePayData; use masking::Secret; use router_env::{instrument, tracing}; @@ -643,7 +642,6 @@ where // Tokenization Action will be DecryptApplePayToken, only when payment method type is Apple Pay // and the connector supports Apple Pay predecrypt - #[cfg(feature = "kms")] if matches!( tokenization_action, TokenizationAction::DecryptApplePayToken diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index d964b4b2e2b2..60ebfd2bdf0f 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -16,26 +16,23 @@ use error_stack::{report, IntoReport, ResultExt}; use external_services::kms; use josekit::jwe; use masking::{ExposeInterface, PeekInterface}; -#[cfg(feature = "kms")] -use openssl::derive::Deriver; -#[cfg(feature = "kms")] -use openssl::pkey::PKey; -#[cfg(feature = "kms")] -use openssl::symm::{decrypt_aead, Cipher}; +use openssl::{ + derive::Deriver, + pkey::PKey, + symm::{decrypt_aead, Cipher}, +}; use router_env::{instrument, logger, tracing}; use time::Duration; use uuid::Uuid; -#[cfg(feature = "kms")] use x509_parser::parse_x509_certificate; use super::{ operations::{BoxedOperation, Operation, PaymentResponse}, CustomerDetails, PaymentData, }; -#[cfg(feature = "kms")] -use crate::connector; use crate::{ configs::settings::{ConnectorRequestReferenceIdConfig, Server, TempLockerDisableConfig}, + connector, consts::{self, BASE64_ENGINE}, core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, @@ -3213,7 +3210,6 @@ pub struct ApplePayHeader { transaction_id: masking::Secret, } -#[cfg(feature = "kms")] impl ApplePayData { pub fn token_json( wallet_data: api_models::payments::WalletData, @@ -3241,12 +3237,16 @@ impl ApplePayData { &self, state: &AppState, ) -> CustomResult { + #[cfg(feature = "kms")] let cert_data = kms::get_kms_client(&state.conf.kms) .await .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc) .await .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; + #[cfg(not(feature = "kms"))] + let cert_data = &state.conf.applepay_decrypt_keys.apple_pay_ppc; + let base64_decode_cert_data = BASE64_ENGINE .decode(cert_data) .into_report() @@ -3297,12 +3297,15 @@ impl ApplePayData { .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed) .attach_printable("Failed to deserialize the public key")?; + #[cfg(feature = "kms")] let decrypted_apple_pay_ppc_key = kms::get_kms_client(&state.conf.kms) .await .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc_key) .await .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; + #[cfg(not(feature = "kms"))] + let decrypted_apple_pay_ppc_key = &state.conf.applepay_decrypt_keys.apple_pay_ppc_key; // Create PKey objects from EcKey let private_key = PKey::private_key_from_pem(decrypted_apple_pay_ppc_key.as_bytes()) .into_report() From 0aa6b30d2c9056e9a21a88bdc064daa7e8659bd6 Mon Sep 17 00:00:00 2001 From: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:57:42 +0530 Subject: [PATCH 03/21] fix(connector): [noon] add connector_auth params and update description (#2429) --- config/config.example.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + crates/router/src/configs/settings.rs | 11 +++- crates/router/src/connector/noon.rs | 57 +++++++++++-------- .../router/src/connector/noon/transformers.rs | 11 +++- loadtest/config/development.toml | 1 + 7 files changed, 57 insertions(+), 26 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 0841b26a37ca..d4b44a062a08 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -187,6 +187,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nmi.base_url = "https://secure.nmi.com/" noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" nuvei.base_url = "https://ppp-test.nuvei.com/" opayo.base_url = "https://pi-test.sagepay.com/" opennode.base_url = "https://dev-api.opennode.com" diff --git a/config/development.toml b/config/development.toml index 0f95dcb6577d..26a7251dc9fb 100644 --- a/config/development.toml +++ b/config/development.toml @@ -161,6 +161,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nmi.base_url = "https://secure.nmi.com/" noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" nuvei.base_url = "https://ppp-test.nuvei.com/" opayo.base_url = "https://pi-test.sagepay.com/" opennode.base_url = "https://dev-api.opennode.com" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 2ff1c6ab3364..ca6b9eeda5b0 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -105,6 +105,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nmi.base_url = "https://secure.nmi.com/" noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" nuvei.base_url = "https://ppp-test.nuvei.com/" opayo.base_url = "https://pi-test.sagepay.com/" opennode.base_url = "https://dev-api.opennode.com" diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index b5e522cf843a..fd4a398fd568 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -523,7 +523,7 @@ pub struct Connectors { pub multisafepay: ConnectorParams, pub nexinets: ConnectorParams, pub nmi: ConnectorParams, - pub noon: ConnectorParams, + pub noon: ConnectorParamsWithModeType, pub nuvei: ConnectorParams, pub opayo: ConnectorParams, pub opennode: ConnectorParams, @@ -552,6 +552,15 @@ pub struct ConnectorParams { pub secondary_base_url: Option, } +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithModeType { + pub base_url: String, + pub secondary_base_url: Option, + /// Can take values like Test or Live for Noon + pub key_mode: String, +} + #[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] pub struct ConnectorParamsWithMoreUrls { diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index deda24bfbd66..8b7c984083c7 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -65,7 +65,7 @@ where fn build_headers( &self, req: &types::RouterData, - _connectors: &settings::Connectors, + connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), @@ -73,12 +73,43 @@ where .to_string() .into(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + let mut api_key = get_auth_header(&req.connector_auth_type, connectors, req.test_mode)?; header.append(&mut api_key); Ok(header) } } +fn get_auth_header( + auth_type: &types::ConnectorAuthType, + connectors: &settings::Connectors, + test_mode: Option, +) -> CustomResult)>, errors::ConnectorError> { + let auth = noon::NoonAuthType::try_from(auth_type)?; + + let encoded_api_key = auth + .business_identifier + .zip(auth.application_identifier) + .zip(auth.api_key) + .map(|((business_identifier, application_identifier), api_key)| { + consts::BASE64_ENGINE.encode(format!( + "{}.{}:{}", + business_identifier, application_identifier, api_key + )) + }); + let key_mode = test_mode.map_or(connectors.noon.key_mode.clone(), |is_test_mode| { + if is_test_mode { + "Test".to_string() + } else { + "Live".to_string() + } + }); + + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Key_{} {}", key_mode, encoded_api_key.peek()).into_masked(), + )]) +} + impl ConnectorCommon for Noon { fn id(&self) -> &'static str { "noon" @@ -92,28 +123,6 @@ impl ConnectorCommon for Noon { connectors.noon.base_url.as_ref() } - fn get_auth_header( - &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = noon::NoonAuthType::try_from(auth_type)?; - - let encoded_api_key = auth - .business_identifier - .zip(auth.application_identifier) - .zip(auth.api_key) - .map(|((business_identifier, application_identifier), api_key)| { - consts::BASE64_ENGINE.encode(format!( - "{}.{}:{}", - business_identifier, application_identifier, api_key - )) - }); - Ok(vec![( - headers::AUTHORIZATION.to_string(), - format!("Key_Test {}", encoded_api_key.peek()).into_masked(), - )]) - } - fn build_error_response( &self, res: Response, diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index eff626189103..d7dc832b445d 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -238,7 +238,16 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { item.request.order_category.clone(), ), }; - let name = item.get_description()?; + + // The description should not have leading or trailing whitespaces, also it should not have double whitespaces and a max 50 chars according to Noon's Docs + let name: String = item + .get_description()? + .trim() + .replace(" ", " ") + .chars() + .take(50) + .collect(); + let (subscription, tokenize_c_c) = match item.request.setup_future_usage.is_some().then_some(( NoonSubscriptionData { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index ebab00cac144..1b413b1cdf8c 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -91,6 +91,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nmi.base_url = "https://secure.nmi.com/" noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" nuvei.base_url = "https://ppp-test.nuvei.com/" opayo.base_url = "https://pi-test.sagepay.com/" opennode.base_url = "https://dev-api.opennode.com" From d81762a8b430ca1f197d7dabb26167f54e235735 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:09:34 +0530 Subject: [PATCH 04/21] feat(pm_list): [Trustpay] add bank_redirect - blik pm type required field info for trustpay (#2390) --- crates/api_models/src/payments.rs | 2 +- crates/router/src/configs/defaults.rs | 79 +++++++++++++++++++ .../src/connector/adyen/transformers.rs | 6 +- .../src/connector/stripe/transformers.rs | 8 +- openapi/openapi_spec.json | 6 +- 5 files changed, 93 insertions(+), 8 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 6711f202e976..31fa00cef9ae 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -868,7 +868,7 @@ pub enum BankRedirectData { Bizum {}, Blik { // Blik Code - blik_code: String, + blik_code: Option, }, Eps { /// The billing details for bank redirection diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index cebb0014064e..ec06a6d7078f 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -3840,6 +3840,85 @@ impl Default for super::settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::Blik, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.blik.blik_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), + display_name: "blik_code".to_string(), + field_type: enums::FieldType::UserBlikCode, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.blik.blik_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), + display_name: "blik_code".to_string(), + field_type: enums::FieldType::UserBlikCode, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + } + ) + ]), + }, + ), ])), ), ( diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 708d795db0e1..94c7f0c58778 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2034,7 +2034,11 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod api_models::payments::BankRedirectData::Blik { blik_code } => { Ok(AdyenPaymentMethod::Blik(Box::new(BlikRedirectionData { payment_type: PaymentType::Blik, - blik_code: blik_code.to_string(), + blik_code: blik_code.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "blik_code", + }, + )?, }))) } api_models::payments::BankRedirectData::Eps { bank_name, .. } => Ok( diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index ce434aa8ba1d..488a7a295bd2 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1582,7 +1582,7 @@ impl TryFrom<&payments::BankRedirectData> for StripePaymentMethodData { type Error = error_stack::Report; fn try_from(bank_redirect_data: &payments::BankRedirectData) -> Result { let payment_method_data_type = StripePaymentMethodType::try_from(bank_redirect_data)?; - match bank_redirect_data.deref() { + match bank_redirect_data { payments::BankRedirectData::BancontactCard { .. } => Ok(Self::BankRedirect( StripeBankRedirectData::StripeBancontactCard(Box::new(StripeBancontactCard { payment_method_data_type, @@ -1591,7 +1591,11 @@ impl TryFrom<&payments::BankRedirectData> for StripePaymentMethodData { payments::BankRedirectData::Blik { blik_code } => Ok(Self::BankRedirect( StripeBankRedirectData::StripeBlik(Box::new(StripeBlik { payment_method_data_type, - code: blik_code.to_owned(), + code: blik_code.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "blik_code", + }, + )?, })), )), payments::BankRedirectData::Eps { bank_name, .. } => Ok(Self::BankRedirect( diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 1d943117c8e9..efa7ebb32cfe 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -2911,12 +2911,10 @@ "properties": { "blik": { "type": "object", - "required": [ - "blik_code" - ], "properties": { "blik_code": { - "type": "string" + "type": "string", + "nullable": true } } } From 34099baa2ec2f73598c4433b0a481dec3fde8c05 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:57:51 +0000 Subject: [PATCH 05/21] test(postman): update postman collection files --- .../bluesnap.postman_collection.json | 1684 ++++++++--------- 1 file changed, 842 insertions(+), 842 deletions(-) diff --git a/postman/collection-json/bluesnap.postman_collection.json b/postman/collection-json/bluesnap.postman_collection.json index d2073774a703..7e12aa04849b 100644 --- a/postman/collection-json/bluesnap.postman_collection.json +++ b/postman/collection-json/bluesnap.postman_collection.json @@ -839,7 +839,7 @@ "name": "Happy Cases", "item": [ { - "name": "Scenario1-Create payment with confirm true", + "name": "Scenario8-Refund full payment", "item": [ { "name": "Payments - Create", @@ -1077,87 +1077,61 @@ "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", + "name": "Refunds - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - 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.test(\"[POST]::/refunds - 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);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_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]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", @@ -1186,45 +1160,139 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "refunds" ] }, - "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" + "description": "To create a refund against an already processed payment" }, "response": [] }, { - "name": "Payments - Confirm", + "name": "Refunds - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/refunds/:id - 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\",", - " );", - " },", - ");", + "pm.test(\"[GET]::/refunds/: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 refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/refunds/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" + } + ] + }, + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario9-Partial refund", + "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/:id/confirm - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -1276,7 +1344,7 @@ "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", @@ -1289,26 +1357,6 @@ } ], "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": [ { @@ -1327,27 +1375,18 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\"}" + "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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "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" + "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": [] }, @@ -1420,10 +1459,10 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", @@ -1469,208 +1508,61 @@ "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", + "name": "Refunds - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - 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.test(\"[POST]::/refunds - 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);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " 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\":6540,\"currency\":\"USD\",\"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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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 - 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 \"succeeded\" for \"status\"", + "// Response body should have value \"540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.amount).to.eql(540);", " },", " );", "}", @@ -1681,26 +1573,6 @@ } ], "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": [ { @@ -1719,105 +1591,75 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"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\"}}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "refunds" ] }, - "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" + "description": "To create a refund against an already processed payment" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Refunds - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/refunds/: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.test(\"[GET]::/refunds/: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);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_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]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.amount).to.eql(540);", " },", " );", "}", @@ -1836,112 +1678,80 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "payments", + "refunds", ":id" ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], "variable": [ { "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "value": "{{refund_id}}", + "description": "(Required) unique refund 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" + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - } - ] - }, - { - "name": "Scenario4-Create payment with Manual capture", - "item": [ + }, { - "name": "Payments - Create", + "name": "Refunds - Create-copy", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - 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.test(\"[POST]::/refunds - 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);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_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]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"requires_capture\" for \"status\"", + "// Response body should have value \"1000\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " pm.expect(jsonData.amount).to.eql(1000);", " },", " );", "}", @@ -1970,97 +1780,63 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":1000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "refunds" ] }, - "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" + "description": "To create a refund against an already processed payment" }, "response": [] }, { - "name": "Payments - Capture", + "name": "Refunds - Retrieve-copy", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/capture - 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/capture - 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.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", " );", - "}", + "});", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", @@ -2068,21 +1844,11 @@ "}", "", "// 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 \"6000\" for \"amount_received\"", - "if (jsonData?.amount_received) {", + "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", " function () {", - " pm.expect(jsonData.amount_received).to.eql(6000);", + " pm.expect(jsonData.amount).to.eql(1000);", " },", " );", "}", @@ -2093,50 +1859,36 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" - }, "url": { - "raw": "{{baseUrl}}/payments/:id/capture", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "capture" + "refunds", + ":id" ], "variable": [ { "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "value": "{{refund_id}}", + "description": "(Required) unique refund id" } ] }, - "description": "To capture the funds for an uncaptured payment" + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Retrieve-copy", "event": [ { "listen": "test", @@ -2204,15 +1956,20 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "", + "// Response body should have \"refunds\"", + "pm.test(\"[POST]::/payments - Content check if 'refunds' exists\", function () {", + " pm.expect(typeof jsonData.refunds !== \"undefined\").to.be.true;", + "});", "" ], "type": "text/javascript" @@ -2257,7 +2014,7 @@ ] }, { - "name": "Scenario5-Void the payment", + "name": "Scenario1-Create payment with confirm true", "item": [ { "name": "Payments - Create", @@ -2328,12 +2085,12 @@ " );", "}", "", - "// Response body should have value \"requires_capture\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2362,7 +2119,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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,\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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", @@ -2378,29 +2135,26 @@ "response": [] }, { - "name": "Payments - Cancel", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/cancel - Status code is 2xx\", function () {", + "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(", - " \"[POST]::/payments/:id/cancel - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "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(\"[POST]::/payments/:id/cancel - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -2423,6 +2177,19 @@ " );", "}", "", + "// 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);", @@ -2436,12 +2203,12 @@ " );", "}", "", - "// Response body should have value \"cancelled\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/cancel - Content check if value for 'status' matches 'cancelled'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"cancelled\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2452,35 +2219,27 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"cancellation_reason\":\"requested_by_customer\"}" - }, "url": { - "raw": "{{baseUrl}}/payments/:id/cancel", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id", - "cancel" + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } ], "variable": [ { @@ -2490,31 +2249,36 @@ } ] }, - "description": "A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action" + "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 - Retrieve", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - 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.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(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -2563,12 +2327,12 @@ " );", "}", "", - "// Response body should have value \"cancelled\" for \"status\"", + "// Response body should have value \"requires_confirmation\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'cancelled'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"cancelled\");", + " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", " },", " );", "}", @@ -2579,66 +2343,63 @@ } ], "request": { - "method": "GET", + "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\":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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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/:id?force_sync=true", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "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" + "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": "Scenario6-Create 3DS payment", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "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 - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "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 - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -2687,24 +2448,15 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", - "", - "// 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;", - " },", - ");", "" ], "type": "text/javascript" @@ -2712,6 +2464,26 @@ } ], "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": [ { @@ -2730,18 +2502,27 @@ "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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"client_secret\":\"{{client_secret}}\"}" }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "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" + "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": [] }, @@ -2814,12 +2595,12 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2867,7 +2648,7 @@ ] }, { - "name": "Scenario7-Create 3DS payment with confrm false", + "name": "Scenario3-Create payment without PMD", "item": [ { "name": "Payments - Create", @@ -2938,12 +2719,12 @@ " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// 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_confirmation'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", @@ -2972,7 +2753,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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\":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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\",\"last_name\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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", @@ -3059,24 +2840,15 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", - "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if 'next_action.redirect_to_url' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", - " },", - ");", "" ], "type": "text/javascript" @@ -3122,7 +2894,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"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\"}}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -3215,12 +2987,12 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3268,7 +3040,7 @@ ] }, { - "name": "Scenario9-Refund full payment", + "name": "Scenario4-Create payment with Manual capture", "item": [ { "name": "Payments - Create", @@ -3339,12 +3111,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_capture\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", " },", " );", "}", @@ -3373,7 +3145,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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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,\"capture_method\":\"manual\",\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"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", @@ -3389,26 +3161,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - 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\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - 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.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -3457,15 +3232,35 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "", + "// 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 \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6000);", + " },", + " );", + "}", "" ], "type": "text/javascript" @@ -3473,27 +3268,35 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "capture" ], "variable": [ { @@ -3503,64 +3306,85 @@ } ] }, - "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" + "description": "To capture the funds for an uncaptured payment" }, "response": [] }, { - "name": "Refunds - Create", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "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(\"[POST]::/refunds - Content-Type is application/json\", function () {", + "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 refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'pending'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"pending\");", - " },", + "// 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.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// 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]::/refunds - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3571,93 +3395,120 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" - }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To create a refund against an already processed payment" + "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": "Scenario5-Void the payment", + "item": [ { - "name": "Refunds - Retrieve", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + "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 refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// 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.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// 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_capture\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", " },", " );", "}", @@ -3668,60 +3519,63 @@ } ], "request": { - "method": "GET", + "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,\"capture_method\":\"manual\",\"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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" - } + "payments" ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "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": "Scenario10-Partial refund", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Cancel", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/cancel - 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\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/cancel - 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.test(\"[POST]::/payments/:id/cancel - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -3744,19 +3598,6 @@ " );", "}", "", - "// 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);", @@ -3770,12 +3611,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"cancelled\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/cancel - Content check if value for 'status' matches 'cancelled'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"cancelled\");", " },", " );", "}", @@ -3804,18 +3645,27 @@ "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\":\"+65\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"cancellation_reason\":\"requested_by_customer\"}" }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/payments/:id/cancel", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id", + "cancel" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "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" + "description": "A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action" }, "response": [] }, @@ -3888,12 +3738,12 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"cancelled\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'cancelled'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"cancelled\");", " },", " );", "}", @@ -3937,64 +3787,99 @@ "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": "Scenario6-Create 3DS payment", + "item": [ { - "name": "Refunds - Create", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "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]::/refunds - Content-Type is application/json\", function () {", + "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 refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'pending'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"pending\");", - " },", + "// 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.\",", " );", "}", "", - "// Response body should have value \"540\" for \"amount\"", + "// 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]::/refunds - Content check if value for 'amount' matches '540'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(540);", + " 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;", + " },", + ");", "" ], "type": "text/javascript" @@ -4020,75 +3905,96 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments" ] }, - "description": "To create a refund against an already processed payment" + "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": "Refunds - Retrieve", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "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]::/refunds/:id - Content-Type is application/json\", function () {", + "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 refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// 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\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(540);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", @@ -4107,80 +4013,112 @@ } ], "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", + "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "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": "Scenario7-Create 3DS payment with confrm false", + "item": [ { - "name": "Refunds - Create-copy", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "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]::/refunds - Content-Type is application/json\", function () {", + "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 refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'pending'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"pending\");", - " },", + "// 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.\",", " );", "}", "", - "// Response body should have value \"1000\" for \"amount\"", + "// 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]::/refunds - Content check if value for 'amount' matches '1000'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(1000);", + " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", " },", " );", "}", @@ -4209,38 +4147,46 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":1000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"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\":\"abc\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments" ] }, - "description": "To create a refund against an already processed payment" + "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": "Refunds - Retrieve-copy", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "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(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + "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", @@ -4249,38 +4195,63 @@ " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// 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 {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// 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.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// 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]::/refunds - Content check if value for 'amount' matches '1000'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(1000);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", + "", + "// Response body should have \"next_action.redirect_to_url\"", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if 'next_action.redirect_to_url' exists\",", + " function () {", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", + " },", + ");", "" ], "type": "text/javascript" @@ -4288,36 +4259,70 @@ } ], "request": { - "method": "GET", + "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}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", - ":id" + "payments", + ":id", + "confirm" ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "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-copy", + "name": "Payments - Retrieve", "event": [ { "listen": "test", @@ -4385,20 +4390,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// 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 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"refunds\"", - "pm.test(\"[POST]::/payments - Content check if 'refunds' exists\", function () {", - " pm.expect(typeof jsonData.refunds !== \"undefined\").to.be.true;", - "});", "" ], "type": "text/javascript" From 9a0e637b0fe2cfed70dbe56188a0f3ee082232d4 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:57:51 +0000 Subject: [PATCH 06/21] chore(version): v1.49.0 --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b10db2403b5..c74b50eb0af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.49.0 (2023-10-03) + +### Features + +- **connector:** [Nuvei] Add order id as the reference id ([#2408](https://github.com/juspay/hyperswitch/pull/2408)) ([`d5d876b`](https://github.com/juspay/hyperswitch/commit/d5d876b821187648994ea53c358467966e99cd23)) +- **pm_auth:** Added pm_auth_config to merchant_connector_account ([#2183](https://github.com/juspay/hyperswitch/pull/2183)) ([`abfdea2`](https://github.com/juspay/hyperswitch/commit/abfdea20b06a8804ec83fe9431f9a034465bb924)) +- **pm_list:** [Trustpay] add bank_redirect - blik pm type required field info for trustpay ([#2390](https://github.com/juspay/hyperswitch/pull/2390)) ([`d81762a`](https://github.com/juspay/hyperswitch/commit/d81762a8b430ca1f197d7dabb26167f54e235735)) +- **webhooks:** Webhooks effect tracker ([#2260](https://github.com/juspay/hyperswitch/pull/2260)) ([`5048d24`](https://github.com/juspay/hyperswitch/commit/5048d248e59b8ecaf8585ffd5134953cf62e74ef)) + +### Bug Fixes + +- **CI:** Fix spell check for CI pull request ([#2420](https://github.com/juspay/hyperswitch/pull/2420)) ([`3b10b1c`](https://github.com/juspay/hyperswitch/commit/3b10b1c473209e36183271a81eb9014a8f5cddfa)) +- **cards:** Allow card cvc 000 ([#2387](https://github.com/juspay/hyperswitch/pull/2387)) ([`f0dc374`](https://github.com/juspay/hyperswitch/commit/f0dc37438b7a6c4b25acff941aca13545217d307)) +- **configs:** Add `lock_settings` in `docker_compose.toml` ([#2396](https://github.com/juspay/hyperswitch/pull/2396)) ([`14fec5c`](https://github.com/juspay/hyperswitch/commit/14fec5c3980397079fe8861caca589157a8ba242)) +- **connector:** [noon] add connector_auth params and update description ([#2429](https://github.com/juspay/hyperswitch/pull/2429)) ([`0aa6b30`](https://github.com/juspay/hyperswitch/commit/0aa6b30d2c9056e9a21a88bdc064daa7e8659bd6)) +- **payment_methos:** prioritized `apple_pay_combined` deserialization over `apple_pay` ([#2393](https://github.com/juspay/hyperswitch/pull/2393)) ([`f12ce9c`](https://github.com/juspay/hyperswitch/commit/f12ce9c72d94674e0ae0ec7f1c91d8b5c43481e8)) +- Temp support for ach gocardless with existing api contracts ([#2395](https://github.com/juspay/hyperswitch/pull/2395)) ([`d43fbcc`](https://github.com/juspay/hyperswitch/commit/d43fbccd54011d0de6f8d39adbd264d9ada77e7e)) + +### Refactors + +- **connector:** + - [Klarna] Expand wildcard match arms ([#2403](https://github.com/juspay/hyperswitch/pull/2403)) ([`89cb63b`](https://github.com/juspay/hyperswitch/commit/89cb63be3328010d26b5f6322449fc50e80593e4)) + - [Klarna] Enhance currency Mapping with ConnectorCurrencyCommon Trait ([#2414](https://github.com/juspay/hyperswitch/pull/2414)) ([`ee7efd0`](https://github.com/juspay/hyperswitch/commit/ee7efd05adbe14bab1d2862d7ab2bf244c226433)) + - [Cryptopay] Update PSync with connector_request_reference_id ([#2388](https://github.com/juspay/hyperswitch/pull/2388)) ([`3680541`](https://github.com/juspay/hyperswitch/commit/36805411772da00719a716d05c650f10ca990d49)) +- **router:** Add `#[cfg(not(feature = "kms"))]` feature flag to test the simplified apple pay flow locally ([#2200](https://github.com/juspay/hyperswitch/pull/2200)) ([`e5ad9c5`](https://github.com/juspay/hyperswitch/commit/e5ad9c5c35f386486afedded90c46793196a17d0)) + +### Testing + +- **postman:** Update postman collection files ([`34099ba`](https://github.com/juspay/hyperswitch/commit/34099baa2ec2f73598c4433b0a481dec3fde8c05)) + +### Documentation + +- **README:** + - Include Hacktoberfest information ([#2386](https://github.com/juspay/hyperswitch/pull/2386)) ([`e8eb929`](https://github.com/juspay/hyperswitch/commit/e8eb929d5b4d99d09940532e3abbca2b811bcf36)) + - Fixed TOC links ([#2402](https://github.com/juspay/hyperswitch/pull/2402)) ([`c81d8e9`](https://github.com/juspay/hyperswitch/commit/c81d8e9a180da8f71d156d39c9f85847f6d7a572)) + +### Miscellaneous Tasks + +- **deps:** Bump webpki from 0.22.0 to 0.22.2 ([#2419](https://github.com/juspay/hyperswitch/pull/2419)) ([`6bf0e75`](https://github.com/juspay/hyperswitch/commit/6bf0e75b69608ea07fd7601906982a19cdc81400)) + +**Full Changelog:** [`v1.48.1+hotfix.1...v1.49.0`](https://github.com/juspay/hyperswitch/compare/v1.48.1+hotfix.1...v1.49.0) + +- - - + + ## 1.48.1 (2023-09-28) ### Bug Fixes From 35f7ce0f4d9e16184e2bb94360d3ced60f8b5af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hudson=20C=2E=20Dalpr=C3=A1?= Date: Wed, 4 Oct 2023 07:05:30 +1300 Subject: [PATCH 07/21] fix(router): merchant account delete does not delete the merchant_key_store (#2367) --- .../src/query/merchant_key_store.rs | 12 ++++ crates/router/src/core/admin.rs | 10 +++- crates/router/src/db/merchant_key_store.rs | 56 ++++++++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/crates/diesel_models/src/query/merchant_key_store.rs b/crates/diesel_models/src/query/merchant_key_store.rs index 02a31a1f397d..27ec3be9fcd0 100644 --- a/crates/diesel_models/src/query/merchant_key_store.rs +++ b/crates/diesel_models/src/query/merchant_key_store.rs @@ -27,4 +27,16 @@ impl MerchantKeyStore { ) .await } + + #[instrument(skip(conn))] + pub async fn delete_by_merchant_id( + conn: &PgPooledConn, + merchant_id: &str, + ) -> StorageResult { + generics::generic_delete::<::Table, _>( + conn, + dsl::merchant_id.eq(merchant_id.to_owned()), + ) + .await + } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index fab416c431ba..dabe22da626e 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -465,11 +465,19 @@ pub async fn merchant_account_delete( state: AppState, merchant_id: String, ) -> RouterResponse { + let mut is_deleted = false; let db = state.store.as_ref(); - let is_deleted = db + let is_merchant_account_deleted = db .delete_merchant_account_by_merchant_id(&merchant_id) .await .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + if is_merchant_account_deleted { + let is_merchant_key_store_deleted = db + .delete_merchant_key_store_by_merchant_id(&merchant_id) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + is_deleted = is_merchant_account_deleted && is_merchant_key_store_deleted; + } let response = api::MerchantAccountDeleteResponse { merchant_id, deleted: is_deleted, diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 8cb93fc369b4..970e2b770324 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -1,7 +1,7 @@ use error_stack::{IntoReport, ResultExt}; use masking::Secret; #[cfg(feature = "accounts_cache")] -use storage_impl::redis::cache::ACCOUNTS_CACHE; +use storage_impl::redis::cache::{CacheKind, ACCOUNTS_CACHE}; use crate::{ connection, @@ -27,6 +27,11 @@ pub trait MerchantKeyStoreInterface { merchant_id: &str, key: &Secret>, ) -> CustomResult; + + async fn delete_merchant_key_store_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult; } #[async_trait::async_trait] @@ -66,6 +71,7 @@ impl MerchantKeyStoreInterface for Store { .map_err(Into::into) .into_report() }; + #[cfg(not(feature = "accounts_cache"))] { fetch_func() @@ -90,6 +96,38 @@ impl MerchantKeyStoreInterface for Store { .change_context(errors::StorageError::DecryptionError) } } + + async fn delete_merchant_key_store_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult { + let delete_func = || async { + let conn = connection::pg_connection_write(self).await?; + diesel_models::merchant_key_store::MerchantKeyStore::delete_by_merchant_id( + &conn, + merchant_id, + ) + .await + .map_err(Into::into) + .into_report() + }; + + #[cfg(not(feature = "accounts_cache"))] + { + delete_func().await + } + + #[cfg(feature = "accounts_cache")] + { + let key_store_cache_key = format!("merchant_key_store_{}", merchant_id); + super::cache::publish_and_redact( + self, + CacheKind::Accounts(key_store_cache_key.into()), + delete_func, + ) + .await + } + } } #[async_trait::async_trait] @@ -140,6 +178,22 @@ impl MerchantKeyStoreInterface for MockDb { .await .change_context(errors::StorageError::DecryptionError) } + + async fn delete_merchant_key_store_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult { + let mut merchant_key_stores = self.merchant_key_store.lock().await; + let index = merchant_key_stores + .iter() + .position(|mks| mks.merchant_id == merchant_id) + .ok_or(errors::StorageError::ValueNotFound(format!( + "No merchant key store found for merchant_id = {}", + merchant_id + )))?; + merchant_key_stores.remove(index); + Ok(true) + } } #[cfg(test)] From 099b241096c69879a805ca81b1c5a23118e10b52 Mon Sep 17 00:00:00 2001 From: ManobhavSachan <94208034+ManobhavSachan@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:52:00 +0530 Subject: [PATCH 08/21] feat(connector): [Stax] Use connector_response_reference_id as reference to merchant (#2415) --- crates/router/src/connector/stax/transformers.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/stax/transformers.rs b/crates/router/src/connector/stax/transformers.rs index 42c7c02a363b..4ee28be19375 100644 --- a/crates/router/src/connector/stax/transformers.rs +++ b/crates/router/src/connector/stax/transformers.rs @@ -23,6 +23,7 @@ pub struct StaxPaymentsRequest { is_refundable: bool, pre_auth: bool, meta: StaxPaymentsRequestMetaData, + idempotency_id: Option, } impl TryFrom<&types::PaymentsAuthorizeRouterData> for StaxPaymentsRequest { @@ -51,6 +52,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for StaxPaymentsRequest { Err(errors::ConnectorError::InvalidWalletToken)? } }), + idempotency_id: Some(item.connector_request_reference_id.clone()), }) } api::PaymentMethodData::BankDebit( @@ -69,6 +71,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for StaxPaymentsRequest { Err(errors::ConnectorError::InvalidWalletToken)? } }), + idempotency_id: Some(item.connector_request_reference_id.clone()), }) } api::PaymentMethodData::BankDebit(_) @@ -282,6 +285,7 @@ pub struct StaxPaymentsResponse { child_captures: Vec, #[serde(rename = "type")] payment_response_type: StaxPaymentResponseTypes, + idempotency_id: Option, } #[derive(Debug, Deserialize, Serialize)] @@ -323,12 +327,14 @@ impl Ok(Self { 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, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some( + item.response.idempotency_id.unwrap_or(item.response.id), + ), }), ..item.data }) From 422523848e6516643a6beef1ba15af4e967f0c5b Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:16:53 +0530 Subject: [PATCH 09/21] build(deps): address `undeclared crate or module` errors on Windows for `scheduler` crate (#2411) --- Cargo.lock | 109 +++------------------------------- crates/scheduler/Cargo.toml | 47 +++++---------- crates/scheduler/src/utils.rs | 7 --- 3 files changed, 22 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cca3b6f58d98..0275312161ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,18 +471,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-bb8-diesel" -version = "0.1.0" -source = "git+https://github.com/juspay/async-bb8-diesel?rev=9a71d142726dbc33f41c1fd935ddaa79841c7be5#9a71d142726dbc33f41c1fd935ddaa79841c7be5" -dependencies = [ - "async-trait", - "bb8", - "diesel", - "thiserror", - "tokio", -] - [[package]] name = "async-bb8-diesel" version = "0.1.0" @@ -735,39 +723,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-sdk-s3" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392b9811ca489747ac84349790e49deaa1f16631949e7dd4156000251c260eae" -dependencies = [ - "aws-credential-types", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-checksums", - "aws-smithy-client", - "aws-smithy-eventstream", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-json", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "bytes", - "http", - "http-body", - "once_cell", - "percent-encoding", - "regex", - "tokio-stream", - "tower", - "tracing", - "url", -] - [[package]] name = "aws-sdk-s3" version = "0.28.0" @@ -1853,9 +1808,9 @@ dependencies = [ name = "diesel_models" version = "0.1.0" dependencies = [ - "async-bb8-diesel 0.1.0 (git+https://github.com/oxidecomputer/async-bb8-diesel?rev=be3d9bce50051d8c0e0c06078e8066cc27db3001)", + "async-bb8-diesel", "aws-config", - "aws-sdk-s3 0.28.0", + "aws-sdk-s3", "common_enums", "common_utils", "diesel", @@ -1940,7 +1895,7 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" name = "drainer" version = "0.1.0" dependencies = [ - "async-bb8-diesel 0.1.0 (git+https://github.com/oxidecomputer/async-bb8-diesel?rev=be3d9bce50051d8c0e0c06078e8066cc27db3001)", + "async-bb8-diesel", "bb8", "clap", "common_utils", @@ -1988,20 +1943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime 2.1.0", - "is-terminal", + "humantime", "log", "regex", "termcolor", @@ -2589,12 +2531,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.27" @@ -2779,18 +2715,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -3680,7 +3604,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "env_logger 0.7.1", + "env_logger", "log", ] @@ -4134,11 +4058,11 @@ dependencies = [ "actix-rt", "actix-web", "api_models", - "async-bb8-diesel 0.1.0 (git+https://github.com/oxidecomputer/async-bb8-diesel?rev=be3d9bce50051d8c0e0c06078e8066cc27db3001)", + "async-bb8-diesel", "async-trait", "awc", "aws-config", - "aws-sdk-s3 0.28.0", + "aws-sdk-s3", "base64 0.21.2", "bb8", "blake3", @@ -4428,36 +4352,19 @@ dependencies = [ name = "scheduler" version = "0.1.0" dependencies = [ - "actix-multipart", - "actix-rt", - "actix-web", - "api_models", - "async-bb8-diesel 0.1.0 (git+https://github.com/juspay/async-bb8-diesel?rev=9a71d142726dbc33f41c1fd935ddaa79841c7be5)", "async-trait", - "aws-config", - "aws-sdk-s3 0.25.1", - "cards", - "clap", "common_utils", - "diesel", "diesel_models", - "dyn-clone", - "env_logger 0.10.0", "error-stack", "external_services", - "frunk", - "frunk_core", "futures", - "infer 0.13.0", "masking", "once_cell", "rand 0.8.5", "redis_interface", - "router_derive", "router_env", "serde", "serde_json", - "signal-hook", "signal-hook-tokio", "storage_impl", "strum 0.24.1", @@ -4824,7 +4731,7 @@ version = "0.1.0" dependencies = [ "actix-web", "api_models", - "async-bb8-diesel 0.1.0 (git+https://github.com/oxidecomputer/async-bb8-diesel?rev=be3d9bce50051d8c0e0c06078e8066cc27db3001)", + "async-bb8-diesel", "async-trait", "bb8", "bytes", diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index 3e7490347381..7ce61d9f59f4 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -9,51 +9,32 @@ olap = [] kv_store = [] [dependencies] -async-bb8-diesel = { git = "https://github.com/juspay/async-bb8-diesel", rev = "9a71d142726dbc33f41c1fd935ddaa79841c7be5" } -clap = { version = "4.2.2", default-features = false, features = ["std", "derive", "help", "usage"] } -diesel = { version = "2.0.3", features = ["postgres", "serde_json", "time"] } +# Third party crates +async-trait = "0.1.68" error-stack = "0.3.1" -frunk = "0.4.1" -frunk_core = "0.4.1" futures = "0.3.28" -once_cell = "1.17.1" -serde = "1.0.159" -serde_json = "1.0.91" -strum = { version = "0.24.1", features = ["derive"] } -time = { version = "0.3.20", features = ["serde", "serde-well-known", "std"] } -env_logger = "0.10.0" +once_cell = "1.18.0" rand = "0.8.5" -signal-hook = "0.3.15" -uuid = { version = "1.3.1", features = ["serde", "v4"] } +serde = "1.0.163" +serde_json = "1.0.96" +strum = { version = "0.24.1", features = ["derive"] } +thiserror = "1.0.40" +time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } +tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +uuid = { version = "1.3.3", features = ["serde", "v4"] } # First party crates -api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext"] } -cards = { version = "0.1.0", path = "../cards" } +diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } external_services = { version = "0.1.0", path = "../external_services" } masking = { version = "0.1.0", path = "../masking" } redis_interface = { version = "0.1.0", path = "../redis_interface" } -router_derive = { version = "0.1.0", path = "../router_derive" } -storage_impl = { version = "0.1.0", path = "../storage_impl" , default-features = false } router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } -diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } -actix-multipart = "0.6.0" -aws-sdk-s3 = { version = "0.25.0", optional = true } -aws-config = {version = "0.55.1", optional = true } -infer = "0.13.0" +storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } [target.'cfg(not(target_os = "windows"))'.dependencies] -signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"]} - -# Third party crates -actix-rt = "2.8.0" -actix-web = "4.3.1" -thiserror = "1.0.39" -async-trait = "0.1.66" -dyn-clone = "1.0.11" -tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } - +signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] } # [[bin]] # name = "scheduler" -# path = "src/bin/scheduler.rs" \ No newline at end of file +# path = "src/bin/scheduler.rs" diff --git a/crates/scheduler/src/utils.rs b/crates/scheduler/src/utils.rs index d8b9b0fc46bb..676ef330f9d1 100644 --- a/crates/scheduler/src/utils.rs +++ b/crates/scheduler/src/utils.rs @@ -377,10 +377,3 @@ where Ok(()) } } - -#[cfg(target_os = "windows")] -pub(crate) async fn signal_handler( - _sig: common_utils::signals::DummySignal, - _sender: oneshot::Sender<()>, -) { -} From 04f2e11cd4f3fd327408cddec36ccf4fb486b935 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:26:52 +0530 Subject: [PATCH 10/21] fix(CI): fix spell check for CI pull request (#2439) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c74b50eb0af6..8924ba3ecc15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ All notable changes to HyperSwitch will be documented here. - **cards:** Allow card cvc 000 ([#2387](https://github.com/juspay/hyperswitch/pull/2387)) ([`f0dc374`](https://github.com/juspay/hyperswitch/commit/f0dc37438b7a6c4b25acff941aca13545217d307)) - **configs:** Add `lock_settings` in `docker_compose.toml` ([#2396](https://github.com/juspay/hyperswitch/pull/2396)) ([`14fec5c`](https://github.com/juspay/hyperswitch/commit/14fec5c3980397079fe8861caca589157a8ba242)) - **connector:** [noon] add connector_auth params and update description ([#2429](https://github.com/juspay/hyperswitch/pull/2429)) ([`0aa6b30`](https://github.com/juspay/hyperswitch/commit/0aa6b30d2c9056e9a21a88bdc064daa7e8659bd6)) -- **payment_methos:** prioritized `apple_pay_combined` deserialization over `apple_pay` ([#2393](https://github.com/juspay/hyperswitch/pull/2393)) ([`f12ce9c`](https://github.com/juspay/hyperswitch/commit/f12ce9c72d94674e0ae0ec7f1c91d8b5c43481e8)) +- **payment_methods:** prioritized `apple_pay_combined` deserialization over `apple_pay` ([#2393](https://github.com/juspay/hyperswitch/pull/2393)) ([`f12ce9c`](https://github.com/juspay/hyperswitch/commit/f12ce9c72d94674e0ae0ec7f1c91d8b5c43481e8)) - Temp support for ach gocardless with existing api contracts ([#2395](https://github.com/juspay/hyperswitch/pull/2395)) ([`d43fbcc`](https://github.com/juspay/hyperswitch/commit/d43fbccd54011d0de6f8d39adbd264d9ada77e7e)) ### Refactors From f720aecf1fb676cec71e636b877a46f9791d713a Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:01:15 +0530 Subject: [PATCH 11/21] feat(router): remove unnecessary lookups in refund and payment_attempt kv flow (#2425) --- crates/router/src/db/refund.rs | 11 +++-------- crates/storage_impl/src/payments/payment_attempt.rs | 7 +++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index 67980730f8fe..93227d718460 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -498,14 +498,9 @@ mod storage { .into_report() } enums::MerchantStorageScheme::RedisKv => { - let key = format!("{}_{}", this.merchant_id, this.refund_id); - + let key = format!("{}_{}", this.merchant_id, this.payment_id); + let field = format!("pa_{}_ref_{}", &this.attempt_id, &this.refund_id); let updated_refund = refund.clone().apply_changeset(this.clone()); - // Check for database presence as well Maybe use a read replica here ? - - let lookup = self.get_lookup_by_lookup_id(&key).await?; - - let field = &lookup.sk_id; let redis_value = utils::Encode::::encode_to_string_of_json( @@ -515,7 +510,7 @@ mod storage { self.get_redis_conn() .map_err(Into::::into)? - .set_hash_fields(&lookup.pk_id, (field, redis_value)) + .set_hash_fields(&key, (field, redis_value)) .await .change_context(errors::StorageError::KVError)?; diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 693772e89b4e..954da5d8c048 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -669,16 +669,15 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{attempt_id}"); - let lookup = self.get_lookup_by_lookup_id(&lookup_id).await?; - let key = &lookup.pk_id; + let key = format!("{merchant_id}_{payment_id}"); + let field = format!("pa_{attempt_id}"); try_redis_get_else_try_database_get( self.get_redis_conn() .map_err(|er| { let error = format!("{}", er); er.change_context(errors::StorageError::RedisError(error)) })? - .get_hash_field_and_deserialize(key, &lookup.sk_id, "PaymentAttempt"), + .get_hash_field_and_deserialize(&key, &field, "PaymentAttempt"), || async { self.router_store .find_payment_attempt_by_payment_id_merchant_id_attempt_id( From 0d703c7ab85c68f433767a70a0feabe8daa4f24c Mon Sep 17 00:00:00 2001 From: Kritika Sharma Date: Wed, 4 Oct 2023 13:19:18 +0530 Subject: [PATCH 12/21] feat(connector): [PowerTranz] Use connector_response_reference_id as reference to merchant (#2413) Co-authored-by: kritiksh --- crates/router/src/connector/powertranz/transformers.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/router/src/connector/powertranz/transformers.rs b/crates/router/src/connector/powertranz/transformers.rs index 4703a81f30fa..4032f8019b09 100644 --- a/crates/router/src/connector/powertranz/transformers.rs +++ b/crates/router/src/connector/powertranz/transformers.rs @@ -123,7 +123,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PowertranzPaymentsRequest .to_string(), three_d_secure, source, - order_identifier: item.payment_id.clone(), + order_identifier: item.connector_request_reference_id.clone(), // billing_address, // shipping_address, extended_data, @@ -239,6 +239,7 @@ pub struct PowertranzBaseResponse { iso_response_code: String, redirect_data: Option, response_message: String, + order_identifier: String, } impl ForeignFrom<(u8, bool, bool)> for enums::AttemptStatus { @@ -297,7 +298,7 @@ impl let connector_transaction_id = item .response .original_trxn_identifier - .unwrap_or(item.response.transaction_identifier); + .unwrap_or(item.response.transaction_identifier.clone()); let redirection_data = item.response .redirect_data @@ -311,7 +312,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.order_identifier), }), Err, ); From 3bfea72df34f4ce0ffdb61e49960fdf09b96eb5a Mon Sep 17 00:00:00 2001 From: Sarang S <85063520+eigengravy@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:24:20 +0530 Subject: [PATCH 13/21] feat(router): [OpenNode] response reference id (#2416) --- crates/router/src/connector/opennode/transformers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index 70a50e1b9222..aa3fae3a5164 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -78,6 +78,7 @@ pub struct OpennodePaymentsResponseData { id: String, hosted_checkout_url: String, status: OpennodePaymentStatus, + order_id: Option, } //TODO: Fill the struct with respective fields @@ -114,7 +115,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.data.order_id, }) } else { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { @@ -125,7 +126,7 @@ impl "Please check the transaction in opennode dashboard and resolve manually" .to_string(), }), - connector_response_reference_id: None, + connector_response_reference_id: item.response.data.order_id, }) }; Ok(Self { From 485c09d16743d73b446d6313f0ee6462c8a77028 Mon Sep 17 00:00:00 2001 From: Lab Rat <35325046+rootCircle@users.noreply.github.com> Date: Wed, 4 Oct 2023 07:55:54 +0000 Subject: [PATCH 14/21] feat(connector): [Payeezy] Use connector_response_reference_id as reference to merchant (#2410) --- crates/router/src/connector/payeezy/transformers.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/router/src/connector/payeezy/transformers.rs index d6c22ba67c31..98e8ea12c00d 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/router/src/connector/payeezy/transformers.rs @@ -67,6 +67,7 @@ pub struct PayeezyPaymentsRequest { pub currency_code: String, pub credit_card: PayeezyPaymentMethod, pub stored_credentials: Option, + pub reference: String, } #[derive(Serialize, Debug)] @@ -118,6 +119,7 @@ fn get_card_specific_payment_data( currency_code, credit_card, stored_credentials, + reference: item.connector_request_reference_id.clone(), }) } fn get_transaction_type_and_stored_creds( @@ -252,6 +254,7 @@ pub struct PayeezyPaymentsResponse { pub gateway_resp_code: String, pub gateway_message: String, pub stored_credentials: Option, + pub reference: Option, } #[derive(Debug, Deserialize)] @@ -354,13 +357,17 @@ impl status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transaction_id, + item.response.transaction_id.clone(), ), redirection_data: None, mandate_reference, connector_metadata: metadata, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some( + item.response + .reference + .unwrap_or(item.response.transaction_id), + ), }), ..item.data }) From d177b4d94f08fb8ef44b5c07ec1bdc771baa016d Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:20:36 +0530 Subject: [PATCH 15/21] refactor(config): update payment method filter for apple pay (#2423) Co-authored-by: Prajjwal Kumar --- config/config.example.toml | 2 +- config/development.toml | 4 ++-- config/docker_compose.toml | 2 +- loadtest/config/development.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index d4b44a062a08..3fe9c19f4411 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -214,7 +214,7 @@ zen.secondary_base_url = "https://secure.zen-test.com/" #Payment Method Filters Based on Country and Currency [pm_filters.default] -apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } # Bank redirect configs for allowed banks through online_banking_czech_republic payment method [bank_config.online_banking_czech_republic] diff --git a/config/development.toml b/config/development.toml index 26a7251dc9fb..941b9d9524d6 100644 --- a/config/development.toml +++ b/config/development.toml @@ -229,7 +229,7 @@ adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_ba [pm_filters.default] google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } -apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } paypal = { currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } klarna = { country = "AT,BE,DK,FI,FR,DE,IE,IT,NL,NO,ES,SE,GB,US,CA", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } affirm = { country = "US", currency = "USD" } @@ -241,7 +241,7 @@ ideal = { country = "NL", currency = "EUR" } [pm_filters.stripe] google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } -apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA" } klarna = { country = "US", currency = "USD" } affirm = { country = "US", currency = "USD" } afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "USD,CAD,GBP,AUD,NZD" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ca6b9eeda5b0..8a7b96b4e6d2 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -132,7 +132,7 @@ zen.secondary_base_url = "https://secure.zen-test.com/" [pm_filters.default] -apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } [connectors.supported] wallets = ["klarna", "braintree", "applepay"] diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 1b413b1cdf8c..6f3b53fba040 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -117,7 +117,7 @@ zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" [pm_filters.default] -apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } [connectors.supported] wallets = ["klarna", "braintree", "applepay"] From 6e5ab0d121d6345f18bccc7f917064caa2737475 Mon Sep 17 00:00:00 2001 From: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:37:37 +0530 Subject: [PATCH 16/21] refactor(payment_methods): add `requires_cvv` config while creating merchant account (#2431) --- crates/router/src/core/admin.rs | 11 +++++++++++ crates/router/src/core/payment_methods/cards.rs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index dabe22da626e..c7009bf4cc98 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -219,6 +219,17 @@ pub async fn create_merchant_account( .insert_merchant(merchant_account, &key_store) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?; + + db.insert_config(diesel_models::configs::ConfigNew { + key: format!("{}_requires_cvv", merchant_account.merchant_id), + config: "true".to_string(), + }) + .await + .map_err(|err| { + crate::logger::error!("Error while setting requires_cvv config: {err:?}"); + }) + .ok(); + Ok(service_api::ApplicationResponse::Json( merchant_account .try_into() diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index c22b7323095b..20a3e130eb25 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1831,7 +1831,7 @@ pub async fn list_customer_payment_method( ) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant_id config for requires_cvv")?; + .attach_printable("Failed to fetch requires_cvv config")?; let requires_cvv = is_requires_cvv.config != "false"; From 591c9b70d9b7f8df5d7f5d2cb2d19cfaa1457fe1 Mon Sep 17 00:00:00 2001 From: Vedant Khairnar Date: Wed, 4 Oct 2023 18:34:56 +0530 Subject: [PATCH 17/21] feat(connector): [Square] Use reference_id as reference to merchant (#2434) Co-authored-by: Vedant Khairnar --- crates/router/src/connector/square/transformers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/connector/square/transformers.rs b/crates/router/src/connector/square/transformers.rs index ff33914c4fd5..01ed507bf34a 100644 --- a/crates/router/src/connector/square/transformers.rs +++ b/crates/router/src/connector/square/transformers.rs @@ -327,6 +327,7 @@ pub struct SquarePaymentsResponseDetails { status: SquarePaymentStatus, id: String, amount_money: SquarePaymentsAmountData, + reference_id: Option, } #[derive(Debug, Deserialize)] pub struct SquarePaymentsResponse { @@ -355,7 +356,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.payment.reference_id, }), amount_captured, ..item.data From 409913fd75076e4ee1dac1e4dc5b2f164528bc23 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:49:34 +0530 Subject: [PATCH 18/21] refactor(webhook): add a function to retrieve payment_id (#2447) Co-authored-by: Abhishek Marrivagu <68317979+Abhicodes-crypto@users.noreply.github.com> --- crates/api_models/src/webhooks.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index a1cafee1f8d9..252e3110d6ab 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -59,6 +59,17 @@ pub enum WebhookResponseTracker { NoEffect, } +impl WebhookResponseTracker { + pub fn get_payment_id(&self) -> Option { + match self { + Self::Payment { payment_id, .. } + | Self::Refund { payment_id, .. } + | Self::Dispute { payment_id, .. } => Some(payment_id.to_string()), + Self::NoEffect => None, + } + } +} + impl From for WebhookFlow { fn from(evt: IncomingWebhookEvent) -> Self { match evt { From ab2cde799371a66eb045cf8b20431b3b108dac44 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit <64925866+apoorvdixit88@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:39:45 +0530 Subject: [PATCH 19/21] feat(router): add profile id and extra filters in lists (#2379) --- crates/api_models/src/disputes.rs | 2 + crates/api_models/src/payments.rs | 17 ++- crates/api_models/src/refunds.rs | 2 + .../src/payments/payment_attempt.rs | 7 +- .../src/payments/payment_intent.rs | 64 ++++---- .../src/query/payment_attempt.rs | 38 +++++ crates/router/src/core/payments.rs | 6 +- crates/router/src/db/dispute.rs | 1 + crates/router/src/db/refund.rs | 2 + crates/router/src/types/storage/dispute.rs | 3 + crates/router/src/types/storage/refund.rs | 14 ++ .../src/mock_db/payment_attempt.rs | 6 +- .../src/payments/payment_attempt.rs | 29 +++- .../src/payments/payment_intent.rs | 138 +++++++++--------- openapi/openapi_spec.json | 5 + 15 files changed, 226 insertions(+), 108 deletions(-) diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs index 7629eeee8767..1b04e7922782 100644 --- a/crates/api_models/src/disputes.rs +++ b/crates/api_models/src/disputes.rs @@ -104,6 +104,8 @@ pub struct DisputeEvidenceBlock { pub struct DisputeListConstraints { /// limit on the number of objects to return pub limit: Option, + /// The identifier for business profile + pub profile_id: Option, /// status of the dispute pub dispute_status: Option, /// stage of the dispute diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 31fa00cef9ae..5f99e050f678 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1999,10 +1999,13 @@ pub struct PaymentListResponseV2 { } #[derive(Clone, Debug, serde::Deserialize)] -#[serde(deny_unknown_fields)] pub struct PaymentListFilterConstraints { /// The identifier for payment pub payment_id: Option, + /// The identifier for business profile + pub profile_id: Option, + /// The identifier for customer + pub customer_id: Option, /// The limit on the number of objects. The default limit is 10 and max limit is 20 #[serde(default = "default_limit")] pub limit: u32, @@ -2015,10 +2018,14 @@ pub struct PaymentListFilterConstraints { pub connector: Option>, /// The list of currencies to filter payments list pub currency: Option>, - /// The list of payment statuses to filter payments list + /// The list of payment status to filter payments list pub status: Option>, /// The list of payment methods to filter payments list - pub payment_methods: Option>, + pub payment_method: Option>, + /// The list of payment method types to filter payments list + pub payment_method_type: Option>, + /// The list of authentication types to filter payments list + pub authentication_type: Option>, } #[derive(Clone, Debug, serde::Serialize)] pub struct PaymentListFilters { @@ -2030,6 +2037,10 @@ pub struct PaymentListFilters { pub status: Vec, /// The list of available payment method filters pub payment_method: Vec, + /// The list of available payment method types + pub payment_method_type: Vec, + /// The list of available authentication types + pub authentication_type: Vec, } #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)] diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 8914e00826a5..489c5dd1c436 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -132,6 +132,8 @@ pub struct RefundListRequest { pub payment_id: Option, /// The identifier for the refund pub refund_id: Option, + /// The identifier for business profile + pub profile_id: Option, /// Limit on the number of objects to return pub limit: Option, /// The starting point within a list of objects diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index ad877ab01e8f..a372a77f0643 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -79,12 +79,15 @@ pub trait PaymentAttemptInterface { storage_scheme: MerchantStorageScheme, ) -> error_stack::Result; + #[allow(clippy::too_many_arguments)] async fn get_total_count_of_filtered_payment_attempts( &self, merchant_id: &str, active_attempt_ids: &[String], connector: Option>, - payment_methods: Option>, + payment_method: Option>, + payment_method_type: Option>, + authentication_type: Option>, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result; } @@ -147,6 +150,8 @@ pub struct PaymentListFilters { pub currency: Vec, pub status: Vec, pub payment_method: Vec, + pub payment_method_type: Vec, + pub authentication_type: Vec, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index 26c244782a56..b3bf8af8c36b 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -391,57 +391,66 @@ impl From for PaymentIntentUpdateInternal { } pub enum PaymentIntentFetchConstraints { - Single { - payment_intent_id: String, - }, - List { - offset: u32, - starting_at: Option, - ending_at: Option, - connector: Option>, - currency: Option>, - status: Option>, - payment_methods: Option>, - customer_id: Option, - starting_after_id: Option, - ending_before_id: Option, - limit: Option, - }, + Single { payment_intent_id: String }, + List(Box), +} + +pub struct PaymentIntentListParams { + pub offset: u32, + pub starting_at: Option, + pub ending_at: Option, + pub connector: Option>, + pub currency: Option>, + pub status: Option>, + pub payment_method: Option>, + pub payment_method_type: Option>, + pub authentication_type: Option>, + pub profile_id: Option, + pub customer_id: Option, + pub starting_after_id: Option, + pub ending_before_id: Option, + pub limit: Option, } impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListConstraints) -> Self { - Self::List { + Self::List(Box::new(PaymentIntentListParams { offset: 0, starting_at: value.created_gte.or(value.created_gt).or(value.created), ending_at: value.created_lte.or(value.created_lt).or(value.created), connector: None, currency: None, status: None, - payment_methods: None, + payment_method: None, + payment_method_type: None, + authentication_type: None, + profile_id: None, customer_id: value.customer_id, starting_after_id: value.starting_after, ending_before_id: value.ending_before, limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V1)), - } + })) } } impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::TimeRange) -> Self { - Self::List { + Self::List(Box::new(PaymentIntentListParams { offset: 0, starting_at: Some(value.start_time), ending_at: value.end_time, connector: None, currency: None, status: None, - payment_methods: None, + payment_method: None, + payment_method_type: None, + authentication_type: None, + profile_id: None, customer_id: None, starting_after_id: None, ending_before_id: None, limit: None, - } + })) } } @@ -450,19 +459,22 @@ impl From for PaymentIntentF if let Some(payment_intent_id) = value.payment_id { Self::Single { payment_intent_id } } else { - Self::List { + Self::List(Box::new(PaymentIntentListParams { offset: value.offset.unwrap_or_default(), starting_at: value.time_range.map(|t| t.start_time), ending_at: value.time_range.and_then(|t| t.end_time), connector: value.connector, currency: value.currency, status: value.status, - payment_methods: value.payment_methods, - customer_id: None, + payment_method: value.payment_method, + payment_method_type: value.payment_method_type, + authentication_type: value.authentication_type, + profile_id: value.profile_id, + customer_id: value.customer_id, starting_after_id: None, ending_before_id: None, limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V2)), - } + })) } } } diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index c22151d38037..8e2fc729dbf3 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -216,6 +216,8 @@ impl PaymentAttempt { Vec, Vec, Vec, + Vec, + Vec, )> { let active_attempts: Vec = pi .iter() @@ -272,11 +274,39 @@ impl PaymentAttempt { .flatten() .collect::>(); + let filter_payment_method_type = filter + .clone() + .select(dsl::payment_method_type) + .distinct() + .get_results_async::>(conn) + .await + .into_report() + .change_context(DatabaseError::Others) + .attach_printable("Error filtering records by payment method type")? + .into_iter() + .flatten() + .collect::>(); + + let filter_authentication_type = filter + .clone() + .select(dsl::authentication_type) + .distinct() + .get_results_async::>(conn) + .await + .into_report() + .change_context(DatabaseError::Others) + .attach_printable("Error filtering records by authentication type")? + .into_iter() + .flatten() + .collect::>(); + Ok(( filter_connector, filter_currency, intent_status, filter_payment_method, + filter_payment_method_type, + filter_authentication_type, )) } pub async fn get_total_count_of_attempts( @@ -285,6 +315,8 @@ impl PaymentAttempt { active_attempt_ids: &[String], connector: Option>, payment_method: Option>, + payment_method_type: Option>, + authentication_type: Option>, ) -> StorageResult { let mut filter = ::table() .count() @@ -299,6 +331,12 @@ impl PaymentAttempt { if let Some(payment_method) = payment_method.clone() { filter = filter.filter(dsl::payment_method.eq_any(payment_method)); } + if let Some(payment_method_type) = payment_method_type.clone() { + filter = filter.filter(dsl::payment_method_type.eq_any(payment_method_type)); + } + if let Some(authentication_type) = authentication_type.clone() { + filter = filter.filter(dsl::authentication_type.eq_any(authentication_type)); + } router_env::logger::debug!(query = %debug_query::(&filter).to_string()); db_metrics::track_database_call::<::Table, _, _>( diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 708e20d20504..1b6d93596f14 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1651,7 +1651,9 @@ pub async fn apply_filters_on_payments( &merchant.merchant_id, &active_attempt_ids, constraints.connector, - constraints.payment_methods, + constraints.payment_method, + constraints.payment_method_type, + constraints.authentication_type, merchant.storage_scheme, ) .await @@ -1698,6 +1700,8 @@ pub async fn get_filters_for_payments( currency: filters.currency, status: filters.status, payment_method: filters.payment_method, + payment_method_type: filters.payment_method_type, + authentication_type: filters.authentication_type, }, )) } diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs index 71f47872c09e..42fb85ad16d5 100644 --- a/crates/router/src/db/dispute.rs +++ b/crates/router/src/db/dispute.rs @@ -562,6 +562,7 @@ mod tests { received_time_gt: None, received_time_lte: None, received_time_gte: None, + profile_id: None, }, ) .await diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index 93227d718460..ccbd6a5b39df 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -902,6 +902,7 @@ impl RefundInterface for MockDb { .clone() .map_or(true, |id| id == refund.refund_id) }) + .filter(|refund| refund_details.profile_id == refund.profile_id) .filter(|refund| { refund.created_at >= refund_details.time_range.map_or( @@ -1025,6 +1026,7 @@ impl RefundInterface for MockDb { .clone() .map_or(true, |id| id == refund.refund_id) }) + .filter(|refund| refund_details.profile_id == refund.profile_id) .filter(|refund| { refund.created_at >= refund_details.time_range.map_or( diff --git a/crates/router/src/types/storage/dispute.rs b/crates/router/src/types/storage/dispute.rs index 7b66df6b05f5..84a1f748bee9 100644 --- a/crates/router/src/types/storage/dispute.rs +++ b/crates/router/src/types/storage/dispute.rs @@ -28,6 +28,9 @@ impl DisputeDbExt for Dispute { .order(dsl::modified_at.desc()) .into_boxed(); + if let Some(profile_id) = dispute_list_constraints.profile_id { + filter = filter.filter(dsl::profile_id.eq(profile_id)); + } if let Some(received_time) = dispute_list_constraints.received_time { filter = filter.filter(dsl::created_at.eq(received_time)); } diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 17d94c6b6788..bdfa8dc5b5ff 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -67,6 +67,17 @@ impl RefundDbExt for Refund { filter = filter.limit(limit).offset(offset); } }; + match &refund_list_details.profile_id { + Some(profile_id) => { + filter = filter + .filter(dsl::profile_id.eq(profile_id.to_owned())) + .limit(limit) + .offset(offset); + } + None => { + filter = filter.limit(limit).offset(offset); + } + }; if let Some(time_range) = refund_list_details.time_range { filter = filter.filter(dsl::created_at.ge(time_range.start_time)); @@ -175,6 +186,9 @@ impl RefundDbExt for Refund { if let Some(ref_id) = &refund_list_details.refund_id { filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned())); } + if let Some(profile_id) = &refund_list_details.profile_id { + filter = filter.filter(dsl::profile_id.eq(profile_id.to_owned())); + } if let Some(time_range) = refund_list_details.time_range { filter = filter.filter(dsl::created_at.ge(time_range.start_time)); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index cd1e2f29b4cb..e74de71c8525 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -1,4 +1,4 @@ -use api_models::enums::{Connector, PaymentMethod}; +use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; use common_utils::errors::CustomResult; use data_models::{ errors::StorageError, @@ -39,7 +39,9 @@ impl PaymentAttemptInterface for MockDb { _merchant_id: &str, _active_attempt_ids: &[String], _connector: Option>, - _payment_methods: Option>, + _payment_method: Option>, + _payment_method_type: Option>, + _authentication_type: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { Err(StorageError::MockDbError)? diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 954da5d8c048..e4d3973918e9 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,4 +1,4 @@ -use api_models::enums::{Connector, PaymentMethod}; +use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; use common_utils::errors::CustomResult; use data_models::{ errors, @@ -176,11 +176,20 @@ impl PaymentAttemptInterface for RouterStore { er.change_context(new_err) }) .map( - |(connector, currency, status, payment_method)| PaymentListFilters { + |( connector, currency, status, payment_method, + payment_method_type, + authentication_type, + )| PaymentListFilters { + connector, + currency, + status, + payment_method, + payment_method_type, + authentication_type, }, ) } @@ -248,7 +257,9 @@ impl PaymentAttemptInterface for RouterStore { merchant_id: &str, active_attempt_ids: &[String], connector: Option>, - payment_methods: Option>, + payment_method: Option>, + payment_method_type: Option>, + authentication_type: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = self @@ -269,7 +280,9 @@ impl PaymentAttemptInterface for RouterStore { merchant_id, active_attempt_ids, connector_strings, - payment_methods, + payment_method, + payment_method_type, + authentication_type, ) .await .map_err(|er| { @@ -826,7 +839,9 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &str, active_attempt_ids: &[String], connector: Option>, - payment_methods: Option>, + payment_method: Option>, + payment_method_type: Option>, + authentication_type: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.router_store @@ -834,7 +849,9 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id, active_attempt_ids, connector, - payment_methods, + payment_method, + payment_method_type, + authentication_type, storage_scheme, ) .await diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index b365c6a37810..8352158be052 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -366,29 +366,20 @@ impl PaymentIntentInterface for crate::RouterStore { PaymentIntentFetchConstraints::Single { payment_intent_id } => { query = query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned())); } - PaymentIntentFetchConstraints::List { - offset, - starting_at, - ending_at, - connector: _, - currency, - status, - payment_methods: _, - customer_id, - starting_after_id, - ending_before_id, - limit, - } => { - if let Some(limit) = limit { - query = query.limit((*limit).into()); - }; + PaymentIntentFetchConstraints::List(params) => { + if let Some(limit) = params.limit { + query = query.limit(limit.into()); + } - if let Some(customer_id) = customer_id { + if let Some(customer_id) = ¶ms.customer_id { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } - query = match (starting_at, starting_after_id) { - (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(*starting_at)), + query = match (params.starting_at, ¶ms.starting_after_id) { + (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)), (None, Some(starting_after_id)) => { // TODO: Fetch partial columns for this query since we only need some columns let starting_at = self @@ -404,8 +395,8 @@ impl PaymentIntentInterface for crate::RouterStore { (None, None) => query, }; - query = match (ending_at, ending_before_id) { - (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(*ending_at)), + query = match (params.ending_at, ¶ms.ending_before_id) { + (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)), (None, Some(ending_before_id)) => { // TODO: Fetch partial columns for this query since we only need some columns let ending_at = self @@ -420,17 +411,26 @@ impl PaymentIntentInterface for crate::RouterStore { } (None, None) => query, }; - query = query.offset((*offset).into()); - query = match currency { + query = query.offset(params.offset.into()); + + query = match ¶ms.currency { Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())), None => query, }; - query = match status { + query = match ¶ms.status { Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())), None => query, }; + + if let Some(currency) = ¶ms.currency { + query = query.filter(pi_dsl::currency.eq_any(currency.clone())); + } + + if let Some(status) = ¶ms.status { + query = query.filter(pi_dsl::status.eq_any(status.clone())); + } } } @@ -490,29 +490,21 @@ impl PaymentIntentInterface for crate::RouterStore { PaymentIntentFetchConstraints::Single { payment_intent_id } => { query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned())) } - PaymentIntentFetchConstraints::List { - offset, - starting_at, - ending_at, - connector, - currency, - status, - payment_methods, - customer_id, - starting_after_id, - ending_before_id, - limit, - } => { - if let Some(limit) = limit { - query = query.limit((*limit).into()); + PaymentIntentFetchConstraints::List(params) => { + if let Some(limit) = params.limit { + query = query.limit(limit.into()); } - if let Some(customer_id) = customer_id { + if let Some(customer_id) = ¶ms.customer_id { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } - query = match (starting_at, starting_after_id) { - (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(*starting_at)), + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } + + query = match (params.starting_at, ¶ms.starting_after_id) { + (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)), (None, Some(starting_after_id)) => { // TODO: Fetch partial columns for this query since we only need some columns let starting_at = self @@ -528,8 +520,8 @@ impl PaymentIntentInterface for crate::RouterStore { (None, None) => query, }; - query = match (ending_at, ending_before_id) { - (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(*ending_at)), + query = match (params.ending_at, ¶ms.ending_before_id) { + (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)), (None, Some(ending_before_id)) => { // TODO: Fetch partial columns for this query since we only need some columns let ending_at = self @@ -545,14 +537,14 @@ impl PaymentIntentInterface for crate::RouterStore { (None, None) => query, }; - query = query.offset((*offset).into()); + query = query.offset(params.offset.into()); - query = match currency { - Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())), - None => query, - }; + if let Some(currency) = ¶ms.currency { + query = query.filter(pi_dsl::currency.eq_any(currency.clone())); + } - let connectors = connector + let connectors = params + .connector .as_ref() .map(|c| c.iter().map(|c| c.to_string()).collect::>()); @@ -561,18 +553,30 @@ impl PaymentIntentInterface for crate::RouterStore { None => query, }; - query = match status { + query = match ¶ms.status { Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())), None => query, }; - query = match payment_methods { - Some(payment_methods) => { - query.filter(pa_dsl::payment_method.eq_any(payment_methods.clone())) + query = match ¶ms.payment_method { + Some(payment_method) => { + query.filter(pa_dsl::payment_method.eq_any(payment_method.clone())) } None => query, }; + query = match ¶ms.payment_method_type { + Some(payment_method_type) => query + .filter(pa_dsl::payment_method_type.eq_any(payment_method_type.clone())), + None => query, + }; + + query = match ¶ms.authentication_type { + Some(authentication_type) => query + .filter(pa_dsl::authentication_type.eq_any(authentication_type.clone())), + None => query, + }; + query } }; @@ -620,34 +624,30 @@ impl PaymentIntentInterface for crate::RouterStore { PaymentIntentFetchConstraints::Single { payment_intent_id } => { query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned())) } - PaymentIntentFetchConstraints::List { - starting_at, - ending_at, - currency, - status, - customer_id, - .. - } => { - if let Some(customer_id) = customer_id { + PaymentIntentFetchConstraints::List(params) => { + if let Some(customer_id) = ¶ms.customer_id { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } - query = match starting_at { - Some(starting_at) => query.filter(pi_dsl::created_at.ge(*starting_at)), + query = match params.starting_at { + Some(starting_at) => query.filter(pi_dsl::created_at.ge(starting_at)), None => query, }; - query = match ending_at { - Some(ending_at) => query.filter(pi_dsl::created_at.le(*ending_at)), + query = match params.ending_at { + Some(ending_at) => query.filter(pi_dsl::created_at.le(ending_at)), None => query, }; - query = match currency { + query = match ¶ms.currency { Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())), None => query, }; - query = match status { + query = match ¶ms.status { Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())), None => query, }; diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index efa7ebb32cfe..7f1647d11328 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -10184,6 +10184,11 @@ "description": "The identifier for the refund", "nullable": true }, + "profile_id": { + "type": "string", + "description": "The identifier for business profile", + "nullable": true + }, "limit": { "type": "integer", "format": "int64", From 6aec168b4f665e980dc989eb4c7f44eaad64f512 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:31:57 +0000 Subject: [PATCH 20/21] chore(version): v1.50.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8924ba3ecc15..d7070c21e935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,40 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.50.0 (2023-10-04) + +### Features + +- **connector:** + - [Stax] Use connector_response_reference_id as reference to merchant ([#2415](https://github.com/juspay/hyperswitch/pull/2415)) ([`099b241`](https://github.com/juspay/hyperswitch/commit/099b241096c69879a805ca81b1c5a23118e10b52)) + - [PowerTranz] Use connector_response_reference_id as reference to merchant ([#2413](https://github.com/juspay/hyperswitch/pull/2413)) ([`0d703c7`](https://github.com/juspay/hyperswitch/commit/0d703c7ab85c68f433767a70a0feabe8daa4f24c)) + - [Payeezy] Use connector_response_reference_id as reference to merchant ([#2410](https://github.com/juspay/hyperswitch/pull/2410)) ([`485c09d`](https://github.com/juspay/hyperswitch/commit/485c09d16743d73b446d6313f0ee6462c8a77028)) + - [Square] Use reference_id as reference to merchant ([#2434](https://github.com/juspay/hyperswitch/pull/2434)) ([`591c9b7`](https://github.com/juspay/hyperswitch/commit/591c9b70d9b7f8df5d7f5d2cb2d19cfaa1457fe1)) +- **router:** + - Remove unnecessary lookups in refund and payment_attempt kv flow ([#2425](https://github.com/juspay/hyperswitch/pull/2425)) ([`f720aec`](https://github.com/juspay/hyperswitch/commit/f720aecf1fb676cec71e636b877a46f9791d713a)) + - [OpenNode] response reference id ([#2416](https://github.com/juspay/hyperswitch/pull/2416)) ([`3bfea72`](https://github.com/juspay/hyperswitch/commit/3bfea72df34f4ce0ffdb61e49960fdf09b96eb5a)) + - Add profile id and extra filters in lists ([#2379](https://github.com/juspay/hyperswitch/pull/2379)) ([`ab2cde7`](https://github.com/juspay/hyperswitch/commit/ab2cde799371a66eb045cf8b20431b3b108dac44)) + +### Bug Fixes + +- **CI:** Fix spell check for CI pull request ([#2439](https://github.com/juspay/hyperswitch/pull/2439)) ([`04f2e11`](https://github.com/juspay/hyperswitch/commit/04f2e11cd4f3fd327408cddec36ccf4fb486b935)) +- **router:** Merchant account delete does not delete the merchant_key_store ([#2367](https://github.com/juspay/hyperswitch/pull/2367)) ([`35f7ce0`](https://github.com/juspay/hyperswitch/commit/35f7ce0f4d9e16184e2bb94360d3ced60f8b5af2)) + +### Refactors + +- **config:** Update payment method filter for apple pay ([#2423](https://github.com/juspay/hyperswitch/pull/2423)) ([`d177b4d`](https://github.com/juspay/hyperswitch/commit/d177b4d94f08fb8ef44b5c07ec1bdc771baa016d)) +- **payment_methods:** Add `requires_cvv` config while creating merchant account ([#2431](https://github.com/juspay/hyperswitch/pull/2431)) ([`6e5ab0d`](https://github.com/juspay/hyperswitch/commit/6e5ab0d121d6345f18bccc7f917064caa2737475)) +- **webhook:** Add a function to retrieve payment_id ([#2447](https://github.com/juspay/hyperswitch/pull/2447)) ([`409913f`](https://github.com/juspay/hyperswitch/commit/409913fd75076e4ee1dac1e4dc5b2f164528bc23)) + +### Build System / Dependencies + +- **deps:** Address `undeclared crate or module` errors on Windows for `scheduler` crate ([#2411](https://github.com/juspay/hyperswitch/pull/2411)) ([`4225238`](https://github.com/juspay/hyperswitch/commit/422523848e6516643a6beef1ba15af4e967f0c5b)) + +**Full Changelog:** [`v1.49.0...v1.50.0`](https://github.com/juspay/hyperswitch/compare/v1.49.0...v1.50.0) + +- - - + + ## 1.49.0 (2023-10-03) ### Features From 05ee47a6e90bd68a0faa6dcc381c48a1f0f274d8 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:24:54 +0530 Subject: [PATCH 21/21] fix(connector): use enum to deserialize latest_charge in stripe psync response (#2444) --- .../src/connector/stripe/transformers.rs | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 488a7a295bd2..57a7935f0300 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -2125,7 +2125,14 @@ pub struct PaymentIntentSyncResponse { #[serde(flatten)] payment_intent_fields: PaymentIntentResponse, pub last_payment_error: Option, - pub latest_charge: Option, + pub latest_charge: Option, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum StripeChargeEnum { + ChargeId(String), + ChargeObject(StripeCharge), } #[derive(Deserialize, Clone, Debug)] @@ -2414,19 +2421,38 @@ impl types::MandateReference::foreign_from(( item.response.payment_method_options.clone(), match item.response.latest_charge.clone() { - Some(charge) => match charge.payment_method_details { - Some(StripePaymentMethodDetailsResponse::Bancontact { bancontact }) => { - bancontact.attached_payment_method.unwrap_or(pm) - } - Some(StripePaymentMethodDetailsResponse::Ideal { ideal }) => { - ideal.attached_payment_method.unwrap_or(pm) + Some(StripeChargeEnum::ChargeObject(charge)) => { + match charge.payment_method_details { + Some(StripePaymentMethodDetailsResponse::Bancontact { bancontact }) => { + bancontact.attached_payment_method.unwrap_or(pm) + } + Some(StripePaymentMethodDetailsResponse::Ideal { ideal }) => { + ideal.attached_payment_method.unwrap_or(pm) + } + Some(StripePaymentMethodDetailsResponse::Sofort { sofort }) => { + sofort.attached_payment_method.unwrap_or(pm) + } + Some(StripePaymentMethodDetailsResponse::Blik) + | Some(StripePaymentMethodDetailsResponse::Eps) + | Some(StripePaymentMethodDetailsResponse::Fpx) + | Some(StripePaymentMethodDetailsResponse::Giropay) + | Some(StripePaymentMethodDetailsResponse::Przelewy24) + | Some(StripePaymentMethodDetailsResponse::Card) + | Some(StripePaymentMethodDetailsResponse::Klarna) + | Some(StripePaymentMethodDetailsResponse::Affirm) + | Some(StripePaymentMethodDetailsResponse::AfterpayClearpay) + | Some(StripePaymentMethodDetailsResponse::ApplePay) + | Some(StripePaymentMethodDetailsResponse::Ach) + | Some(StripePaymentMethodDetailsResponse::Sepa) + | Some(StripePaymentMethodDetailsResponse::Becs) + | Some(StripePaymentMethodDetailsResponse::Bacs) + | Some(StripePaymentMethodDetailsResponse::Wechatpay) + | Some(StripePaymentMethodDetailsResponse::Alipay) + | Some(StripePaymentMethodDetailsResponse::CustomerBalance) + | None => pm, } - Some(StripePaymentMethodDetailsResponse::Sofort { sofort }) => { - sofort.attached_payment_method.unwrap_or(pm) - } - _ => pm, - }, - None => pm, + } + Some(StripeChargeEnum::ChargeId(_)) | None => pm, }, )) });