fix(router): mark retry payment as failure if connector_tokenization fails (#5114)

This commit is contained in:
Shankar Singh C
2024-07-01 20:07:41 +05:30
committed by GitHub
parent af2497b501
commit ecb8cafaed
9 changed files with 145 additions and 69 deletions

View File

@ -33,7 +33,6 @@ use crate::{
storage::enums, storage::enums,
transformers::{ForeignFrom, ForeignTryFrom}, transformers::{ForeignFrom, ForeignTryFrom},
}, },
unimplemented_payment_method,
utils::OptionExt, utils::OptionExt,
}; };
@ -1761,9 +1760,11 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent
let payment_method_token = match payment_method_token { let payment_method_token = match payment_method_token {
types::PaymentMethodToken::Token(payment_method_token) => payment_method_token, types::PaymentMethodToken::Token(payment_method_token) => payment_method_token,
types::PaymentMethodToken::ApplePayDecrypt(_) => Err( types::PaymentMethodToken::ApplePayDecrypt(_) => {
unimplemented_payment_method!("Apple Pay", "Simplified", "Stripe"), Err(errors::ConnectorError::InvalidWalletToken {
)?, wallet_name: "Apple Pay".to_string(),
})?
}
}; };
Some(StripePaymentMethodData::Wallet( Some(StripePaymentMethodData::Wallet(
StripeWallet::ApplepayPayment(ApplepayPayment { StripeWallet::ApplepayPayment(ApplepayPayment {

View File

@ -302,6 +302,7 @@ where
#[cfg(not(feature = "frm"))] #[cfg(not(feature = "frm"))]
None, None,
&business_profile, &business_profile,
false,
) )
.await?; .await?;
@ -373,6 +374,7 @@ where
#[cfg(not(feature = "frm"))] #[cfg(not(feature = "frm"))]
None, None,
&business_profile, &business_profile,
false,
) )
.await?; .await?;
@ -1394,6 +1396,7 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest>(
header_payload: HeaderPayload, header_payload: HeaderPayload,
frm_suggestion: Option<storage_enums::FrmSuggestion>, frm_suggestion: Option<storage_enums::FrmSuggestion>,
business_profile: &storage::business_profile::BusinessProfile, business_profile: &storage::business_profile::BusinessProfile,
is_retry_payment: bool,
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>> ) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
where where
F: Send + Clone + Sync, F: Send + Clone + Sync,
@ -1476,7 +1479,7 @@ where
router_data = router_data.add_session_token(state, &connector).await?; router_data = router_data.add_session_token(state, &connector).await?;
let mut should_continue_further = access_token::update_router_data_with_access_token_result( let should_continue_further = access_token::update_router_data_with_access_token_result(
&add_access_token_result, &add_access_token_result,
&mut router_data, &mut router_data,
&call_connector_action, &call_connector_action,
@ -1526,16 +1529,22 @@ where
_ => (), _ => (),
}; };
let pm_token = router_data let payment_method_token_response = router_data
.add_payment_method_token(state, &connector, &tokenization_action) .add_payment_method_token(
state,
&connector,
&tokenization_action,
should_continue_further,
)
.await?; .await?;
if let Some(payment_method_token) = pm_token.clone() {
router_data.payment_method_token = Some( let mut should_continue_further =
hyperswitch_domain_models::router_data::PaymentMethodToken::Token(Secret::new( tokenization::update_router_data_with_payment_method_token_result(
payment_method_token, payment_method_token_response,
)), &mut router_data,
is_retry_payment,
should_continue_further,
); );
};
(router_data, should_continue_further) = complete_preprocessing_steps_if_required( (router_data, should_continue_further) = complete_preprocessing_steps_if_required(
state, state,

View File

@ -84,13 +84,17 @@ pub trait Feature<F, T> {
_state: &SessionState, _state: &SessionState,
_connector: &api::ConnectorData, _connector: &api::ConnectorData,
_tokenization_action: &payments::TokenizationAction, _tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> _should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult>
where where
F: Clone, F: Clone,
Self: Sized, Self: Sized,
dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>, dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>,
{ {
Ok(None) Ok(types::PaymentMethodTokenResult {
payment_method_token_result: Ok(None),
is_payment_method_tokenization_performed: false,
})
} }
async fn preprocessing_steps<'a>( async fn preprocessing_steps<'a>(

View File

@ -140,7 +140,8 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
state: &SessionState, state: &SessionState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> { should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
let request = self.request.clone(); let request = self.request.clone();
tokenization::add_payment_method_token( tokenization::add_payment_method_token(
state, state,
@ -148,6 +149,7 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
tokenization_action, tokenization_action,
self, self,
types::PaymentMethodTokenizationData::try_from(request)?, types::PaymentMethodTokenizationData::try_from(request)?,
should_continue_payment,
) )
.await .await
} }

View File

@ -103,7 +103,8 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
state: &SessionState, state: &SessionState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
_tokenization_action: &payments::TokenizationAction, _tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> { should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
// TODO: remove this and handle it in core // TODO: remove this and handle it in core
if matches!(connector.connector_name, types::Connector::Payme) { if matches!(connector.connector_name, types::Connector::Payme) {
let request = self.request.clone(); let request = self.request.clone();
@ -113,10 +114,14 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
&payments::TokenizationAction::TokenizeInConnector, &payments::TokenizationAction::TokenizeInConnector,
self, self,
types::PaymentMethodTokenizationData::try_from(request)?, types::PaymentMethodTokenizationData::try_from(request)?,
should_continue_payment,
) )
.await .await
} else { } else {
Ok(None) Ok(types::PaymentMethodTokenResult {
payment_method_token_result: Ok(None),
is_payment_method_tokenization_performed: false,
})
} }
} }

View File

@ -107,7 +107,8 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
state: &SessionState, state: &SessionState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> { should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
let request = self.request.clone(); let request = self.request.clone();
tokenization::add_payment_method_token( tokenization::add_payment_method_token(
state, state,
@ -115,6 +116,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
tokenization_action, tokenization_action,
self, self,
types::PaymentMethodTokenizationData::try_from(request)?, types::PaymentMethodTokenizationData::try_from(request)?,
should_continue_payment,
) )
.await .await
} }

View File

@ -321,6 +321,7 @@ where
api::HeaderPayload::default(), api::HeaderPayload::default(),
frm_suggestion, frm_suggestion,
business_profile, business_profile,
true,
) )
.await .await
} }

