feat(payments): add tokenization action handling to payment flow for braintree (#9506)

This commit is contained in:
Ayush Anand
2025-09-30 12:56:58 +05:30
committed by GitHub
parent 5427b07afb
commit efab34f0ef
3 changed files with 166 additions and 41 deletions

View File

@ -224,7 +224,7 @@ where
let mut connector_http_status_code = None; let mut connector_http_status_code = None;
let (payment_data, connector_response_data) = match connector { let (payment_data, connector_response_data) = match connector {
ConnectorCallType::PreDetermined(connector_data) => { ConnectorCallType::PreDetermined(connector_data) => {
let (mca_type_details, updated_customer, router_data) = let (mca_type_details, updated_customer, router_data, tokenization_action) =
call_connector_service_prerequisites( call_connector_service_prerequisites(
state, state,
req_state.clone(), req_state.clone(),
@ -264,6 +264,7 @@ where
mca_type_details, mca_type_details,
router_data, router_data,
updated_customer, updated_customer,
tokenization_action,
) )
.await?; .await?;
@ -304,7 +305,7 @@ where
let mut connectors = connectors.clone().into_iter(); let mut connectors = connectors.clone().into_iter();
let connector_data = get_connector_data(&mut connectors)?; let connector_data = get_connector_data(&mut connectors)?;
let (mca_type_details, updated_customer, router_data) = let (mca_type_details, updated_customer, router_data, tokenization_action) =
call_connector_service_prerequisites( call_connector_service_prerequisites(
state, state,
req_state.clone(), req_state.clone(),
@ -344,6 +345,7 @@ where
mca_type_details, mca_type_details,
router_data, router_data,
updated_customer, updated_customer,
tokenization_action,
) )
.await?; .await?;
@ -4397,6 +4399,7 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest, D>(
merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails,
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>, mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
updated_customer: Option<storage::CustomerUpdate>, updated_customer: Option<storage::CustomerUpdate>,
tokenization_action: TokenizationAction,
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>> ) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
where where
F: Send + Clone + Sync, F: Send + Clone + Sync,
@ -4426,7 +4429,20 @@ where
&mut router_data, &mut router_data,
&call_connector_action, &call_connector_action,
); );
let payment_method_token_response = router_data
.add_payment_method_token(
state,
&connector,
&tokenization_action,
should_continue_further,
)
.await?;
let 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,
);
let should_continue = match router_data let should_continue = match router_data
.create_order_at_connector(state, &connector, should_continue_further) .create_order_at_connector(state, &connector, should_continue_further)
.await? .await?
@ -4527,6 +4543,7 @@ pub async fn call_connector_service_prerequisites<F, RouterDReq, ApiRequest, D>(
domain::MerchantConnectorAccountTypeDetails, domain::MerchantConnectorAccountTypeDetails,
Option<storage::CustomerUpdate>, Option<storage::CustomerUpdate>,
RouterData<F, RouterDReq, router_types::PaymentsResponseData>, RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
TokenizationAction,
)> )>
where where
F: Send + Clone + Sync, F: Send + Clone + Sync,
@ -4582,10 +4599,16 @@ where
) )
.await?; .await?;
let tokenization_action = operation
.to_domain()?
.get_connector_tokenization_action(state, payment_data)
.await?;
Ok(( Ok((
merchant_connector_account_type_details, merchant_connector_account_type_details,
updated_customer, updated_customer,
router_data, router_data,
tokenization_action,
)) ))
} }
@ -4650,25 +4673,29 @@ where
.await?, .await?,
)); ));
let (merchant_connector_account_type_details, updated_customer, router_data) = let (
call_connector_service_prerequisites( merchant_connector_account_type_details,
state, updated_customer,
req_state, router_data,
merchant_context, _tokenization_action,
connector, ) = call_connector_service_prerequisites(
operation, state,
payment_data, req_state,
customer, merchant_context,
call_connector_action, connector,
schedule_time, operation,
header_payload, payment_data,
frm_suggestion, customer,
business_profile, call_connector_action,
is_retry_payment, schedule_time,
should_retry_with_pan, header_payload,
all_keys_required, frm_suggestion,
) business_profile,
.await?; is_retry_payment,
should_retry_with_pan,
all_keys_required,
)
.await?;
Ok(( Ok((
merchant_connector_account_type_details, merchant_connector_account_type_details,
external_vault_merchant_connector_account_type_details, external_vault_merchant_connector_account_type_details,
@ -4897,6 +4924,7 @@ pub async fn decide_unified_connector_service_call<F, RouterDReq, ApiRequest, D>
merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails,
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>, mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
updated_customer: Option<storage::CustomerUpdate>, updated_customer: Option<storage::CustomerUpdate>,
tokenization_action: TokenizationAction,
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>> ) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
where where
F: Send + Clone + Sync, F: Send + Clone + Sync,
@ -4993,6 +5021,7 @@ where
merchant_connector_account_type_details, merchant_connector_account_type_details,
router_data, router_data,
updated_customer, updated_customer,
tokenization_action,
) )
.await .await
} }
@ -6938,6 +6967,48 @@ where
Ok(merchant_connector_account) Ok(merchant_connector_account)
} }
#[cfg(feature = "v2")]
fn is_payment_method_tokenization_enabled_for_connector(
state: &SessionState,
connector_name: &str,
payment_method: storage::enums::PaymentMethod,
payment_method_type: Option<storage::enums::PaymentMethodType>,
mandate_flow_enabled: storage_enums::FutureUsage,
) -> RouterResult<bool> {
let connector_tokenization_filter = state.conf.tokenization.0.get(connector_name);
Ok(connector_tokenization_filter
.map(|connector_filter| {
connector_filter
.payment_method
.clone()
.contains(&payment_method)
&& is_payment_method_type_allowed_for_connector(
payment_method_type,
connector_filter.payment_method_type.clone(),
)
&& is_payment_flow_allowed_for_connector(
mandate_flow_enabled,
connector_filter.flow.clone(),
)
})
.unwrap_or(false))
}
// Determines connector tokenization eligibility: if no flow restriction, allow for one-off/CIT with raw cards; if flow = “mandates”, only allow MIT off-session with stored tokens.
#[cfg(feature = "v2")]
fn is_payment_flow_allowed_for_connector(
mandate_flow_enabled: storage_enums::FutureUsage,
payment_flow: Option<PaymentFlow>,
) -> bool {
if payment_flow.is_none() {
true
} else {
matches!(payment_flow, Some(PaymentFlow::Mandates))
&& matches!(mandate_flow_enabled, storage_enums::FutureUsage::OffSession)
}
}
#[cfg(feature = "v1")]
fn is_payment_method_tokenization_enabled_for_connector( fn is_payment_method_tokenization_enabled_for_connector(
state: &SessionState, state: &SessionState,
connector_name: &str, connector_name: &str,
@ -6975,7 +7046,7 @@ fn is_payment_method_tokenization_enabled_for_connector(
}) })
.unwrap_or(false)) .unwrap_or(false))
} }
#[cfg(feature = "v1")]
fn is_payment_flow_allowed_for_connector( fn is_payment_flow_allowed_for_connector(
mandate_flow_enabled: Option<storage_enums::FutureUsage>, mandate_flow_enabled: Option<storage_enums::FutureUsage>,
payment_flow: Option<PaymentFlow>, payment_flow: Option<PaymentFlow>,
@ -7198,6 +7269,7 @@ fn is_payment_method_type_allowed_for_connector(
} }
} }
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn decide_payment_method_tokenize_action( async fn decide_payment_method_tokenize_action(
state: &SessionState, state: &SessionState,
@ -7267,7 +7339,7 @@ pub struct GooglePayPaymentProcessingDetails {
pub google_pay_root_signing_keys: Secret<String>, pub google_pay_root_signing_keys: Secret<String>,
pub google_pay_recipient_id: Secret<String>, pub google_pay_recipient_id: Secret<String>,
} }
#[cfg(feature = "v1")]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TokenizationAction { pub enum TokenizationAction {
TokenizeInRouter, TokenizeInRouter,
@ -7278,23 +7350,10 @@ pub enum TokenizationAction {
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)] #[derive(Clone, Debug)]
pub async fn get_connector_tokenization_action_when_confirm_true<F, Req, D>( pub enum TokenizationAction {
_state: &SessionState, TokenizeInConnector,
_operation: &BoxedOperation<'_, F, Req, D>, SkipConnectorTokenization,
payment_data: &mut D,
_validate_result: &operations::ValidateResult,
_merchant_key_store: &domain::MerchantKeyStore,
_customer: &Option<domain::Customer>,
_business_profile: &domain::Profile,
) -> RouterResult<(D, TokenizationAction)>
where
F: Send + Clone,
D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
{
// TODO: Implement this function
let payment_data = payment_data.to_owned();
Ok((payment_data, TokenizationAction::SkipConnectorTokenization))
} }
#[cfg(feature = "v1")] #[cfg(feature = "v1")]

