From d061e0a7c55c518cf76047eb1cbe506b0faed0bf Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:08:18 +0530 Subject: [PATCH] feat(payment_method): add logic for setup_future_usage downgrade and add filter based on zero mandate config (#7775) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 16 ++++ config/deployments/integration_test.toml | 24 +++++ config/deployments/production.toml | 24 +++++ config/deployments/sandbox.toml | 24 +++++ config/development.toml | 24 +++++ config/docker_compose.toml | 24 +++++ crates/diesel_models/src/payment_attempt.rs | 29 ++++++ crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/user/sample_data.rs | 2 + .../src/payments/payment_attempt.rs | 8 ++ .../payment_connector_required_fields.rs | 10 ++- .../src/configs/secrets_transformers.rs | 1 + crates/router/src/configs/settings.rs | 6 ++ .../router/src/core/payment_methods/cards.rs | 88 +++++++++++++------ .../src/core/payments/flows/authorize_flow.rs | 33 ++++++- crates/router/src/core/payments/helpers.rs | 1 + .../payments/operations/payment_create.rs | 1 + .../payments/operations/payment_response.rs | 7 ++ crates/router/src/core/payments/retry.rs | 4 + .../router/src/core/payments/transformers.rs | 8 +- .../src/types/storage/payment_attempt.rs | 3 + crates/router/src/utils/user/sample_data.rs | 1 + .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 5 ++ .../down.sql | 2 + .../up.sql | 1 + .../2025-01-13-081847_drop_v1_columns/up.sql | 3 +- 27 files changed, 318 insertions(+), 33 deletions(-) create mode 100644 migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/down.sql create mode 100644 migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/up.sql diff --git a/config/config.example.toml b/config/config.example.toml index 9afb30076b..f766948048 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -470,6 +470,22 @@ default_return_url = "https://www.example.com/" # Default return url when no ret slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswitch discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch +[zero_mandates.supported_payment_methods] +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +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,adyen" } # 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.bacs = { connector_list = "adyen" } # Mandate supported payment method type and connector for bank_debit +bank_debit.sepa = { connector_list = "gocardless,adyen" } # 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,globalpay" } +wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica,authorizedotnet" } +wallet.samsung_pay = { connector_list = "cybersource" } +wallet.google_pay = { connector_list = "bankofamerica,authorizedotnet" } +bank_redirect.giropay = { connector_list = "globalpay" } + [mandates.supported_payment_methods] card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal,xendit" card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal,xendit" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 15bfe73eca..4e3c2881cd 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -172,6 +172,30 @@ payout_connector_list = "nomupay,stripe,wise" [delayed_session_response] connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which have delayed session response +[zero_mandates.supported_payment_methods] +bank_debit.ach = { connector_list = "gocardless,adyen" } +bank_debit.becs = { connector_list = "gocardless,adyen" } +bank_debit.bacs = { connector_list = "gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.samsung_pay.connector_list = "cybersource" +wallet.google_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.paypal.connector_list = "adyen,novalnet" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" +bank_redirect.ideal.connector_list = "adyen" +bank_redirect.bancontact_card.connector_list = "adyen" +bank_redirect.trustly.connector_list = "adyen" +bank_redirect.open_banking_uk.connector_list = "adyen" + [mandates.supported_payment_methods] bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 9d3273647f..4c46815b9e 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -172,6 +172,30 @@ force_cookies = false [frm] enabled = false +[zero_mandates.supported_payment_methods] +bank_debit.ach = { connector_list = "gocardless,adyen" } +bank_debit.becs = { connector_list = "gocardless,adyen" } +bank_debit.bacs = { connector_list = "gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.samsung_pay.connector_list = "cybersource" +wallet.google_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.paypal.connector_list = "adyen,novalnet" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" +bank_redirect.ideal.connector_list = "adyen" +bank_redirect.bancontact_card.connector_list = "adyen" +bank_redirect.trustly.connector_list = "adyen" +bank_redirect.open_banking_uk.connector_list = "adyen" + [mandates.supported_payment_methods] bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 8c9569d4df..69e5d4faed 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -172,6 +172,30 @@ force_cookies = false [frm] enabled = true +[zero_mandates.supported_payment_methods] +bank_debit.ach = { connector_list = "gocardless,adyen" } +bank_debit.becs = { connector_list = "gocardless,adyen" } +bank_debit.bacs = { connector_list = "gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.samsung_pay.connector_list = "cybersource" +wallet.google_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.paypal.connector_list = "adyen,novalnet" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" +bank_redirect.ideal.connector_list = "adyen" +bank_redirect.bancontact_card.connector_list = "adyen" +bank_redirect.trustly.connector_list = "adyen" +bank_redirect.open_banking_uk.connector_list = "adyen" + [mandates.supported_payment_methods] bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } diff --git a/config/development.toml b/config/development.toml index a332e2a1c7..7f8f345bcf 100644 --- a/config/development.toml +++ b/config/development.toml @@ -786,6 +786,30 @@ connectors_with_webhook_source_verification_call = "paypal" [billing_connectors_payment_sync] billing_connectors_which_require_payment_sync = "stripebilling, recurly" +[zero_mandates.supported_payment_methods] +bank_debit.ach = { connector_list = "gocardless,adyen" } +bank_debit.becs = { connector_list = "gocardless,adyen" } +bank_debit.bacs = { connector_list = "gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.samsung_pay.connector_list = "cybersource" +wallet.google_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.paypal.connector_list = "adyen,novalnet" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" +bank_redirect.ideal.connector_list = "adyen" +bank_redirect.bancontact_card.connector_list = "adyen" +bank_redirect.trustly.connector_list = "adyen" +bank_redirect.open_banking_uk.connector_list = "adyen" + [mandates.supported_payment_methods] bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 7842a1f468..c6ef21f38f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -668,6 +668,30 @@ adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_ba [bank_config.open_banking_uk] adyen = { banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,halifax,lloyds,monzo,nat_west,nationwide_bank,royal_bank_of_scotland,starling,tsb_bank,tesco_bank,ulster_bank,barclays,hsbc_bank,revolut,santander_przelew24,open_bank_success,open_bank_failure,open_bank_cancelled" } +[zero_mandates.supported_payment_methods] +bank_debit.ach = { connector_list = "gocardless,adyen" } +bank_debit.becs = { connector_list = "gocardless,adyen" } +bank_debit.bacs = { connector_list = "gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,worldpay,nmi,bankofamerica,wellsfargo,bamboraapac,nexixpay,novalnet,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.samsung_pay.connector_list = "cybersource" +wallet.google_pay.connector_list = "adyen,cybersource,bankofamerica,novalnet,authorizedotnet" +wallet.paypal.connector_list = "adyen,novalnet" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" +bank_redirect.ideal.connector_list = "adyen" +bank_redirect.bancontact_card.connector_list = "adyen" +bank_redirect.trustly.connector_list = "adyen" +bank_redirect.open_banking_uk.connector_list = "adyen" + [mandates.supported_payment_methods] pay_later.klarna = { connector_list = "adyen" } wallet.google_pay = { connector_list = "stripe,adyen,bankofamerica,authorizedotnet,novalnet" } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index a3599fc1b4..63f039c32f 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -197,6 +197,7 @@ pub struct PaymentAttempt { pub charges: Option, pub issuer_error_code: Option, pub issuer_error_message: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -410,6 +411,7 @@ pub struct PaymentAttemptNew { #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_before: Option, pub card_discovery: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -533,6 +535,7 @@ pub enum PaymentAttemptUpdate { payment_method_data: Option, connector_mandate_detail: Option, charges: Option, + setup_future_usage_applied: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -922,6 +925,7 @@ pub struct PaymentAttemptUpdateInternal { pub charges: Option, pub issuer_error_code: Option, pub issuer_error_message: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -1110,6 +1114,7 @@ impl PaymentAttemptUpdate { charges, issuer_error_code, issuer_error_message, + setup_future_usage_applied, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1174,6 +1179,8 @@ impl PaymentAttemptUpdate { charges: charges.or(source.charges), issuer_error_code: issuer_error_code.or(source.issuer_error_code), issuer_error_message: issuer_error_message.or(source.issuer_error_message), + setup_future_usage_applied: setup_future_usage_applied + .or(source.setup_future_usage_applied), ..source } } @@ -2231,6 +2238,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2292,6 +2300,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2385,6 +2394,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2447,6 +2457,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2510,6 +2521,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -2573,6 +2585,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::ConnectorMandateDetailUpdate { connector_mandate_detail, @@ -2634,6 +2647,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -2695,6 +2709,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -2720,6 +2735,7 @@ impl From for PaymentAttemptUpdateInternal { payment_method_data, connector_mandate_detail, charges, + setup_future_usage_applied, } => { let (connector_transaction_id, processor_transaction_data) = connector_transaction_id @@ -2783,6 +2799,7 @@ impl From for PaymentAttemptUpdateInternal { card_discovery: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied, } } PaymentAttemptUpdate::ErrorUpdate { @@ -2863,6 +2880,7 @@ impl From for PaymentAttemptUpdateInternal { connector_mandate_detail: None, card_discovery: None, charges: None, + setup_future_usage_applied: None, } } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { @@ -2922,6 +2940,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -2989,6 +3008,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -3063,6 +3083,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, } } PaymentAttemptUpdate::PreprocessingUpdate { @@ -3136,6 +3157,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, } } PaymentAttemptUpdate::CaptureUpdate { @@ -3199,6 +3221,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -3261,6 +3284,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -3332,6 +3356,7 @@ impl From for PaymentAttemptUpdateInternal { card_discovery: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3394,6 +3419,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3458,6 +3484,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -3531,6 +3558,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, } } PaymentAttemptUpdate::PostSessionTokensUpdate { @@ -3593,6 +3621,7 @@ impl From for PaymentAttemptUpdateInternal { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: None, }, } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 07f36be2bf..802aa698a8 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -933,6 +933,7 @@ diesel::table! { #[max_length = 64] issuer_error_code -> Nullable, issuer_error_message -> Nullable, + setup_future_usage_applied -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index e093d7775d..9212d416cb 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -210,6 +210,7 @@ pub struct PaymentAttemptBatchNew { pub extended_authorization_applied: Option, pub capture_before: Option, pub card_discovery: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -292,6 +293,7 @@ impl PaymentAttemptBatchNew { extended_authorization_applied: self.extended_authorization_applied, capture_before: self.capture_before, card_discovery: self.card_discovery, + setup_future_usage_applied: self.setup_future_usage_applied, } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 494950d42c..a074cca9b5 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -859,6 +859,7 @@ pub struct PaymentAttempt { pub charges: Option, pub issuer_error_code: Option, pub issuer_error_message: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -1108,6 +1109,7 @@ pub struct PaymentAttemptNew { pub extended_authorization_applied: Option, pub capture_before: Option, pub card_discovery: Option, + pub setup_future_usage_applied: Option, } #[cfg(feature = "v1")] @@ -1225,6 +1227,7 @@ pub enum PaymentAttemptUpdate { payment_method_data: Option, connector_mandate_detail: Option, charges: Option, + setup_future_usage_applied: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -1501,6 +1504,7 @@ impl PaymentAttemptUpdate { payment_method_data, connector_mandate_detail, charges, + setup_future_usage_applied, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -1525,6 +1529,7 @@ impl PaymentAttemptUpdate { payment_method_data, connector_mandate_detail, charges, + setup_future_usage_applied, }, Self::UnresolvedResponseUpdate { status, @@ -1851,6 +1856,7 @@ impl behaviour::Conversion for PaymentAttempt { charges: self.charges, issuer_error_code: self.issuer_error_code, issuer_error_message: self.issuer_error_message, + setup_future_usage_applied: self.setup_future_usage_applied, // Below fields are deprecated. Please add any new fields above this line. connector_transaction_data: None, }) @@ -1941,6 +1947,7 @@ impl behaviour::Conversion for PaymentAttempt { charges: storage_model.charges, issuer_error_code: storage_model.issuer_error_code, issuer_error_message: storage_model.issuer_error_message, + setup_future_usage_applied: storage_model.setup_future_usage_applied, }) } .await @@ -2026,6 +2033,7 @@ impl behaviour::Conversion for PaymentAttempt { extended_authorization_applied: self.extended_authorization_applied, capture_before: self.capture_before, card_discovery: self.card_discovery, + setup_future_usage_applied: self.setup_future_usage_applied, }) } } diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index fc2e6dc51f..acf2b60f0c 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -5,9 +5,17 @@ use api_models::{enums, payment_methods::RequiredFieldInfo}; use crate::settings::{ self, ConnectorFields, Mandates, PaymentMethodType, RequiredFieldFinal, SupportedConnectorsForMandate, SupportedPaymentMethodTypesForMandate, - SupportedPaymentMethodsForMandate, + SupportedPaymentMethodsForMandate, ZeroMandates, }; +impl Default for ZeroMandates { + fn default() -> Self { + Self { + supported_payment_methods: SupportedPaymentMethodsForMandate(HashMap::new()), + } + } +} + impl Default for Mandates { fn default() -> Self { Self { diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 7cd4ce2d4b..45a6903cb9 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -502,6 +502,7 @@ pub(crate) async fn fetch_raw_secrets( email: conf.email, user: conf.user, mandates: conf.mandates, + zero_mandates: conf.zero_mandates, network_transaction_id_supported_connectors: conf .network_transaction_id_supported_connectors, required_fields: conf.required_fields, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 83591b15e3..67f7e46770 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -93,6 +93,7 @@ pub struct Settings { pub user: UserSettings, pub cors: CorsSettings, pub mandates: Mandates, + pub zero_mandates: ZeroMandates, pub network_transaction_id_supported_connectors: NetworkTransactionIdSupportedConnectors, pub required_fields: RequiredFields, pub delayed_session_response: DelayedSessionConfig, @@ -515,6 +516,11 @@ pub struct Mandates { pub update_mandate_supported: SupportedPaymentMethodsForMandate, } +#[derive(Debug, Deserialize, Clone)] +pub struct ZeroMandates { + pub supported_payment_methods: SupportedPaymentMethodsForMandate, +} + #[derive(Debug, Deserialize, Clone, Default)] pub struct NetworkTransactionIdSupportedConnectors { #[serde(deserialize_with = "deserialize_hashset")] diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index dc79cd6845..6227ce8c9f 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -48,6 +48,7 @@ use hyperswitch_constraint_graph as cgraph; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::customer::CustomerUpdate; use hyperswitch_domain_models::mandates::CommonMandateReference; +use hyperswitch_interfaces::secrets_interface::secret_state::RawSecret; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -3393,7 +3394,7 @@ pub async fn list_payment_methods( payment_attempt.as_ref(), billing_address.as_ref(), mca.connector_name.clone(), - &state.conf.saved_payment_methods, + &state.conf, ) .await?; } @@ -3448,7 +3449,7 @@ pub async fn list_payment_methods( payment_attempt.as_ref(), billing_address.as_ref(), mca.connector_name.clone(), - &state.conf.saved_payment_methods, + &state.conf, ) .await?; } @@ -4448,7 +4449,7 @@ pub async fn filter_payment_methods( payment_attempt: Option<&storage::PaymentAttempt>, address: Option<&domain::Address>, connector: String, - saved_payment_methods: &settings::EligiblePaymentMethods, + configs: &settings::Settings, ) -> errors::CustomResult<(), errors::ApiErrorResponse> { for payment_method in payment_methods.iter() { let parse_result = serde_json::from_value::( @@ -4530,6 +4531,9 @@ pub async fn filter_payment_methods( payment_method_object.payment_method_type, ); + // Filter logic for payment method types based on the below conditions + // Case 1: If the payment method type support Zero Mandate flow, filter only payment method type that support it + // Case 2: Whether the payment method type support Mandates or not, list all the payment method types if payment_attempt .and_then(|attempt| attempt.mandate_details.as_ref()) .is_some() @@ -4540,10 +4544,60 @@ pub async fn filter_payment_methods( }) .unwrap_or(false) { + payment_intent.map(|intent| intent.amount).map(|amount| { + if amount == MinorUnit::zero() { + if configs + .zero_mandates + .supported_payment_methods + .0 + .get(&payment_method) + .and_then(|supported_pm_for_mandates| { + supported_pm_for_mandates + .0 + .get(&payment_method_type_info.payment_method_type) + .map(|supported_connector_for_mandates| { + supported_connector_for_mandates + .connector_list + .contains(&connector_variant) + }) + }) + .unwrap_or(false) + { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::SetupMandate, + )); + } + } else if configs + .mandates + .supported_payment_methods + .0 + .get(&payment_method) + .and_then(|supported_pm_for_mandates| { + supported_pm_for_mandates + .0 + .get(&payment_method_type_info.payment_method_type) + .map(|supported_connector_for_mandates| { + supported_connector_for_mandates + .connector_list + .contains(&connector_variant) + }) + }) + .unwrap_or(false) + { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NewMandate, + )); + } else { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NonMandate, + )); + } + }); + } else { context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NewMandate, + euclid::enums::PaymentType::NonMandate, )); - }; + } payment_attempt .and_then(|attempt| attempt.mandate_data.as_ref()) @@ -4555,25 +4609,6 @@ pub async fn filter_payment_methods( } }); - payment_attempt - .map(|attempt| { - attempt.mandate_data.is_none() - && attempt.mandate_details.is_none() - && payment_intent - .and_then(|intent| intent.setup_future_usage) - .map(|future_usage| { - future_usage == common_enums::FutureUsage::OnSession - }) - .unwrap_or(true) - }) - .and_then(|res| { - res.then(|| { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NonMandate, - )) - }) - }); - payment_attempt .and_then(|inner| inner.capture_method) .map(|capture_method| { @@ -4591,7 +4626,8 @@ pub async fn filter_payment_methods( .as_ref() .map(|cs| { if cs.starts_with("pm_") { - saved_payment_methods + configs + .saved_payment_methods .sdk_eligible_payment_methods .contains(payment_method.to_string().as_str()) } else { @@ -4653,7 +4689,7 @@ pub async fn filter_payment_methods( _payment_attempt: Option<&storage::PaymentAttempt>, _address: Option<&domain::Address>, _connector: String, - _saved_payment_methods: &settings::EligiblePaymentMethods, + _configs: &settings::Settings, ) -> errors::CustomResult<(), errors::ApiErrorResponse> { todo!() } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 308a3f2ede..04c94edd00 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -344,6 +344,37 @@ impl Feature for types::PaymentsAu ) .to_payment_failed_response()?; + // Check if the connector supports mandate payment + // if the payment_method_type does not support mandate for the given connector, downgrade the setup future usage to on session + if self.request.setup_future_usage + == Some(diesel_models::enums::FutureUsage::OffSession) + && !self + .request + .payment_method_type + .and_then(|payment_method_type| { + state + .conf + .mandates + .supported_payment_methods + .0 + .get(&enums::PaymentMethod::from(payment_method_type)) + .and_then(|supported_pm_for_mandates| { + supported_pm_for_mandates.0.get(&payment_method_type).map( + |supported_connector_for_mandates| { + supported_connector_for_mandates + .connector_list + .contains(&connector.connector_name) + }, + ) + }) + }) + .unwrap_or(false) + { + // downgrade the setup future usage to on session + self.request.setup_future_usage = + Some(diesel_models::enums::FutureUsage::OnSession); + }; + if crate::connector::utils::PaymentsAuthorizeRequestData::is_customer_initiated_mandate_payment( &self.request, ) { @@ -354,7 +385,7 @@ impl Feature for types::PaymentsAu self.request.payment_method_data.clone(), ) .to_payment_failed_response()?; - } + }; let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< api::Authorize, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index e606291b8d..6953d1837d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4500,6 +4500,7 @@ impl AttemptType { extended_authorization_applied: None, capture_before: None, card_discovery: None, + setup_future_usage_applied: None, } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index f1b15be199..ef90a2f2be 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1366,6 +1366,7 @@ impl PaymentCreate { extended_authorization_applied: None, capture_before: None, card_discovery: None, + setup_future_usage_applied: request.setup_future_usage, }, additional_pm_data, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index f5f95288ee..0e9d1f957f 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -98,6 +98,10 @@ impl PostUpdateTracker, types::PaymentsAuthor .mandate_id .or_else(|| router_data.request.mandate_id.clone()); + // update setup_future_usage incase it is downgraded to on-session + payment_data.payment_attempt.setup_future_usage_applied = + router_data.request.setup_future_usage; + payment_data = Box::pin(payment_response_update_tracker( db, payment_data, @@ -1841,6 +1845,9 @@ async fn payment_response_update_tracker( .connector_mandate_detail .clone(), charges, + setup_future_usage_applied: payment_data + .payment_attempt + .setup_future_usage_applied, }), ), }; diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 3e8c4637b4..9e179d5552 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -420,6 +420,7 @@ where payment_data.get_payment_attempt().clone(), new_attempt_count, is_step_up, + payment_data.get_payment_intent().setup_future_usage, ); let db = &*state.store; @@ -490,6 +491,7 @@ where payment_method_data: additional_payment_method_data, connector_mandate_detail: None, charges, + setup_future_usage_applied: None, }; #[cfg(feature = "v1")] @@ -614,6 +616,7 @@ pub fn make_new_payment_attempt( old_payment_attempt: storage::PaymentAttempt, new_attempt_count: i16, is_step_up: bool, + setup_futture_usage_intent: Option, ) -> storage::PaymentAttemptNew { let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); storage::PaymentAttemptNew { @@ -682,6 +685,7 @@ pub fn make_new_payment_attempt( extended_authorization_applied: Default::default(), capture_before: Default::default(), card_discovery: old_payment_attempt.card_discovery, + setup_future_usage_applied: setup_futture_usage_intent, // setup future usage is picked from intent for new payment attempt } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 6d039310c8..3b6e124329 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2560,7 +2560,7 @@ where captures: captures_response, mandate_id, mandate_data, - setup_future_usage: payment_intent.setup_future_usage, + setup_future_usage: payment_attempt.setup_future_usage_applied, off_session: payment_intent.off_session, capture_on: None, capture_method: payment_attempt.capture_method, @@ -3298,7 +3298,7 @@ impl TryFrom> for types::PaymentsAuthoriz Ok(Self { payment_method_data: (payment_method_data.get_required_value("payment_method_data")?), - setup_future_usage: payment_data.payment_intent.setup_future_usage, + setup_future_usage: payment_data.payment_attempt.setup_future_usage_applied, mandate_id: payment_data.mandate_id.clone(), off_session: payment_data.mandate_id.as_ref().map(|_| true), setup_mandate_details: payment_data.setup_mandate.clone(), @@ -3739,7 +3739,7 @@ impl TryFrom> for types::PaymentsPostSess merchant_order_reference_id, capture_method: payment_data.payment_attempt.capture_method, shipping_cost: payment_data.payment_intent.shipping_cost, - setup_future_usage: payment_data.payment_intent.setup_future_usage, + setup_future_usage: payment_data.payment_attempt.setup_future_usage_applied, router_return_url, }) } @@ -4110,7 +4110,7 @@ impl TryFrom> for types::SetupMandateRequ .payment_method_data .get_required_value("payment_method_data")?), statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, - setup_future_usage: payment_data.payment_intent.setup_future_usage, + setup_future_usage: payment_data.payment_attempt.setup_future_usage_applied, off_session: payment_data.mandate_id.as_ref().map(|_| true), mandate_id: payment_data.mandate_id.clone(), setup_mandate_details: payment_data.setup_mandate, diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index ce6c1e92bd..b2ef401abc 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -222,6 +222,7 @@ mod tests { extended_authorization_applied: Default::default(), capture_before: Default::default(), card_discovery: Default::default(), + setup_future_usage_applied: Default::default(), }; let store = state @@ -309,6 +310,7 @@ mod tests { extended_authorization_applied: Default::default(), capture_before: Default::default(), card_discovery: Default::default(), + setup_future_usage_applied: Default::default(), }; let store = state .stores @@ -409,6 +411,7 @@ mod tests { extended_authorization_applied: Default::default(), capture_before: Default::default(), card_discovery: Default::default(), + setup_future_usage_applied: Default::default(), }; let store = state .stores diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 113023fbcb..3ec06e4cd9 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -368,6 +368,7 @@ pub async fn generate_sample_data( extended_authorization_applied: None, capture_before: None, card_discovery: None, + setup_future_usage_applied: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 06f98bb26b..1b97970bf1 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -232,6 +232,7 @@ impl PaymentAttemptInterface for MockDb { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: payment_attempt.setup_future_usage_applied, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 0156270484..25d89cbd6e 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -650,6 +650,7 @@ impl PaymentAttemptInterface for KVRouterStore { charges: None, issuer_error_code: None, issuer_error_message: None, + setup_future_usage_applied: payment_attempt.setup_future_usage_applied, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1653,6 +1654,7 @@ impl DataModelExt for PaymentAttempt { charges: self.charges, issuer_error_code: self.issuer_error_code, issuer_error_message: self.issuer_error_message, + setup_future_usage_applied: self.setup_future_usage_applied, // Below fields are deprecated. Please add any new fields above this line. connector_transaction_data: None, } @@ -1738,6 +1740,7 @@ impl DataModelExt for PaymentAttempt { charges: storage_model.charges, issuer_error_code: storage_model.issuer_error_code, issuer_error_message: storage_model.issuer_error_message, + setup_future_usage_applied: storage_model.setup_future_usage_applied, } } } @@ -1824,6 +1827,7 @@ impl DataModelExt for PaymentAttemptNew { extended_authorization_applied: self.extended_authorization_applied, capture_before: self.capture_before, card_discovery: self.card_discovery, + setup_future_usage_applied: self.setup_future_usage_applied, } } @@ -1899,6 +1903,7 @@ impl DataModelExt for PaymentAttemptNew { extended_authorization_applied: storage_model.extended_authorization_applied, capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, + setup_future_usage_applied: storage_model.setup_future_usage_applied, } } } diff --git a/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/down.sql b/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/down.sql new file mode 100644 index 0000000000..2c339fff45 --- /dev/null +++ b/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS setup_future_usage_applied; \ No newline at end of file diff --git a/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/up.sql b/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/up.sql new file mode 100644 index 0000000000..fd210528e6 --- /dev/null +++ b/migrations/2025-04-09-074315_add_setup_future_usage_to_payment_attempt/up.sql @@ -0,0 +1 @@ +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS setup_future_usage_applied "FutureUsage"; \ No newline at end of file diff --git a/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql b/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql index 69357bb04a..1140974d7c 100644 --- a/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql @@ -91,7 +91,8 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN connector_mandate_detail, DROP COLUMN charge_id, DROP COLUMN issuer_error_code, - DROP COLUMN issuer_error_message; + DROP COLUMN issuer_error_message, + DROP COLUMN setup_future_usage_applied; ALTER TABLE payment_methods