Skip to content

Commit ecb8caf

Browse files
fix(router): mark retry payment as failure if connector_tokenization fails (#5114)
1 parent af2497b commit ecb8caf

File tree

9 files changed

+146
-70
lines changed

9 files changed

+146
-70
lines changed

crates/router/src/connector/stripe/transformers.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use crate::{
3333
storage::enums,
3434
transformers::{ForeignFrom, ForeignTryFrom},
3535
},
36-
unimplemented_payment_method,
3736
utils::OptionExt,
3837
};
3938

@@ -1761,9 +1760,11 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent
17611760

17621761
let payment_method_token = match payment_method_token {
17631762
types::PaymentMethodToken::Token(payment_method_token) => payment_method_token,
1764-
types::PaymentMethodToken::ApplePayDecrypt(_) => Err(
1765-
unimplemented_payment_method!("Apple Pay", "Simplified", "Stripe"),
1766-
)?,
1763+
types::PaymentMethodToken::ApplePayDecrypt(_) => {
1764+
Err(errors::ConnectorError::InvalidWalletToken {
1765+
wallet_name: "Apple Pay".to_string(),
1766+
})?
1767+
}
17671768
};
17681769
Some(StripePaymentMethodData::Wallet(
17691770
StripeWallet::ApplepayPayment(ApplepayPayment {

crates/router/src/core/payments.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ where
302302
#[cfg(not(feature = "frm"))]
303303
None,
304304
&business_profile,
305+
false,
305306
)
306307
.await?;
307308

@@ -373,6 +374,7 @@ where
373374
#[cfg(not(feature = "frm"))]
374375
None,
375376
&business_profile,
377+
false,
376378
)
377379
.await?;
378380

@@ -1394,6 +1396,7 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest>(
13941396
header_payload: HeaderPayload,
13951397
frm_suggestion: Option<storage_enums::FrmSuggestion>,
13961398
business_profile: &storage::business_profile::BusinessProfile,
1399+
is_retry_payment: bool,
13971400
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
13981401
where
13991402
F: Send + Clone + Sync,
@@ -1476,7 +1479,7 @@ where
14761479

14771480
router_data = router_data.add_session_token(state, &connector).await?;
14781481

1479-
let mut should_continue_further = access_token::update_router_data_with_access_token_result(
1482+
let should_continue_further = access_token::update_router_data_with_access_token_result(
14801483
&add_access_token_result,
14811484
&mut router_data,
14821485
&call_connector_action,
@@ -1526,16 +1529,22 @@ where
15261529
_ => (),
15271530
};
15281531

1529-
let pm_token = router_data
1530-
.add_payment_method_token(state, &connector, &tokenization_action)
1532+
let payment_method_token_response = router_data
1533+
.add_payment_method_token(
1534+
state,
1535+
&connector,
1536+
&tokenization_action,
1537+
should_continue_further,
1538+
)
15311539
.await?;
1532-
if let Some(payment_method_token) = pm_token.clone() {
1533-
router_data.payment_method_token = Some(
1534-
hyperswitch_domain_models::router_data::PaymentMethodToken::Token(Secret::new(
1535-
payment_method_token,
1536-
)),
1540+
1541+
let mut should_continue_further =
1542+
tokenization::update_router_data_with_payment_method_token_result(
1543+
payment_method_token_response,
1544+
&mut router_data,
1545+
is_retry_payment,
1546+
should_continue_further,
15371547
);
1538-
};
15391548

15401549
(router_data, should_continue_further) = complete_preprocessing_steps_if_required(
15411550
state,

crates/router/src/core/payments/flows.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@ pub trait Feature<F, T> {
8484
_state: &SessionState,
8585
_connector: &api::ConnectorData,
8686
_tokenization_action: &payments::TokenizationAction,
87-
) -> RouterResult<Option<String>>
87+
_should_continue_payment: bool,
88+
) -> RouterResult<types::PaymentMethodTokenResult>
8889
where
8990
F: Clone,
9091
Self: Sized,
9192
dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>,
9293
{
93-
Ok(None)
94+
Ok(types::PaymentMethodTokenResult {
95+
payment_method_token_result: Ok(None),
96+
is_payment_method_tokenization_performed: false,
97+
})
9498
}
9599

96100
async fn preprocessing_steps<'a>(

crates/router/src/core/payments/flows/authorize_flow.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,16 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
140140
state: &SessionState,
141141
connector: &api::ConnectorData,
142142
tokenization_action: &payments::TokenizationAction,
143-
) -> RouterResult<Option<String>> {
143+
should_continue_payment: bool,
144+
) -> RouterResult<types::PaymentMethodTokenResult> {
144145
let request = self.request.clone();
145146
tokenization::add_payment_method_token(
146147
state,
147148
connector,
148149
tokenization_action,
149150
self,
150151
types::PaymentMethodTokenizationData::try_from(request)?,
152+
should_continue_payment,
151153
)
152154
.await
153155
}

crates/router/src/core/payments/flows/complete_authorize_flow.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
103103
state: &SessionState,
104104
connector: &api::ConnectorData,
105105
_tokenization_action: &payments::TokenizationAction,
106-
) -> RouterResult<Option<String>> {
106+
should_continue_payment: bool,
107+
) -> RouterResult<types::PaymentMethodTokenResult> {
107108
// TODO: remove this and handle it in core
108109
if matches!(connector.connector_name, types::Connector::Payme) {
109110
let request = self.request.clone();
@@ -113,10 +114,14 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
113114
&payments::TokenizationAction::TokenizeInConnector,
114115
self,
115116
types::PaymentMethodTokenizationData::try_from(request)?,
117+
should_continue_payment,
116118
)
117119
.await
118120
} else {
119-
Ok(None)
121+
Ok(types::PaymentMethodTokenResult {
122+
payment_method_token_result: Ok(None),
123+
is_payment_method_tokenization_performed: false,
124+
})
120125
}
121126
}
122127

crates/router/src/core/payments/flows/setup_mandate_flow.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,16 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
107107
state: &SessionState,
108108
connector: &api::ConnectorData,
109109
tokenization_action: &payments::TokenizationAction,
110-
) -> RouterResult<Option<String>> {
110+
should_continue_payment: bool,
111+
) -> RouterResult<types::PaymentMethodTokenResult> {
111112
let request = self.request.clone();
112113
tokenization::add_payment_method_token(
113114
state,
114115
connector,
115116
tokenization_action,
116117
self,
117118
types::PaymentMethodTokenizationData::try_from(request)?,
119+
should_continue_payment,
118120
)
119121
.await
120122
}

crates/router/src/core/payments/retry.rs

+1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ where
321321
api::HeaderPayload::default(),
322322
frm_suggestion,
323323
business_profile,
324+
true,
324325
)
325326
.await
326327
}