View File

@ -94,6 +94,8 @@ pub use self::{
payment_session_intent::PaymentSessionIntent, payment_session_intent::PaymentSessionIntent,
}; };
use super::{helpers, CustomerDetails, OperationSessionGetters, OperationSessionSetters}; use super::{helpers, CustomerDetails, OperationSessionGetters, OperationSessionSetters};
#[cfg(feature = "v2")]
use crate::core::payments;
use crate::{ use crate::{
core::errors::{self, CustomResult, RouterResult}, core::errors::{self, CustomResult, RouterResult},
routes::{app::ReqState, SessionState}, routes::{app::ReqState, SessionState},
@ -426,6 +428,16 @@ pub trait Domain<F: Clone, R, D>: Send + Sync {
Ok(()) Ok(())
} }
/// Get connector tokenization action
#[cfg(feature = "v2")]
async fn get_connector_tokenization_action<'a>(
&'a self,
_state: &SessionState,
_payment_data: &D,
) -> RouterResult<(payments::TokenizationAction)> {
Ok(payments::TokenizationAction::SkipConnectorTokenization)
}
// #[cfg(feature = "v2")] // #[cfg(feature = "v2")]
// async fn call_connector<'a, RouterDataReq>( // async fn call_connector<'a, RouterDataReq>(
// &'a self, // &'a self,

