From aa3b9df9775ad41b26104710490dc16379097e5f Mon Sep 17 00:00:00 2001 From: Tushar Shukla Date: Fri, 14 Aug 2026 00:22:21 +0530 Subject: [PATCH] fix(core): allow Authorize without payment_method on the 3DS return leg Connectors that finalise 3-D Secure through the Authorize flow (Ilixium, NMI, Airwallex, Nexixpay, ...) are re-invoked after the ACS posts back to `TermUrl`. At that point the caller no longer holds the raw instrument, so Hyperswitch sends `payment_method_data: None` and omits the proto field. The Authorize handler required `payment_method` unconditionally and rejected the call with `InvalidArgument: Missing required field: payment_method` before any connector code ran, making 3DS completion impossible for every such connector. `payment_method` is now required only when this is not a redirect-return leg. The leg is identified by `redirection_response`, the same signal the connectors themselves key off (`request.redirect_response.is_some()`). A plain authorize with no `payment_method` is still rejected exactly as before. `PaymentsAuthorizeData::payment_method_data` is not optional, so the absent instrument is modelled explicitly as `PaymentMethodData::NoInstrumentAfterRedirect` rather than substituted with a default that a connector could mistake for a payable instrument. Every existing match on `PaymentMethodData` now handles the new variant through its existing unsupported-payment-method rejection, so no connector's behaviour changes; adding the variant is compiler-enforced, so a connector that later wants to accept the leg has to opt in deliberately. Verified end to end against a mock Ilixium server: HS /payments -> UCS -> `POST /direct/auth`, ACS challenge, complete-authorize -> `POST /direct/threedcomplete`, payment `succeeded`. The no-3DS authorize and the missing-`payment_method` rejection were re-checked and are unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- .../grpc-server/src/server/payments.rs | 50 +++++++++++++++---- .../src/connectors/aci/transformers.rs | 3 +- .../src/connectors/adyen/transformers.rs | 13 ++--- .../src/connectors/bambora/transformers.rs | 3 +- .../connectors/bankofamerica/transformers.rs | 6 ++- .../connectors/barclaycard/transformers.rs | 1 + .../src/connectors/billwerk/transformers.rs | 3 +- .../src/connectors/braintree/transformers.rs | 15 ++++-- .../src/connectors/cryptopay/transformers.rs | 3 +- .../connectors/cybersource/transformers.rs | 18 ++++--- .../src/connectors/datatrans/transformers.rs | 3 +- .../src/connectors/dlocal/transformers.rs | 3 +- .../src/connectors/fiserv/transformers.rs | 3 +- .../src/connectors/fiuu/transformers.rs | 6 ++- .../src/connectors/forte/transformers.rs | 3 +- .../connectors/givepayments/transformers.rs | 3 +- .../src/connectors/hipay/transformers.rs | 3 +- .../imerchantsolutions/transformers.rs | 3 +- .../src/connectors/loonio/transformers.rs | 11 ++-- .../src/connectors/mifinity/transformers.rs | 3 +- .../connectors/multisafepay/transformers.rs | 26 +++++----- .../src/connectors/nexinets/transformers.rs | 11 ++-- .../src/connectors/noon/transformers.rs | 6 ++- .../src/connectors/paypal/transformers.rs | 6 ++- .../src/connectors/placetopay/transformers.rs | 3 +- .../src/connectors/razorpay/transformers.rs | 3 +- .../src/connectors/redsys/transformers.rs | 1 + .../connectors/sanlam_common/transformers.rs | 3 +- .../src/connectors/stax/transformers.rs | 3 +- .../src/connectors/stripe/transformers.rs | 17 ++++--- .../src/connectors/trustly/transformers.rs | 3 +- .../src/connectors/trustpay/transformers.rs | 3 +- .../src/connectors/volt/transformers.rs | 3 +- .../src/connectors/wellsfargo/transformers.rs | 13 +++-- .../src/connectors/worldpay/transformers.rs | 3 +- .../domain_types/src/connector_types.rs | 1 + .../domain_types/src/payment_method_data.rs | 11 ++++ crates/types-traits/domain_types/src/types.rs | 37 ++++++++++---- 38 files changed, 207 insertions(+), 102 deletions(-) diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index aad5e44271..058968780f 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -889,14 +889,25 @@ impl PaymentService for Payments { // Convert proto request to intermediate type let payload: AuthorizationRequest = proto_payload.clone().into(); - let payment_method_data_action = PaymentMethodDataAction::get_payment_method_data_action(proto_payload.payment_method.clone().ok_or(ucs_env::error::GrpcError::from(IntegrationError::MissingRequiredField { field_name: "payment_method", context: domain_types::errors::IntegrationErrorContext::default() }))?) - .map_err(|err| { - tracing::error!("PAYMENT_AUTHORIZE_FLOW: failed to get payment method data action - error: {:?}", err); - ucs_env::error::GrpcError::from(IntegrationError::InvalidDataFormat { field_name: "payment_method", context: domain_types::errors::IntegrationErrorContext::default() }) - })?; + // `payment_method` is required for an ordinary authorization, but not for the + // second leg of a redirect-based one: connectors that finalise 3DS through + // Authorize are re-invoked after the ACS posts back to `TermUrl`, and the + // caller no longer holds the raw instrument at that point. That leg is + // identified by `redirection_response`, exactly as the connectors themselves + // identify it (`request.redirect_response.is_some()`), and it is the only case + // in which an absent `payment_method` is accepted. + let payment_method_data_action = match proto_payload.payment_method.clone() { + Some(payment_method) => Some(PaymentMethodDataAction::get_payment_method_data_action(payment_method) + .map_err(|err| { + tracing::error!("PAYMENT_AUTHORIZE_FLOW: failed to get payment method data action - error: {:?}", err); + ucs_env::error::GrpcError::from(IntegrationError::InvalidDataFormat { field_name: "payment_method", context: domain_types::errors::IntegrationErrorContext::default() }) + })?), + None if proto_payload.redirection_response.is_some() => None, + None => Err(ucs_env::error::GrpcError::from(IntegrationError::MissingRequiredField { field_name: "payment_method", context: domain_types::errors::IntegrationErrorContext::default() }))?, + }; let authorize_response = match payment_method_data_action { - PaymentMethodDataAction::CardProxy(proxy_card_details) => { + Some(PaymentMethodDataAction::CardProxy(proxy_card_details)) => { let token_data = proxy_card_details.to_token_data(); let payment_method_data = payment_method_data::PaymentMethodData::Card(payment_method_data::Card::::foreign_try_from(proxy_card_details).map_err(|err| { tracing::error!("PAYMENT_AUTHORIZE_FLOW: failed to get payment method data action - error: {:?}", err); @@ -917,7 +928,7 @@ impl PaymentService for Payments { )) .await? } - PaymentMethodDataAction::Card(card_details) => { + Some(PaymentMethodDataAction::Card(card_details)) => { tracing::info!("REGULAR: Processing regular payment authorization (no injector)"); let payment_method_data = payment_method_data::PaymentMethodData::Card(payment_method_data::Card::::foreign_try_from(card_details).map_err(|err| { tracing::error!("PAYMENT_AUTHORIZE_FLOW: failed to get payment method data action - error: {:?}", err); @@ -938,7 +949,7 @@ impl PaymentService for Payments { )) .await? } - PaymentMethodDataAction::Default => { + Some(PaymentMethodDataAction::Default) => { let payment_method_data = payment_method_data::PaymentMethodData::convert_to_domain_model_for_non_card_payment_methods(proto_payload.payment_method.clone().ok_or(ucs_env::error::GrpcError::from(IntegrationError::MissingRequiredField { field_name: "payment_method", context: domain_types::errors::IntegrationErrorContext::default() }))?) .map_err(|err| { tracing::error!("Failed to convert payment method data: {:?}", err); @@ -959,7 +970,7 @@ impl PaymentService for Payments { )) .await? } - PaymentMethodDataAction::CardWithNoCvc(card_details) => { + Some(PaymentMethodDataAction::CardWithNoCvc(card_details)) => { tracing::info!("REGULAR: Processing payment authorization with CardWithNoCvc"); let payment_method_data = payment_method_data::PaymentMethodData::CardWithNoCvc( payment_method_data::CardWithNoCvc::foreign_try_from(card_details).map_err(|err| { @@ -981,6 +992,27 @@ impl PaymentService for Payments { )) .await? } + // Redirect-return leg: no instrument was sent (guarded above by + // `redirection_response` being present). The connector finalises from the + // redirect payload and its own transaction reference; connectors that + // cannot do so reject `NoInstrumentAfterRedirect` rather than mistaking it + // for a payable instrument. + None => { + tracing::info!("REDIRECT_COMPLETION: authorization re-entered after redirect without payment method data"); + Box::pin(self.process_authorization_internal::( + &config, + payload, + metadata_payload.connector.clone(), + metadata_payload.connector_config.clone(), + metadata, + &metadata_payload, + &service_name, + &metadata_payload.request_id, + None, + payment_method_data::PaymentMethodData::NoInstrumentAfterRedirect, + )) + .await? + } }; Ok(tonic::Response::new(authorize_response)) diff --git a/crates/integrations/connector-integration/src/connectors/aci/transformers.rs b/crates/integrations/connector-integration/src/connectors/aci/transformers.rs index bf4750ab13..47e94f569a 100644 --- a/crates/integrations/connector-integration/src/connectors/aci/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/aci/transformers.rs @@ -762,7 +762,8 @@ impl | PaymentMethodData::OpenBanking(_) | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Aci"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs b/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs index 645a3442fd..d85eb95ed4 100644 --- a/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs @@ -3905,11 +3905,11 @@ impl Err(IntegrationError::NotImplemented( - ("payment method").into(), - Default::default(), - ) - .into()), + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err( + IntegrationError::NotImplemented(("payment method").into(), Default::default()) + .into(), + ), }, } } @@ -6525,7 +6525,8 @@ impl Err( + | PaymentMethodData::PaymentMethodToken(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err( IntegrationError::NotImplemented(("payment method").into(), Default::default()) .into(), ), diff --git a/crates/integrations/connector-integration/src/connectors/bambora/transformers.rs b/crates/integrations/connector-integration/src/connectors/bambora/transformers.rs index 26e085b176..c8cb8d7a27 100644 --- a/crates/integrations/connector-integration/src/connectors/bambora/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/bambora/transformers.rs @@ -298,7 +298,8 @@ impl | PaymentMethodData::MobilePayment(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { return Err(IntegrationError::NotSupported { message: "Selected payment method".to_string(), connector: "bambora", diff --git a/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs b/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs index 8a2770a97f..57bdb672f4 100644 --- a/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs @@ -767,7 +767,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( domain_types::utils::get_unimplemented_payment_method_error_message( "Bank of America", @@ -2061,7 +2062,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/barclaycard/transformers.rs b/crates/integrations/connector-integration/src/connectors/barclaycard/transformers.rs index b62f0e93fd..996172acce 100644 --- a/crates/integrations/connector-integration/src/connectors/barclaycard/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/barclaycard/transformers.rs @@ -157,6 +157,7 @@ fn payment_method_data_label( PaymentMethodData::NetworkToken(_) => "network_token", PaymentMethodData::CardWithNoCvc(_) => "card_with_no_cvc", PaymentMethodData::MobilePayment(_) => "mobile_payment", + PaymentMethodData::NoInstrumentAfterRedirect => "no_instrument_after_redirect", } } diff --git a/crates/integrations/connector-integration/src/connectors/billwerk/transformers.rs b/crates/integrations/connector-integration/src/connectors/billwerk/transformers.rs index a38bad30fc..81e71e14a0 100644 --- a/crates/integrations/connector-integration/src/connectors/billwerk/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/billwerk/transformers.rs @@ -240,7 +240,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("billwerk"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/braintree/transformers.rs b/crates/integrations/connector-integration/src/connectors/braintree/transformers.rs index 9725d6c726..3d426e7c5e 100644 --- a/crates/integrations/connector-integration/src/connectors/braintree/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/braintree/transformers.rs @@ -633,7 +633,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("braintree"), connector: "Braintree", @@ -1647,7 +1648,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("braintree"), connector: "Braintree", @@ -2684,7 +2686,8 @@ fn get_braintree_redirect_form< | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { return Err(ConnectorError::unexpected_response_error_http_status_unknown().into()); } }, @@ -3014,7 +3017,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("braintree"), connector: "Braintree", @@ -3375,7 +3379,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("braintree"), connector: "Braintree", diff --git a/crates/integrations/connector-integration/src/connectors/cryptopay/transformers.rs b/crates/integrations/connector-integration/src/connectors/cryptopay/transformers.rs index a675f1f1f3..4a3e2c177a 100644 --- a/crates/integrations/connector-integration/src/connectors/cryptopay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/cryptopay/transformers.rs @@ -111,7 +111,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: get_unimplemented_payment_method_error_message("CryptoPay"), connector: "Cryptopay", diff --git a/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs b/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs index 1fb4ae5e85..d4f99fc76c 100644 --- a/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs @@ -347,7 +347,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: domain_types::utils::get_unimplemented_payment_method_error_message( @@ -2604,7 +2605,8 @@ impl { + | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: domain_types::utils::get_unimplemented_payment_method_error_message( "Cybersource", @@ -2710,7 +2712,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Cybersource"), connector: "Cybersource", @@ -3747,7 +3750,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), Default::default(), @@ -4036,7 +4040,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), Default::default(), @@ -5138,7 +5143,8 @@ impl { + | PaymentMethodData::PaymentMethodToken(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/datatrans/transformers.rs b/crates/integrations/connector-integration/src/connectors/datatrans/transformers.rs index 17b9884d94..6e99b96ff8 100644 --- a/crates/integrations/connector-integration/src/connectors/datatrans/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/datatrans/transformers.rs @@ -789,7 +789,8 @@ impl { + | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( UNSUPPORTED_PAYMENT_METHOD_ERROR.to_string(), datatrans_context( diff --git a/crates/integrations/connector-integration/src/connectors/dlocal/transformers.rs b/crates/integrations/connector-integration/src/connectors/dlocal/transformers.rs index c1dde111c4..3561f341ad 100644 --- a/crates/integrations/connector-integration/src/connectors/dlocal/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/dlocal/transformers.rs @@ -454,7 +454,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( crate::utils::get_unimplemented_payment_method_error_message("Dlocal"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/fiserv/transformers.rs b/crates/integrations/connector-integration/src/connectors/fiserv/transformers.rs index ed495e3e37..70f9c9c043 100644 --- a/crates/integrations/connector-integration/src/connectors/fiserv/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/fiserv/transformers.rs @@ -549,7 +549,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiserv"), Default::default() diff --git a/crates/integrations/connector-integration/src/connectors/fiuu/transformers.rs b/crates/integrations/connector-integration/src/connectors/fiuu/transformers.rs index c421275d82..ac0fb0d702 100644 --- a/crates/integrations/connector-integration/src/connectors/fiuu/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/fiuu/transformers.rs @@ -691,7 +691,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), Default::default(), @@ -1062,7 +1063,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/forte/transformers.rs b/crates/integrations/connector-integration/src/connectors/forte/transformers.rs index 809fadda20..b39f3d645e 100644 --- a/crates/integrations/connector-integration/src/connectors/forte/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/forte/transformers.rs @@ -352,7 +352,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented(utils::get_unimplemented_payment_method_error_message("Forte") , Default::default()))? } } diff --git a/crates/integrations/connector-integration/src/connectors/givepayments/transformers.rs b/crates/integrations/connector-integration/src/connectors/givepayments/transformers.rs index 7763055f40..e085dabb21 100644 --- a/crates/integrations/connector-integration/src/connectors/givepayments/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/givepayments/transformers.rs @@ -447,7 +447,8 @@ impl { + | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(errors::IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Givepayments"), errors::IntegrationErrorContext { diff --git a/crates/integrations/connector-integration/src/connectors/hipay/transformers.rs b/crates/integrations/connector-integration/src/connectors/hipay/transformers.rs index 2f48a29afb..0fa280dd8c 100644 --- a/crates/integrations/connector-integration/src/connectors/hipay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/hipay/transformers.rs @@ -598,7 +598,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( "Payment method not supported for tokenization".to_string(), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs b/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs index 1d8e100945..69a23e91bf 100644 --- a/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs @@ -632,7 +632,8 @@ impl { + | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(errors::IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Imerchantsolutions"), errors::IntegrationErrorContext { diff --git a/crates/integrations/connector-integration/src/connectors/loonio/transformers.rs b/crates/integrations/connector-integration/src/connectors/loonio/transformers.rs index 327bb06501..d4defe085b 100644 --- a/crates/integrations/connector-integration/src/connectors/loonio/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/loonio/transformers.rs @@ -250,10 +250,13 @@ impl Err(IntegrationError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Loonio"), - Default::default(), - ))?, + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { + Err(IntegrationError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Loonio"), + Default::default(), + ))? + } } } } diff --git a/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs b/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs index 86081487da..bb25ad6402 100644 --- a/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs @@ -260,7 +260,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Mifinity"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/multisafepay/transformers.rs b/crates/integrations/connector-integration/src/connectors/multisafepay/transformers.rs index d3e7e46c05..5cb93769f0 100644 --- a/crates/integrations/connector-integration/src/connectors/multisafepay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/multisafepay/transformers.rs @@ -174,13 +174,12 @@ fn get_order_type_from_payment_method( | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(IntegrationError::NotImplemented( - crate::utils::get_unimplemented_payment_method_error_message("multisafepay"), - Default::default(), - )) - .attach_printable("Payment method not supported")? - } + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(IntegrationError::NotImplemented( + crate::utils::get_unimplemented_payment_method_error_message("multisafepay"), + Default::default(), + )) + .attach_printable("Payment method not supported")?, }; Ok(payment_type) @@ -374,13 +373,12 @@ fn get_gateway_from_payment_method( | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(IntegrationError::NotImplemented( - crate::utils::get_unimplemented_payment_method_error_message("multisafepay"), - Default::default(), - )) - .attach_printable("Payment method not supported")? - } + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(IntegrationError::NotImplemented( + crate::utils::get_unimplemented_payment_method_error_message("multisafepay"), + Default::default(), + )) + .attach_printable("Payment method not supported")?, }; Ok(gateway) diff --git a/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs b/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs index b843168823..2641b3cb58 100644 --- a/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs @@ -774,12 +774,11 @@ fn get_payment_details_and_product< | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(IntegrationError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("nexinets"), - Default::default(), - ))? - } + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(IntegrationError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("nexinets"), + Default::default(), + ))?, } } diff --git a/crates/integrations/connector-integration/src/connectors/noon/transformers.rs b/crates/integrations/connector-integration/src/connectors/noon/transformers.rs index a66c318857..885ccc75c5 100644 --- a/crates/integrations/connector-integration/src/connectors/noon/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/noon/transformers.rs @@ -392,7 +392,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Noon"), connector: "Noon", @@ -1313,7 +1314,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Noon"), connector: "Noon", diff --git a/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs b/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs index ceb3556d7d..1334e0dbb5 100644 --- a/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs @@ -1654,7 +1654,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Paypal"), connector: "Paypal", @@ -3212,7 +3213,8 @@ impl { + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Paypal"), connector: "Paypal", diff --git a/crates/integrations/connector-integration/src/connectors/placetopay/transformers.rs b/crates/integrations/connector-integration/src/connectors/placetopay/transformers.rs index 114db73541..398447611b 100644 --- a/crates/integrations/connector-integration/src/connectors/placetopay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/placetopay/transformers.rs @@ -211,7 +211,8 @@ impl | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Placetopay"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs index e885744bd7..5a5e1eb066 100644 --- a/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs @@ -574,7 +574,8 @@ impl { + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NoInstrumentAfterRedirect) => { return Err(IntegrationError::NotImplemented( format!("Payment Method {pm:?} not supported for Razorpay"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/redsys/transformers.rs b/crates/integrations/connector-integration/src/connectors/redsys/transformers.rs index 682487b11a..1df92a8d72 100644 --- a/crates/integrations/connector-integration/src/connectors/redsys/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/redsys/transformers.rs @@ -287,6 +287,7 @@ where | Some(PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | Some(PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_)) | Some(PaymentMethodData::CardWithNoCvc(_)) + | Some(PaymentMethodData::NoInstrumentAfterRedirect) | None => Err(IntegrationError::NotImplemented( domain_types::utils::get_unimplemented_payment_method_error_message("redsys"), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/sanlam_common/transformers.rs b/crates/integrations/connector-integration/src/connectors/sanlam_common/transformers.rs index 3c6eb05393..84e221eaf3 100644 --- a/crates/integrations/connector-integration/src/connectors/sanlam_common/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/sanlam_common/transformers.rs @@ -278,7 +278,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: get_unimplemented_payment_method_error_message("AbsaSanlam"), connector: "AbsaSanlam", diff --git a/crates/integrations/connector-integration/src/connectors/stax/transformers.rs b/crates/integrations/connector-integration/src/connectors/stax/transformers.rs index 768f528205..b242a52169 100644 --- a/crates/integrations/connector-integration/src/connectors/stax/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/stax/transformers.rs @@ -1109,7 +1109,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( "Only card and ACH bank debit tokenization are supported for Stax".to_string(), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs b/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs index 1429d2e761..e30ec33730 100644 --- a/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs @@ -1606,13 +1606,12 @@ fn create_stripe_payment_method< | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(IntegrationError::NotImplemented( - get_unimplemented_payment_method_error_message("stripe"), - Default::default(), - ) - .into()) - } + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(IntegrationError::NotImplemented( + get_unimplemented_payment_method_error_message("stripe"), + Default::default(), + ) + .into()), } } @@ -5224,7 +5223,8 @@ impl | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::NetworkToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(IntegrationError::NotImplemented( get_unimplemented_payment_method_error_message("stripe"), Default::default(), @@ -5623,6 +5623,7 @@ impl | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId( _, ) + | PaymentMethodData::NoInstrumentAfterRedirect | PaymentMethodData::Card(_) => Err(IntegrationError::NotImplemented( "Network tokenization for payment method".to_string(), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/trustly/transformers.rs b/crates/integrations/connector-integration/src/connectors/trustly/transformers.rs index 1d0683cc6d..9e127ced55 100644 --- a/crates/integrations/connector-integration/src/connectors/trustly/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/trustly/transformers.rs @@ -426,7 +426,8 @@ impl Err(error_stack::report!( + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(error_stack::report!( errors::IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Trustly"), connector: "Trustly", diff --git a/crates/integrations/connector-integration/src/connectors/trustpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/trustpay/transformers.rs index 91cb2aaffb..8a49d272ed 100644 --- a/crates/integrations/connector-integration/src/connectors/trustpay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/trustpay/transformers.rs @@ -1840,7 +1840,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("trustpay"), connector: "trustpay", diff --git a/crates/integrations/connector-integration/src/connectors/volt/transformers.rs b/crates/integrations/connector-integration/src/connectors/volt/transformers.rs index bde9deac61..b08bb83586 100644 --- a/crates/integrations/connector-integration/src/connectors/volt/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/volt/transformers.rs @@ -300,7 +300,8 @@ impl { + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { Err(error_stack::report!(IntegrationError::NotSupported { message: utils::get_unimplemented_payment_method_error_message("Volt"), connector: "Volt", diff --git a/crates/integrations/connector-integration/src/connectors/wellsfargo/transformers.rs b/crates/integrations/connector-integration/src/connectors/wellsfargo/transformers.rs index d2c1fd0207..9e7144e544 100644 --- a/crates/integrations/connector-integration/src/connectors/wellsfargo/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/wellsfargo/transformers.rs @@ -621,11 +621,14 @@ impl | PaymentMethodData::OpenBanking(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) | PaymentMethodData::CardWithNoCvc(_) - | PaymentMethodData::MobilePayment(_) => Err(IntegrationError::NotSupported { - message: "Payment method".to_string(), - connector: "Wellsfargo", - context: Default::default(), - })?, + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::NoInstrumentAfterRedirect => { + Err(IntegrationError::NotSupported { + message: "Payment method".to_string(), + connector: "Wellsfargo", + context: Default::default(), + })? + } }; // Get amount and currency - amount is in minor units (cents) diff --git a/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs index 1b3f221e2b..4dd8739496 100644 --- a/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs @@ -260,7 +260,8 @@ fn fetch_payment_instrument< | PaymentMethodData::OpenBanking(_) | PaymentMethodData::PaymentMethodToken(_) | PaymentMethodData::DecryptedWalletTokenDetailsForNetworkTransactionId(_) - | PaymentMethodData::NetworkToken(_) => Err(IntegrationError::NotImplemented(utils::get_unimplemented_payment_method_error_message("worldpay") , Default::default()) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::NoInstrumentAfterRedirect => Err(IntegrationError::NotImplemented(utils::get_unimplemented_payment_method_error_message("worldpay") , Default::default()) .into()) } } diff --git a/crates/types-traits/domain_types/src/connector_types.rs b/crates/types-traits/domain_types/src/connector_types.rs index ea042f94f9..f24bb01a82 100644 --- a/crates/types-traits/domain_types/src/connector_types.rs +++ b/crates/types-traits/domain_types/src/connector_types.rs @@ -4056,6 +4056,7 @@ impl From> for PaymentMethodData match pm_data { PaymentMethodData::Card(_) => Self::Card, PaymentMethodData::CardWithNoCvc(_) => Self::CardWithNoCvc, + PaymentMethodData::NoInstrumentAfterRedirect => Self::NoInstrumentAfterRedirect, PaymentMethodData::CardRedirect(card_redirect_data) => match card_redirect_data { payment_method_data::CardRedirectData::Knet {} => Self::Knet, payment_method_data::CardRedirectData::Benefit {} => Self::Benefit, diff --git a/crates/types-traits/domain_types/src/payment_method_data.rs b/crates/types-traits/domain_types/src/payment_method_data.rs index c1b7449e25..571e083eee 100644 --- a/crates/types-traits/domain_types/src/payment_method_data.rs +++ b/crates/types-traits/domain_types/src/payment_method_data.rs @@ -383,6 +383,17 @@ pub enum PaymentMethodData { OpenBanking(OpenBankingData), NetworkToken(NetworkTokenData), MobilePayment(MobilePaymentData), + /// No raw payment instrument accompanies this request. + /// + /// Emitted only for the second leg of a redirect-based authorization (e.g. the 3DS + /// return leg): the customer has come back from the ACS/hosted page and the caller no + /// longer holds the card or wallet details, so the connector must finalise the payment + /// from the redirect payload plus its own transaction reference instead. + /// + /// A connector that cannot finalise without an instrument should reject this variant + /// (the usual `_ => NotImplemented` arm already does), never treat it as a fallback for + /// an ordinary authorization. + NoInstrumentAfterRedirect, } impl PaymentMethodData { diff --git a/crates/types-traits/domain_types/src/types.rs b/crates/types-traits/domain_types/src/types.rs index 914ecfbb47..7d0f6823c9 100644 --- a/crates/types-traits/domain_types/src/types.rs +++ b/crates/types-traits/domain_types/src/types.rs @@ -4487,6 +4487,28 @@ impl< )?), }; + // The redirect-return leg of an authorization carries no instrument, so there is no + // payment method type to derive from it. Every other authorization still requires one. + let payment_method_type = match value.payment_method.clone() { + Some(payment_method) => >::foreign_try_from(payment_method)?, + None if matches!( + payment_method_data, + PaymentMethodData::NoInstrumentAfterRedirect + ) => + { + None + } + None => { + return Err(report!(IntegrationError::InvalidDataFormat { + field_name: "unknown", + context: IntegrationErrorContext { + additional_context: Some("Payment method data is required".to_string()), + ..Default::default() + }, + })); + } + }; + Ok(Self { authentication_data, capture_method: Some(CaptureMethod::foreign_try_from(value.capture_method)?), @@ -4501,17 +4523,7 @@ impl< .cloned() .map(BrowserInformation::foreign_try_from) .transpose()?, - payment_method_type: >::foreign_try_from( - value.payment_method.clone().ok_or_else(|| { - IntegrationError::InvalidDataFormat { - field_name: "unknown", - context: IntegrationErrorContext { - additional_context: Some("Payment method data is required".to_string()), - ..Default::default() - }, - } - })?, - )?, + payment_method_type, minor_amount: common_utils::types::MinorUnit::new(amount.minor_amount), email, customer_document_details, @@ -13188,6 +13200,9 @@ pub enum PaymentMethodDataType { QwikcilverWalletDirect, Skrill, CardWithNoCvc, + /// Companion of [`PaymentMethodData::NoInstrumentAfterRedirect`]: the redirect-return leg + /// carries no instrument, so it matches no mandate-capable payment method data type. + NoInstrumentAfterRedirect, } impl ForeignTryFrom for Secret {