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

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

View File

@ -84,13 +84,17 @@ pub trait Feature<F, T> {
_state: &SessionState,
_connector: &api::ConnectorData,
_tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>>
_should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult>
where
F: Clone,
Self: Sized,
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>(

View File

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

View File

@ -103,7 +103,8 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
state: &SessionState,
connector: &api::ConnectorData,
_tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> {
should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
// TODO: remove this and handle it in core
if matches!(connector.connector_name, types::Connector::Payme) {
let request = self.request.clone();
@ -113,10 +114,14 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
&payments::TokenizationAction::TokenizeInConnector,
self,
types::PaymentMethodTokenizationData::try_from(request)?,
should_continue_payment,
)
.await
} 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,
connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> {
should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
let request = self.request.clone();
tokenization::add_payment_method_token(
state,
@ -115,6 +116,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
tokenization_action,
self,
types::PaymentMethodTokenizationData::try_from(request)?,
should_continue_payment,
)
.await
}

View File

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

View File

@ -784,62 +784,109 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
tokenization_action: &payments::TokenizationAction,
router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
pm_token_request_data: types::PaymentMethodTokenizationData,
) -> RouterResult<Option<String>> {
match tokenization_action {
payments::TokenizationAction::TokenizeInConnector
| payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => {
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
should_continue_payment: bool,
) -> RouterResult<types::PaymentMethodTokenResult> {
if should_continue_payment {
match tokenization_action {
payments::TokenizationAction::TokenizeInConnector
| payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => {
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let pm_token_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
Err(types::ErrorResponse::default());
let pm_token_response_data: Result<
types::PaymentsResponseData,
types::ErrorResponse,
> = Err(types::ErrorResponse::default());
let pm_token_router_data =
helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
router_data.clone(),
pm_token_request_data,
pm_token_response_data,
let pm_token_router_data =
helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
router_data.clone(),
pm_token_request_data,
pm_token_response_data,
);
router_data
.request
.set_session_token(pm_token_router_data.session_token.clone());
let resp = services::execute_connector_processing_step(
state,
connector_integration,
&pm_token_router_data,
payments::CallConnectorAction::Trigger,
None,
)
.await
.to_payment_failed_response()?;
metrics::CONNECTOR_PAYMENT_METHOD_TOKENIZATION.add(
&metrics::CONTEXT,
1,
&add_attributes([
("connector", connector.connector_name.to_string()),
("payment_method", router_data.payment_method.to_string()),
]),
);
router_data
.request
.set_session_token(pm_token_router_data.session_token.clone());
let payment_token_resp = resp.response.map(|res| {
if let types::PaymentsResponseData::TokenizationResponse { token } = res {
Some(token)
} else {
None
}
});
let resp = services::execute_connector_processing_step(
state,
connector_integration,
&pm_token_router_data,
payments::CallConnectorAction::Trigger,
None,
)
.await
.to_payment_failed_response()?;
metrics::CONNECTOR_PAYMENT_METHOD_TOKENIZATION.add(
&metrics::CONTEXT,
1,
&add_attributes([
("connector", connector.connector_name.to_string()),
("payment_method", router_data.payment_method.to_string()),
]),
);
let pm_token = match resp.response {
Ok(response) => match response {
types::PaymentsResponseData::TokenizationResponse { token } => Some(token),
_ => None,
},
Err(err) => {
logger::debug!(payment_method_tokenization_error=?err);
None
}
};
Ok(pm_token)
Ok(types::PaymentMethodTokenResult {
payment_method_token_result: payment_token_resp,
is_payment_method_tokenization_performed: true,
})
}
_ => Ok(types::PaymentMethodTokenResult {
payment_method_token_result: Ok(None),
is_payment_method_tokenization_performed: false,
}),
}
_ => Ok(None),
} 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
}
}