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 (payment_data, connector_response_data) = match connector {
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(
state,
req_state.clone(),
@ -264,6 +264,7 @@ where
mca_type_details,
router_data,
updated_customer,
tokenization_action,
)
.await?;
@ -304,7 +305,7 @@ where
let mut connectors = connectors.clone().into_iter();
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(
state,
req_state.clone(),
@ -344,6 +345,7 @@ where
mca_type_details,
router_data,
updated_customer,
tokenization_action,
)
.await?;
@ -4397,6 +4399,7 @@ pub async fn call_connector_service<F, RouterDReq, ApiRequest, D>(
merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails,
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
updated_customer: Option<storage::CustomerUpdate>,
tokenization_action: TokenizationAction,
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
where
F: Send + Clone + Sync,
@ -4426,7 +4429,20 @@ where
&mut router_data,
&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
.create_order_at_connector(state, &connector, should_continue_further)
.await?
@ -4527,6 +4543,7 @@ pub async fn call_connector_service_prerequisites<F, RouterDReq, ApiRequest, D>(
domain::MerchantConnectorAccountTypeDetails,
Option<storage::CustomerUpdate>,
RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
TokenizationAction,
)>
where
F: Send + Clone + Sync,
@ -4582,10 +4599,16 @@ where
)
.await?;
let tokenization_action = operation
.to_domain()?
.get_connector_tokenization_action(state, payment_data)
.await?;
Ok((
merchant_connector_account_type_details,
updated_customer,
router_data,
tokenization_action,
))
}
@ -4650,25 +4673,29 @@ where
.await?,
));
let (merchant_connector_account_type_details, updated_customer, router_data) =
call_connector_service_prerequisites(
state,
req_state,
merchant_context,
connector,
operation,
payment_data,
customer,
call_connector_action,
schedule_time,
header_payload,
frm_suggestion,
business_profile,
is_retry_payment,
should_retry_with_pan,
all_keys_required,
)
.await?;
let (
merchant_connector_account_type_details,
updated_customer,
router_data,
_tokenization_action,
) = call_connector_service_prerequisites(
state,
req_state,
merchant_context,
connector,
operation,
payment_data,
customer,
call_connector_action,
schedule_time,
header_payload,
frm_suggestion,
business_profile,
is_retry_payment,
should_retry_with_pan,
all_keys_required,
)
.await?;
Ok((
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,
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
updated_customer: Option<storage::CustomerUpdate>,
tokenization_action: TokenizationAction,
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
where
F: Send + Clone + Sync,
@ -4993,6 +5021,7 @@ where
merchant_connector_account_type_details,
router_data,
updated_customer,
tokenization_action,
)
.await
}
@ -6938,6 +6967,48 @@ where
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(
state: &SessionState,
connector_name: &str,
@ -6975,7 +7046,7 @@ fn is_payment_method_tokenization_enabled_for_connector(
})
.unwrap_or(false))
}
#[cfg(feature = "v1")]
fn is_payment_flow_allowed_for_connector(
mandate_flow_enabled: Option<storage_enums::FutureUsage>,
payment_flow: Option<PaymentFlow>,
@ -7198,6 +7269,7 @@ fn is_payment_method_type_allowed_for_connector(
}
}
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
async fn decide_payment_method_tokenize_action(
state: &SessionState,
@ -7267,7 +7339,7 @@ pub struct GooglePayPaymentProcessingDetails {
pub google_pay_root_signing_keys: Secret<String>,
pub google_pay_recipient_id: Secret<String>,
}
#[cfg(feature = "v1")]
#[derive(Clone, Debug)]
pub enum TokenizationAction {
TokenizeInRouter,
@ -7278,23 +7350,10 @@ pub enum TokenizationAction {
}
#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn get_connector_tokenization_action_when_confirm_true<F, Req, D>(
_state: &SessionState,
_operation: &BoxedOperation<'_, F, Req, D>,
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))
#[derive(Clone, Debug)]
pub enum TokenizationAction {
TokenizeInConnector,
SkipConnectorTokenization,
}
#[cfg(feature = "v1")]

View File

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