crates/router/src/core/payments/tokenization.rs

+98-51
Original file line numberDiff line numberDiff line change
@@ -784,62 +784,109 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
784784
tokenization_action: &payments::TokenizationAction,
785785
router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
786786
pm_token_request_data: types::PaymentMethodTokenizationData,
787-
) -> RouterResult<Option<String>> {
788-
match tokenization_action {
789-
payments::TokenizationAction::TokenizeInConnector
790-
| payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => {
791-
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
792-
api::PaymentMethodToken,
793-
types::PaymentMethodTokenizationData,
794-
types::PaymentsResponseData,
795-
> = connector.connector.get_connector_integration();
796-
797-
let pm_token_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
798-
Err(types::ErrorResponse::default());
799-
800-
let pm_token_router_data =
801-
helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
802-
router_data.clone(),
803-
pm_token_request_data,
804-
pm_token_response_data,
787+
should_continue_payment: bool,
788+
) -> RouterResult<types::PaymentMethodTokenResult> {
789+
if should_continue_payment {
790+
match tokenization_action {
791+
payments::TokenizationAction::TokenizeInConnector
792+
| payments::TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt(_) => {
793+
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
794+
api::PaymentMethodToken,
795+
types::PaymentMethodTokenizationData,
796+
types::PaymentsResponseData,
797+
> = connector.connector.get_connector_integration();
798+
799+
let pm_token_response_data: Result<
800+
types::PaymentsResponseData,
801+
types::ErrorResponse,
802+
> = Err(types::ErrorResponse::default());
803+
804+
let pm_token_router_data =
805+
helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
806+
router_data.clone(),
807+
pm_token_request_data,
808+
pm_token_response_data,
809+
);
810+
811+
router_data
812+
.request
813+
.set_session_token(pm_token_router_data.session_token.clone());
814+
815+
let resp = services::execute_connector_processing_step(
816+
state,
817+
connector_integration,
818+
&pm_token_router_data,
819+
payments::CallConnectorAction::Trigger,
820+
None,
821+
)
822+
.await
823+
.to_payment_failed_response()?;
824+
825+
metrics::CONNECTOR_PAYMENT_METHOD_TOKENIZATION.add(
826+
&metrics::CONTEXT,
827+
1,
828+
&add_attributes([
829+
("connector", connector.connector_name.to_string()),
830+
("payment_method", router_data.payment_method.to_string()),
831+
]),
805832
);
806833

807-
router_data
808-
.request
809-
.set_session_token(pm_token_router_data.session_token.clone());
810-
811-
let resp = services::execute_connector_processing_step(
812-
state,
813-
connector_integration,
814-
&pm_token_router_data,
815-
payments::CallConnectorAction::Trigger,
816-
None,
817-
)
818-
.await
819-
.to_payment_failed_response()?;
820-
821-
metrics::CONNECTOR_PAYMENT_METHOD_TOKENIZATION.add(
822-
&metrics::CONTEXT,
823-
1,
824-
&add_attributes([
825-
("connector", connector.connector_name.to_string()),
826-
("payment_method", router_data.payment_method.to_string()),
827-
]),
828-
);
829-
830-
let pm_token = match resp.response {
831-
Ok(response) => match response {
832-
types::PaymentsResponseData::TokenizationResponse { token } => Some(token),
833-
_ => None,
834-
},
835-
Err(err) => {
834+
let payment_token_resp = resp.response.map(|res| {
835+
if let types::PaymentsResponseData::TokenizationResponse { token } = res {
836+
Some(token)
837+
} else {
838+
None
839+
}
840+
});
841+
842+
Ok(types::PaymentMethodTokenResult {
843+
payment_method_token_result: payment_token_resp,
844+
is_payment_method_tokenization_performed: true,
845+
})
846+
}
847+
_ => Ok(types::PaymentMethodTokenResult {
848+
payment_method_token_result: Ok(None),
849+
is_payment_method_tokenization_performed: false,
850+
}),
851+
}
852+
} else {
853+
logger::debug!("Skipping connector tokenization based on should_continue_payment flag");
854+
Ok(types::PaymentMethodTokenResult {
855+
payment_method_token_result: Ok(None),
856+
is_payment_method_tokenization_performed: false,
857+
})
858+
}
859+
}
860+
861+
pub fn update_router_data_with_payment_method_token_result<F: Clone, T>(
862+
payment_method_token_result: types::PaymentMethodTokenResult,
863+
router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
864+
is_retry_payment: bool,
865+
should_continue_further: bool,
866+
) -> bool {
867+
if payment_method_token_result.is_payment_method_tokenization_performed {
868+
match payment_method_token_result.payment_method_token_result {
869+
Ok(pm_token_result) => {
870+
router_data.payment_method_token = pm_token_result.map(|pm_token| {
871+
hyperswitch_domain_models::router_data::PaymentMethodToken::Token(
872+
masking::Secret::new(pm_token),
873+
)
874+
});
875+
876+
true
877+
}
878+
Err(err) => {
879+
if is_retry_payment {
880+
router_data.response = Err(err);
881+
false
882+
} else {
836883
logger::debug!(payment_method_tokenization_error=?err);
837-
None
884+
true
838885
}
839-
};
840-
Ok(pm_token)
886+
}
841887
}
842-
_ => Ok(None),
888+
} else {
889+
should_continue_further
843890
}
844891
}
845892

crates/router/src/types.rs

+5
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,11 @@ pub struct AddAccessTokenResult {
545545
pub connector_supports_access_token: bool,
546546
}
547547

548+
pub struct PaymentMethodTokenResult {
549+
pub payment_method_token_result: Result<Option<String>, ErrorResponse>,
550+
pub is_payment_method_tokenization_performed: bool,
551+
}
552+
548553
#[derive(Debug, Clone, Copy)]
549554
pub enum Redirection {
550555
Redirect,

0 commit comments

Comments
 (0)