From 3d55e3ba45619978e8ca9e5012c156dc017d2879 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:26:55 +0530 Subject: [PATCH] feat(pm_list): add required fields for sofort (#3192) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> --- config/config.example.toml | 15 +- config/development.toml | 3 +- config/docker_compose.toml | 3 +- crates/api_models/src/payments.rs | 6 +- crates/router/src/configs/defaults.rs | 293 +++++++++++++++++- .../router/src/connector/aci/transformers.rs | 6 +- .../src/connector/adyen/transformers.rs | 5 +- .../src/connector/paypal/transformers.rs | 11 +- .../src/connector/stripe/transformers.rs | 87 +++--- loadtest/config/development.toml | 3 +- openapi/openapi_spec.json | 14 +- 11 files changed, 381 insertions(+), 65 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index d53a6e28ef6f..00c325f058e6 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -374,13 +374,14 @@ slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswit discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch [mandates.supported_payment_methods] -card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card -wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets -pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later -bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_redirect.ideal = { connector_list = "stripe,adyen" } # Mandate supported payment method type and connector for bank_redirect +card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card +wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets +pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later +bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} # Mandate supported payment method type and connector for bank_redirect +bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} # Required fields info used while listing the payment_method_data diff --git a/config/development.toml b/config/development.toml index 7c494ea28521..d447930b5902 100644 --- a/config/development.toml +++ b/config/development.toml @@ -474,7 +474,8 @@ card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,global bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} -bank_redirect.ideal = {connector_list = "stripe,adyen"} +bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} +bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 79039e136792..d468f1dd412f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -340,7 +340,8 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalp bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} -bank_redirect.ideal = {connector_list = "stripe,adyen"} +bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} +bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 8c27f498d7ad..f8d31c6e414e 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1336,15 +1336,15 @@ pub enum BankRedirectData { }, Sofort { /// The billing details for bank redirection - billing_details: BankRedirectBilling, + billing_details: Option, /// The country for bank payment #[schema(value_type = CountryAlpha2, example = "US")] - country: api_enums::CountryAlpha2, + country: Option, /// The preferred language #[schema(example = "en")] - preferred_language: String, + preferred_language: Option, }, Trustly { /// The country for bank payment diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index e4a470d0da35..b6d8fa114b9a 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -4750,14 +4750,303 @@ impl Default for super::settings::RequiredFields { ConnectorFields { fields: HashMap::from([ ( - enums::Connector::Stripe, + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ("payment_method_data.bank_redirect.sofort.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "ES".to_string(), + "GB".to_string(), + "SE".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "CH".to_string(), + "BE".to_string(), + "FR".to_string(), + "FI".to_string(), + "IT".to_string(), + "PL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), common: HashMap::new(), } ), - ]), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ("billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AT".to_string(), + "BE".to_string(), + "DE".to_string(), + "ES".to_string(), + "IT".to_string(), + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "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, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ES".to_string(), + "GB".to_string(), + "IT".to_string(), + "DE".to_string(), + "FR".to_string(), + "AT".to_string(), + "BE".to_string(), + "NL".to_string(), + "BE".to_string(), + "SK".to_string(), + ] + }, + value: None, + } + )] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ("payment_method_data.bank_redirect.sofort.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "ES".to_string(), + "GB".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "BE".to_string(), + ] + }, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) + ]), + non_mandate : HashMap::from([ + ("payment_method_data.bank_redirect.sofort.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "ES".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "BE".to_string(), + ] + }, + value: None, + } + )]), + common: HashMap::new( + + ), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: 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.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "ES".to_string(), + "GB".to_string(), + "SE".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "CH".to_string(), + "BE".to_string(), + "FR".to_string(), + "FI".to_string(), + "IT".to_string(), + "PL".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), }, ), ( diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index f34909f5489f..97cef72b02a7 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -202,7 +202,11 @@ impl api_models::payments::BankRedirectData::Sofort { country, .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Sofortueberweisung, - bank_account_country: Some(country.to_owned()), + bank_account_country: Some(country.to_owned().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "sofort.country", + }, + )?), bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 76678a1b33b0..8da5d15c4446 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2715,10 +2715,7 @@ fn get_redirect_extra_details( country, preferred_language, .. - } => Ok(( - Some(preferred_language.to_string()), - Some(country.to_owned()), - )), + } => Ok((preferred_language.clone(), *country)), api_models::payments::BankRedirectData::OpenBankingUk { country, .. } => { let country = country.ok_or(errors::ConnectorError::MissingRequiredField { field_name: "country", diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index d384bc4cfd53..baf8f48279d9 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -360,8 +360,15 @@ fn get_payment_source( preferred_language: _, billing_details, } => Ok(PaymentSourceItem::Sofort(RedirectRequest { - name: billing_details.get_billing_name()?, - country_code: *country, + name: billing_details + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "sofort.billing_details", + })? + .get_billing_name()?, + country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "sofort.country", + })?, experience_context: ContextStruct { return_url: item.request.complete_authorize_url.clone(), cancel_url: item.request.complete_authorize_url.clone(), diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 1dbb310868a6..70582c41aa4d 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -290,7 +290,7 @@ pub struct StripeSofort { #[serde(rename = "payment_method_data[type]")] pub payment_method_data_type: StripePaymentMethodType, #[serde(rename = "payment_method_options[sofort][preferred_language]")] - preferred_language: String, + preferred_language: Option, #[serde(rename = "payment_method_data[sofort][country]")] country: api_enums::CountryAlpha2, } @@ -1115,36 +1115,10 @@ impl TryFrom<(&payments::BankRedirectData, Option)> for StripeBillingAddre }), payments::BankRedirectData::Ideal { billing_details, .. - } => { - let billing_name = billing_details - .clone() - .and_then(|billing_data| billing_data.billing_name.clone()); - - let billing_email = billing_details - .clone() - .and_then(|billing_data| billing_data.email.clone()); - match is_customer_initiated_mandate_payment { - Some(true) => Ok(Self { - name: Some(billing_name.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "billing_name", - }, - )?), - - email: Some(billing_email.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "billing_email", - }, - )?), - ..Self::default() - }), - Some(false) | None => Ok(Self { - name: billing_name, - email: billing_email, - ..Self::default() - }), - } - } + } => Ok(get_stripe_sepa_dd_mandate_billing_details( + billing_details, + is_customer_initiated_mandate_payment, + )?), payments::BankRedirectData::Przelewy24 { billing_details, .. } => Ok(Self { @@ -1183,11 +1157,11 @@ impl TryFrom<(&payments::BankRedirectData, Option)> for StripeBillingAddre } payments::BankRedirectData::Sofort { billing_details, .. - } => Ok(Self { - name: billing_details.billing_name.clone(), - email: billing_details.email.clone(), - ..Self::default() - }), + } => Ok(get_stripe_sepa_dd_mandate_billing_details( + billing_details, + is_customer_initiated_mandate_payment, + )?), + payments::BankRedirectData::Bizum {} | payments::BankRedirectData::Blik { .. } | payments::BankRedirectData::Interac { .. } @@ -1674,8 +1648,10 @@ impl TryFrom<&payments::BankRedirectData> for StripePaymentMethodData { } => Ok(Self::BankRedirect(StripeBankRedirectData::StripeSofort( Box::new(StripeSofort { payment_method_data_type, - country: country.to_owned(), - preferred_language: preferred_language.to_owned(), + country: country.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "sofort.country", + })?, + preferred_language: preferred_language.clone(), }), ))), payments::BankRedirectData::OnlineBankingFpx { .. } => { @@ -3593,6 +3569,41 @@ pub struct Evidence { pub submit: bool, } +// Mandates for bank redirects - ideal and sofort happens through sepa direct debit in stripe +fn get_stripe_sepa_dd_mandate_billing_details( + billing_details: &Option, + is_customer_initiated_mandate_payment: Option, +) -> Result { + let billing_name = billing_details + .clone() + .and_then(|billing_data| billing_data.billing_name.clone()); + + let billing_email = billing_details + .clone() + .and_then(|billing_data| billing_data.email.clone()); + match is_customer_initiated_mandate_payment { + Some(true) => Ok(StripeBillingAddress { + name: Some( + billing_name.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_name", + })?, + ), + + email: Some( + billing_email.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_email", + })?, + ), + ..StripeBillingAddress::default() + }), + Some(false) | None => Ok(StripeBillingAddress { + name: billing_name, + email: billing_email, + ..StripeBillingAddress::default() + }), + } +} + impl TryFrom<&types::SubmitEvidenceRouterData> for Evidence { type Error = error_stack::Report; fn try_from(item: &types::SubmitEvidenceRouterData) -> Result { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 268ebd1d3ac9..954336070d58 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -246,7 +246,8 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalp bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} -bank_redirect.ideal = {connector_list = "stripe,adyen"} +bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} +bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} [analytics] source = "sqlx" diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 5696b6fd692b..d516eeed4cc7 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -5367,13 +5367,16 @@ "sofort": { "type": "object", "required": [ - "billing_details", - "country", - "preferred_language" + "country" ], "properties": { "billing_details": { - "$ref": "#/components/schemas/BankRedirectBilling" + "allOf": [ + { + "$ref": "#/components/schemas/BankRedirectBilling" + } + ], + "nullable": true }, "country": { "$ref": "#/components/schemas/CountryAlpha2" @@ -5381,7 +5384,8 @@ "preferred_language": { "type": "string", "description": "The preferred language", - "example": "en" + "example": "en", + "nullable": true } } }