View File

@ -538,6 +538,60 @@ impl<F: Clone + Send + Sync> Domain<F, PaymentsConfirmIntentRequest, PaymentConf
.set_connector_in_payment_attempt(Some(connector_data.connector_name.to_string())); .set_connector_in_payment_attempt(Some(connector_data.connector_name.to_string()));
Ok(connector_data) Ok(connector_data)
} }
async fn get_connector_tokenization_action<'a>(
&'a self,
state: &SessionState,
payment_data: &PaymentConfirmData<F>,
) -> RouterResult<payments::TokenizationAction> {
let connector = payment_data.payment_attempt.connector.to_owned();
let is_connector_mandate_flow = payment_data
.mandate_data
.as_ref()
.and_then(|mandate_details| mandate_details.mandate_reference_id.as_ref())
.map(|mandate_reference| match mandate_reference {
api_models::payments::MandateReferenceId::ConnectorMandateId(_) => true,
api_models::payments::MandateReferenceId::NetworkMandateId(_)
| api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_) => false,
})
.unwrap_or(false);
let tokenization_action = match connector {
Some(_) if is_connector_mandate_flow => {
payments::TokenizationAction::SkipConnectorTokenization
}
Some(connector) => {
let payment_method = payment_data
.payment_attempt
.get_payment_method()
.ok_or_else(|| errors::ApiErrorResponse::InternalServerError)
.attach_printable("Payment method not found")?;
let payment_method_type: Option<common_enums::PaymentMethodType> =
payment_data.payment_attempt.get_payment_method_type();
let mandate_flow_enabled = payment_data.payment_intent.setup_future_usage;
let is_connector_tokenization_enabled =
payments::is_payment_method_tokenization_enabled_for_connector(
state,
&connector,
payment_method,
payment_method_type,
mandate_flow_enabled,
)?;
if is_connector_tokenization_enabled {
payments::TokenizationAction::TokenizeInConnector
} else {
payments::TokenizationAction::SkipConnectorTokenization
}
}
None => payments::TokenizationAction::SkipConnectorTokenization,
};
Ok(tokenization_action)
}
} }
#[async_trait] #[async_trait]