From 8cfa966d34e914c8df06f000b59fdad53d3f9902 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:29:53 +0530 Subject: [PATCH] feat(connectors): [Stripe] add extended authorization for cards (#9084) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/connectors/stripe/transformers.rs | 124 +++++++++++++++--- .../src/router_data.rs | 10 ++ 2 files changed, 119 insertions(+), 15 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs index 00e3647029..ab9232df55 100644 --- a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, fmt::Debug, ops::Deref}; use api_models::{self, enums as api_enums, payments}; use common_enums::{enums, AttemptStatus, PaymentChargeType, StripeChargeType}; -use common_types::payments::{AcceptanceType, SplitPaymentsRequest}; +use common_types::{ + payments::{AcceptanceType, SplitPaymentsRequest}, + primitive_wrappers, +}; use common_utils::{ collect_missing_value_keys, errors::CustomResult, @@ -19,7 +22,7 @@ use hyperswitch_domain_models::{ }, router_data::{ AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData, - PaymentMethodToken, RouterData, + ExtendedAuthorizationResponseData, PaymentMethodToken, RouterData, }, router_flow_types::{Execute, RSync}, router_request_types::{ @@ -287,6 +290,9 @@ pub struct StripeCardData { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "payment_method_options[card][request_incremental_authorization]")] pub request_incremental_authorization: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "payment_method_options[card][request_extended_authorization]")] + request_extended_authorization: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -296,6 +302,12 @@ pub enum StripeRequestIncrementalAuthorization { Never, } +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum StripeRequestExtendedAuthorization { + IfAvailable, +} + #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripePayLaterData { #[serde(rename = "payment_method_data[type]")] @@ -1260,6 +1272,7 @@ fn create_stripe_payment_method( is_customer_initiated_mandate_payment: Option, billing_address: StripeBillingAddress, request_incremental_authorization: bool, + request_extended_authorization: Option, ) -> Result< ( StripePaymentMethodData, @@ -1279,6 +1292,7 @@ fn create_stripe_payment_method( card_details, payment_method_auth_type, request_incremental_authorization, + request_extended_authorization, ))?, Some(StripePaymentMethodType::Card), billing_address, @@ -1497,10 +1511,27 @@ fn get_stripe_card_network(card_network: common_enums::CardNetwork) -> Option for StripePaymentMethodData { +impl + TryFrom<( + &Card, + Auth3ds, + bool, + Option, + )> for StripePaymentMethodData +{ type Error = ConnectorError; fn try_from( - (card, payment_method_auth_type, request_incremental_authorization): (&Card, Auth3ds, bool), + ( + card, + payment_method_auth_type, + request_incremental_authorization, + request_extended_authorization, + ): ( + &Card, + Auth3ds, + bool, + Option, + ), ) -> Result { Ok(Self::Card(StripeCardData { payment_method_data_type: StripePaymentMethodType::Card, @@ -1518,6 +1549,14 @@ impl TryFrom<(&Card, Auth3ds, bool)> for StripePaymentMethodData { } else { None }, + request_extended_authorization: if request_extended_authorization + .map(|request_extended_authorization| request_extended_authorization.is_true()) + .unwrap_or(false) + { + Some(StripeRequestExtendedAuthorization::IfAvailable) + } else { + None + }, })) } } @@ -1868,6 +1907,7 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest .clone() .and_then(get_stripe_card_network), request_incremental_authorization: None, + request_extended_authorization: None, }), PaymentMethodData::CardRedirect(_) | PaymentMethodData::Wallet(_) @@ -1917,6 +1957,7 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest } })?, item.request.request_incremental_authorization, + item.request.request_extended_authorization, )?; validate_shipping_address_against_payment_method( @@ -2243,6 +2284,7 @@ impl TryFrom<&TokenizationRouterData> for TokenRequest { None, StripeBillingAddress::default(), false, + None, )? .0 } @@ -2537,6 +2579,21 @@ pub struct StripeAdditionalCardDetails { checks: Option, three_d_secure: Option, network_transaction_id: Option, + extended_authorization: Option, + #[serde(default, with = "common_utils::custom_serde::timestamp::option")] + pub capture_before: Option, +} + +#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] +pub struct StripeExtendedAuthorizationResponse { + status: Option, +} + +#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum StripeExtendedAuthorizationStatus { + Disabled, + Enabled, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] @@ -2586,25 +2643,45 @@ pub enum StripePaymentMethodDetailsResponse { pub struct AdditionalPaymentMethodDetails { pub payment_checks: Option, pub authentication_details: Option, + pub extended_authorization: Option, + pub capture_before: Option, } -impl From for AdditionalPaymentMethodConnectorResponse { - fn from(item: AdditionalPaymentMethodDetails) -> Self { +impl From<&AdditionalPaymentMethodDetails> for AdditionalPaymentMethodConnectorResponse { + fn from(item: &AdditionalPaymentMethodDetails) -> Self { Self::Card { - authentication_data: item.authentication_details, - payment_checks: item.payment_checks, + authentication_data: item.authentication_details.clone(), + payment_checks: item.payment_checks.clone(), card_network: None, domestic_network: None, } } } +impl From<&AdditionalPaymentMethodDetails> for ExtendedAuthorizationResponseData { + fn from(item: &AdditionalPaymentMethodDetails) -> Self { + Self { + extended_authentication_applied: item.extended_authorization.as_ref().map( + |extended_authorization| { + primitive_wrappers::ExtendedAuthorizationAppliedBool::from(matches!( + extended_authorization.status, + Some(StripeExtendedAuthorizationStatus::Enabled) + )) + }, + ), + capture_before: item.capture_before, + } + } +} + impl StripePaymentMethodDetailsResponse { pub fn get_additional_payment_method_data(&self) -> Option { match self { Self::Card { card } => Some(AdditionalPaymentMethodDetails { payment_checks: card.checks.clone(), authentication_details: card.three_d_secure.clone(), + extended_authorization: card.extended_authorization.clone(), + capture_before: card.capture_before, }), Self::Ideal { .. } | Self::Bancontact { .. } @@ -2694,16 +2771,31 @@ pub struct SetupIntentResponse { fn extract_payment_method_connector_response_from_latest_charge( stripe_charge_enum: &StripeChargeEnum, ) -> Option { - if let StripeChargeEnum::ChargeObject(charge_object) = stripe_charge_enum { - charge_object - .payment_method_details - .as_ref() - .and_then(StripePaymentMethodDetailsResponse::get_additional_payment_method_data) + let additional_payment_method_details = + if let StripeChargeEnum::ChargeObject(charge_object) = stripe_charge_enum { + charge_object + .payment_method_details + .as_ref() + .and_then(StripePaymentMethodDetailsResponse::get_additional_payment_method_data) + } else { + None + }; + + let additional_payment_method_data = additional_payment_method_details + .as_ref() + .map(AdditionalPaymentMethodConnectorResponse::from); + let extended_authorization_data = additional_payment_method_details + .as_ref() + .map(ExtendedAuthorizationResponseData::from); + + if additional_payment_method_data.is_some() || extended_authorization_data.is_some() { + Some(ConnectorResponseData::new( + additional_payment_method_data, + extended_authorization_data, + )) } else { None } - .map(AdditionalPaymentMethodConnectorResponse::from) - .map(ConnectorResponseData::with_additional_payment_method_data) } fn extract_payment_method_connector_response_from_latest_attempt( @@ -2717,6 +2809,7 @@ fn extract_payment_method_connector_response_from_latest_attempt( } else { None } + .as_ref() .map(AdditionalPaymentMethodConnectorResponse::from) .map(ConnectorResponseData::with_additional_payment_method_data) } @@ -4166,6 +4259,7 @@ impl ccard, payment_method_auth_type, item.request.request_incremental_authorization, + None, ))?) } PaymentMethodData::PayLater(_) => Ok(Self::PayLater(StripePayLaterData { diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 6e3d7827d8..3177b1b0a4 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -474,6 +474,16 @@ impl ConnectorResponseData { extended_authorization_response_data: None, } } + pub fn new( + additional_payment_method_data: Option, + extended_authorization_response_data: Option, + ) -> Self { + Self { + additional_payment_method_data, + extended_authorization_response_data, + } + } + pub fn get_extended_authorization_response_data( &self, ) -> Option<&ExtendedAuthorizationResponseData> {