mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(payments): add tokenization action handling to payment flow for braintree (#9506)
This commit is contained in:
@ -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")]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user