View File

@ -784,7 +784,9 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>, router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
pm_token_request_data: types::PaymentMethodTokenizationData, pm_token_request_data: types::PaymentMethodTokenizationData,
) -> RouterResult<Option<String>> { should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
if should_continue_payment {
match tokenization_action { match tokenization_action {
payments::TokenizationAction::TokenizeInConnector payments::TokenizationAction::TokenizeInConnector
| payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => { | payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => {
@ -794,8 +796,10 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
types::PaymentsResponseData, types::PaymentsResponseData,
> = connector.connector.get_connector_integration(); > = connector.connector.get_connector_integration();
let pm_token_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> = let pm_token_response_data: Result<
Err(types::ErrorResponse::default()); types::PaymentsResponseData,
types::ErrorResponse,
> = Err(types::ErrorResponse::default());
let pm_token_router_data = let pm_token_router_data =
helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>( helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
@ -827,19 +831,62 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
]), ]),
); );
let pm_token = match resp.response { let payment_token_resp = resp.response.map(|res| {
Ok(response) => match response { if let types::PaymentsResponseData::TokenizationResponse { token } = res {
types::PaymentsResponseData::TokenizationResponse { token } => Some(token), Some(token)
_ => None, } else {
},
Err(err) => {
logger::debug!(payment_method_tokenization_error=?err);
None None
} }
}; });
Ok(pm_token)
Ok(types::PaymentMethodTokenResult {
payment_method_token_result: payment_token_resp,
is_payment_method_tokenization_performed: true,
})
} }
_ => Ok(None), _ => Ok(types::PaymentMethodTokenResult {
payment_method_token_result: Ok(None),
is_payment_method_tokenization_performed: false,
}),
}
} else {
logger::debug!("Skipping connector tokenization based on should_continue_payment flag");
Ok(types::PaymentMethodTokenResult {
payment_method_token_result: Ok(None),
is_payment_method_tokenization_performed: false,
})
}
}
pub fn update_router_data_with_payment_method_token_result<F: Clone, T>(
payment_method_token_result: types::PaymentMethodTokenResult,
router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
is_retry_payment: bool,
should_continue_further: bool,
) -> bool {
if payment_method_token_result.is_payment_method_tokenization_performed {
match payment_method_token_result.payment_method_token_result {
Ok(pm_token_result) => {
router_data.payment_method_token = pm_token_result.map(|pm_token| {
hyperswitch_domain_models::router_data::PaymentMethodToken::Token(
masking::Secret::new(pm_token),
)
});
true
}
Err(err) => {
if is_retry_payment {
router_data.response = Err(err);
false
} else {
logger::debug!(payment_method_tokenization_error=?err);
true
}
}
}
} else {
should_continue_further
} }
} }

View File

@ -545,6 +545,11 @@ pub struct AddAccessTokenResult {
pub connector_supports_access_token: bool, pub connector_supports_access_token: bool,
} }
pub struct PaymentMethodTokenResult {
pub payment_method_token_result: Result<Option<String>, ErrorResponse>,
pub is_payment_method_tokenization_performed: bool,
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Redirection { pub enum Redirection {
Redirect, Redirect,