feat(connectors): [Stripe] add extended authorization for cards (#9084)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
AkshayaFoiger
2025-08-29 16:29:53 +05:30
committed by GitHub
parent 60ef06e8ff
commit 8cfa966d34
2 changed files with 119 additions and 15 deletions

View File

@ -2,7 +2,10 @@ use std::{collections::HashMap, fmt::Debug, ops::Deref};
use api_models::{self, enums as api_enums, payments}; use api_models::{self, enums as api_enums, payments};
use common_enums::{enums, AttemptStatus, PaymentChargeType, StripeChargeType}; use common_enums::{enums, AttemptStatus, PaymentChargeType, StripeChargeType};
use common_types::payments::{AcceptanceType, SplitPaymentsRequest}; use common_types::{
payments::{AcceptanceType, SplitPaymentsRequest},
primitive_wrappers,
};
use common_utils::{ use common_utils::{
collect_missing_value_keys, collect_missing_value_keys,
errors::CustomResult, errors::CustomResult,
@ -19,7 +22,7 @@ use hyperswitch_domain_models::{
}, },
router_data::{ router_data::{
AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData, AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData,
PaymentMethodToken, RouterData, ExtendedAuthorizationResponseData, PaymentMethodToken, RouterData,
}, },
router_flow_types::{Execute, RSync}, router_flow_types::{Execute, RSync},
router_request_types::{ router_request_types::{
@ -287,6 +290,9 @@ pub struct StripeCardData {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "payment_method_options[card][request_incremental_authorization]")] #[serde(rename = "payment_method_options[card][request_incremental_authorization]")]
pub request_incremental_authorization: Option<StripeRequestIncrementalAuthorization>, pub request_incremental_authorization: Option<StripeRequestIncrementalAuthorization>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "payment_method_options[card][request_extended_authorization]")]
request_extended_authorization: Option<StripeRequestExtendedAuthorization>,
} }
#[derive(Debug, Eq, PartialEq, Serialize)] #[derive(Debug, Eq, PartialEq, Serialize)]
@ -296,6 +302,12 @@ pub enum StripeRequestIncrementalAuthorization {
Never, Never,
} }
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StripeRequestExtendedAuthorization {
IfAvailable,
}
#[derive(Debug, Eq, PartialEq, Serialize)] #[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripePayLaterData { pub struct StripePayLaterData {
#[serde(rename = "payment_method_data[type]")] #[serde(rename = "payment_method_data[type]")]
@ -1260,6 +1272,7 @@ fn create_stripe_payment_method(
is_customer_initiated_mandate_payment: Option<bool>, is_customer_initiated_mandate_payment: Option<bool>,
billing_address: StripeBillingAddress, billing_address: StripeBillingAddress,
request_incremental_authorization: bool, request_incremental_authorization: bool,
request_extended_authorization: Option<primitive_wrappers::RequestExtendedAuthorizationBool>,
) -> Result< ) -> Result<
( (
StripePaymentMethodData, StripePaymentMethodData,
@ -1279,6 +1292,7 @@ fn create_stripe_payment_method(
card_details, card_details,
payment_method_auth_type, payment_method_auth_type,
request_incremental_authorization, request_incremental_authorization,
request_extended_authorization,
))?, ))?,
Some(StripePaymentMethodType::Card), Some(StripePaymentMethodType::Card),
billing_address, billing_address,
@ -1497,10 +1511,27 @@ fn get_stripe_card_network(card_network: common_enums::CardNetwork) -> Option<St
} }
} }
impl TryFrom<(&Card, Auth3ds, bool)> for StripePaymentMethodData { impl
TryFrom<(
&Card,
Auth3ds,
bool,
Option<primitive_wrappers::RequestExtendedAuthorizationBool>,
)> for StripePaymentMethodData
{
type Error = ConnectorError; type Error = ConnectorError;
fn try_from( 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<primitive_wrappers::RequestExtendedAuthorizationBool>,
),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
Ok(Self::Card(StripeCardData { Ok(Self::Card(StripeCardData {
payment_method_data_type: StripePaymentMethodType::Card, payment_method_data_type: StripePaymentMethodType::Card,
@ -1518,6 +1549,14 @@ impl TryFrom<(&Card, Auth3ds, bool)> for StripePaymentMethodData {
} else { } else {
None 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() .clone()
.and_then(get_stripe_card_network), .and_then(get_stripe_card_network),
request_incremental_authorization: None, request_incremental_authorization: None,
request_extended_authorization: None,
}), }),
PaymentMethodData::CardRedirect(_) PaymentMethodData::CardRedirect(_)
| PaymentMethodData::Wallet(_) | PaymentMethodData::Wallet(_)
@ -1917,6 +1957,7 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest
} }
})?, })?,
item.request.request_incremental_authorization, item.request.request_incremental_authorization,
item.request.request_extended_authorization,
)?; )?;
validate_shipping_address_against_payment_method( validate_shipping_address_against_payment_method(
@ -2243,6 +2284,7 @@ impl TryFrom<&TokenizationRouterData> for TokenRequest {
None, None,
StripeBillingAddress::default(), StripeBillingAddress::default(),
false, false,
None,
)? )?
.0 .0
} }
@ -2537,6 +2579,21 @@ pub struct StripeAdditionalCardDetails {
checks: Option<Value>, checks: Option<Value>,
three_d_secure: Option<Value>, three_d_secure: Option<Value>,
network_transaction_id: Option<String>, network_transaction_id: Option<String>,
extended_authorization: Option<StripeExtendedAuthorizationResponse>,
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub capture_before: Option<PrimitiveDateTime>,
}
#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct StripeExtendedAuthorizationResponse {
status: Option<StripeExtendedAuthorizationStatus>,
}
#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum StripeExtendedAuthorizationStatus {
Disabled,
Enabled,
} }
#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] #[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)]
@ -2586,25 +2643,45 @@ pub enum StripePaymentMethodDetailsResponse {
pub struct AdditionalPaymentMethodDetails { pub struct AdditionalPaymentMethodDetails {
pub payment_checks: Option<Value>, pub payment_checks: Option<Value>,
pub authentication_details: Option<Value>, pub authentication_details: Option<Value>,
pub extended_authorization: Option<StripeExtendedAuthorizationResponse>,
pub capture_before: Option<PrimitiveDateTime>,
} }
impl From<AdditionalPaymentMethodDetails> for AdditionalPaymentMethodConnectorResponse { impl From<&AdditionalPaymentMethodDetails> for AdditionalPaymentMethodConnectorResponse {
fn from(item: AdditionalPaymentMethodDetails) -> Self { fn from(item: &AdditionalPaymentMethodDetails) -> Self {
Self::Card { Self::Card {
authentication_data: item.authentication_details, authentication_data: item.authentication_details.clone(),
payment_checks: item.payment_checks, payment_checks: item.payment_checks.clone(),
card_network: None, card_network: None,
domestic_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 { impl StripePaymentMethodDetailsResponse {
pub fn get_additional_payment_method_data(&self) -> Option<AdditionalPaymentMethodDetails> { pub fn get_additional_payment_method_data(&self) -> Option<AdditionalPaymentMethodDetails> {
match self { match self {
Self::Card { card } => Some(AdditionalPaymentMethodDetails { Self::Card { card } => Some(AdditionalPaymentMethodDetails {
payment_checks: card.checks.clone(), payment_checks: card.checks.clone(),
authentication_details: card.three_d_secure.clone(), authentication_details: card.three_d_secure.clone(),
extended_authorization: card.extended_authorization.clone(),
capture_before: card.capture_before,
}), }),
Self::Ideal { .. } Self::Ideal { .. }
| Self::Bancontact { .. } | Self::Bancontact { .. }
@ -2694,16 +2771,31 @@ pub struct SetupIntentResponse {
fn extract_payment_method_connector_response_from_latest_charge( fn extract_payment_method_connector_response_from_latest_charge(
stripe_charge_enum: &StripeChargeEnum, stripe_charge_enum: &StripeChargeEnum,
) -> Option<ConnectorResponseData> { ) -> Option<ConnectorResponseData> {
if let StripeChargeEnum::ChargeObject(charge_object) = stripe_charge_enum { let additional_payment_method_details =
charge_object if let StripeChargeEnum::ChargeObject(charge_object) = stripe_charge_enum {
.payment_method_details charge_object
.as_ref() .payment_method_details
.and_then(StripePaymentMethodDetailsResponse::get_additional_payment_method_data) .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 { } else {
None None
} }
.map(AdditionalPaymentMethodConnectorResponse::from)
.map(ConnectorResponseData::with_additional_payment_method_data)
} }
fn extract_payment_method_connector_response_from_latest_attempt( fn extract_payment_method_connector_response_from_latest_attempt(
@ -2717,6 +2809,7 @@ fn extract_payment_method_connector_response_from_latest_attempt(
} else { } else {
None None
} }
.as_ref()
.map(AdditionalPaymentMethodConnectorResponse::from) .map(AdditionalPaymentMethodConnectorResponse::from)
.map(ConnectorResponseData::with_additional_payment_method_data) .map(ConnectorResponseData::with_additional_payment_method_data)
} }
@ -4166,6 +4259,7 @@ impl
ccard, ccard,
payment_method_auth_type, payment_method_auth_type,
item.request.request_incremental_authorization, item.request.request_incremental_authorization,
None,
))?) ))?)
} }
PaymentMethodData::PayLater(_) => Ok(Self::PayLater(StripePayLaterData { PaymentMethodData::PayLater(_) => Ok(Self::PayLater(StripePayLaterData {

View File

@ -474,6 +474,16 @@ impl ConnectorResponseData {
extended_authorization_response_data: None, extended_authorization_response_data: None,
} }
} }
pub fn new(
additional_payment_method_data: Option<AdditionalPaymentMethodConnectorResponse>,
extended_authorization_response_data: Option<ExtendedAuthorizationResponseData>,
) -> Self {
Self {
additional_payment_method_data,
extended_authorization_response_data,
}
}
pub fn get_extended_authorization_response_data( pub fn get_extended_authorization_response_data(
&self, &self,
) -> Option<&ExtendedAuthorizationResponseData> { ) -> Option<&ExtendedAuthorizationResponseData> {