mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat(core): [Network Tokenization] pre network tokenization (#6873)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -3929,6 +3929,9 @@ impl ProfileCreateBridge for api::ProfileCreate {
|
||||
is_debit_routing_enabled: self.is_debit_routing_enabled.unwrap_or_default(),
|
||||
merchant_business_country: self.merchant_business_country,
|
||||
is_iframe_redirection_enabled: self.is_iframe_redirection_enabled,
|
||||
is_pre_network_tokenization_enabled: self
|
||||
.is_pre_network_tokenization_enabled
|
||||
.unwrap_or_default(),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -4378,6 +4381,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate {
|
||||
is_debit_routing_enabled: self.is_debit_routing_enabled.unwrap_or_default(),
|
||||
merchant_business_country: self.merchant_business_country,
|
||||
is_iframe_redirection_enabled: self.is_iframe_redirection_enabled,
|
||||
is_pre_network_tokenization_enabled: self.is_pre_network_tokenization_enabled,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ pub use hyperswitch_domain_models::{
|
||||
router_request_types::CustomerDetails,
|
||||
};
|
||||
use hyperswitch_domain_models::{
|
||||
payments::{payment_intent::CustomerData, ClickToPayMetaData},
|
||||
payments::{self, payment_intent::CustomerData, ClickToPayMetaData},
|
||||
router_data::AccessToken,
|
||||
};
|
||||
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||
@ -488,6 +488,7 @@ where
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
payment_data = match connector_details {
|
||||
ConnectorCallType::PreDetermined(ref connector) => {
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
@ -508,6 +509,7 @@ where
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (router_data, mca) = call_connector_service(
|
||||
state,
|
||||
req_state.clone(),
|
||||
@ -3097,6 +3099,53 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let customer_acceptance = payment_data
|
||||
.get_payment_attempt()
|
||||
.customer_acceptance
|
||||
.clone();
|
||||
|
||||
if is_pre_network_tokenization_enabled(
|
||||
state,
|
||||
business_profile,
|
||||
customer_acceptance,
|
||||
connector.connector_name,
|
||||
) {
|
||||
let payment_method_data = payment_data.get_payment_method_data();
|
||||
let customer_id = payment_data.get_payment_intent().customer_id.clone();
|
||||
if let (Some(domain::PaymentMethodData::Card(card_data)), Some(customer_id)) =
|
||||
(payment_method_data, customer_id)
|
||||
{
|
||||
let vault_operation =
|
||||
get_vault_operation_for_pre_network_tokenization(state, customer_id, card_data)
|
||||
.await;
|
||||
match vault_operation {
|
||||
payments::VaultOperation::SaveCardAndNetworkTokenData(
|
||||
card_and_network_token_data,
|
||||
) => {
|
||||
payment_data.set_vault_operation(
|
||||
payments::VaultOperation::SaveCardAndNetworkTokenData(Box::new(
|
||||
*card_and_network_token_data.clone(),
|
||||
)),
|
||||
);
|
||||
|
||||
payment_data.set_payment_method_data(Some(
|
||||
domain::PaymentMethodData::NetworkToken(
|
||||
card_and_network_token_data
|
||||
.network_token
|
||||
.network_token_data
|
||||
.clone(),
|
||||
),
|
||||
));
|
||||
}
|
||||
payments::VaultOperation::SaveCardData(card_data_for_vault) => payment_data
|
||||
.set_vault_operation(payments::VaultOperation::SaveCardData(
|
||||
card_data_for_vault.clone(),
|
||||
)),
|
||||
payments::VaultOperation::ExistingVaultData(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
if payment_data
|
||||
.get_payment_attempt()
|
||||
@ -4248,7 +4297,7 @@ pub async fn get_session_token_for_click_to_pay(
|
||||
merchant_id: &id_type::MerchantId,
|
||||
merchant_context: &domain::MerchantContext,
|
||||
authentication_product_ids: common_types::payments::AuthenticationConnectorAccountMap,
|
||||
payment_intent: &hyperswitch_domain_models::payments::PaymentIntent,
|
||||
payment_intent: &payments::PaymentIntent,
|
||||
profile_id: &id_type::ProfileId,
|
||||
) -> RouterResult<api_models::payments::SessionToken> {
|
||||
let click_to_pay_mca_id = authentication_product_ids
|
||||
@ -6169,6 +6218,64 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub fn is_pre_network_tokenization_enabled(
|
||||
state: &SessionState,
|
||||
business_profile: &domain::Profile,
|
||||
customer_acceptance: Option<Secret<serde_json::Value>>,
|
||||
connector_name: enums::Connector,
|
||||
) -> bool {
|
||||
let ntid_supported_connectors = &state
|
||||
.conf
|
||||
.network_transaction_id_supported_connectors
|
||||
.connector_list;
|
||||
|
||||
let is_nt_supported_connector = ntid_supported_connectors.contains(&connector_name);
|
||||
|
||||
business_profile.is_network_tokenization_enabled
|
||||
&& business_profile.is_pre_network_tokenization_enabled
|
||||
&& customer_acceptance.is_some()
|
||||
&& is_nt_supported_connector
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub async fn get_vault_operation_for_pre_network_tokenization(
|
||||
state: &SessionState,
|
||||
customer_id: id_type::CustomerId,
|
||||
card_data: &hyperswitch_domain_models::payment_method_data::Card,
|
||||
) -> payments::VaultOperation {
|
||||
let pre_tokenization_response =
|
||||
tokenization::pre_payment_tokenization(state, customer_id, card_data)
|
||||
.await
|
||||
.ok();
|
||||
match pre_tokenization_response {
|
||||
Some((Some(token_response), Some(token_ref))) => {
|
||||
let token_data = domain::NetworkTokenData::from(token_response);
|
||||
let network_token_data_for_vault = payments::NetworkTokenDataForVault {
|
||||
network_token_data: token_data.clone(),
|
||||
network_token_req_ref_id: token_ref,
|
||||
};
|
||||
|
||||
payments::VaultOperation::SaveCardAndNetworkTokenData(Box::new(
|
||||
payments::CardAndNetworkTokenDataForVault {
|
||||
card_data: card_data.clone(),
|
||||
network_token: network_token_data_for_vault.clone(),
|
||||
},
|
||||
))
|
||||
}
|
||||
Some((None, Some(token_ref))) => {
|
||||
payments::VaultOperation::SaveCardData(payments::CardDataForVault {
|
||||
card_data: card_data.clone(),
|
||||
network_token_req_ref_id: Some(token_ref),
|
||||
})
|
||||
}
|
||||
_ => payments::VaultOperation::SaveCardData(payments::CardDataForVault {
|
||||
card_data: card_data.clone(),
|
||||
network_token_req_ref_id: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn get_connector_choice<F, Req, D>(
|
||||
|
||||
@ -2618,8 +2618,10 @@ pub async fn make_pm_data<'a, F: Clone, R, D>(
|
||||
(_, Some(hyperswitch_token)) => {
|
||||
let existing_vault_data = payment_data.get_vault_operation();
|
||||
|
||||
let vault_data = existing_vault_data.map(|data| match data {
|
||||
domain_payments::VaultOperation::ExistingVaultData(vault_data) => vault_data,
|
||||
let vault_data = existing_vault_data.and_then(|data| match data {
|
||||
domain_payments::VaultOperation::ExistingVaultData(vault_data) => Some(vault_data),
|
||||
domain_payments::VaultOperation::SaveCardData(_)
|
||||
| domain_payments::VaultOperation::SaveCardAndNetworkTokenData(_) => None,
|
||||
});
|
||||
|
||||
let pm_data = Box::pin(payment_methods::retrieve_payment_method_with_token(
|
||||
|
||||
@ -170,6 +170,8 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
|
||||
.and_then(|billing_details| billing_details.address.as_ref())
|
||||
.and_then(|address| address.get_optional_full_name());
|
||||
let mut should_avoid_saving = false;
|
||||
let vault_operation = payment_data.vault_operation.clone();
|
||||
let payment_method_info = payment_data.payment_method_info.clone();
|
||||
|
||||
if let Some(payment_method_info) = &payment_data.payment_method_info {
|
||||
if payment_data.payment_intent.off_session.is_none() && resp.response.is_ok() {
|
||||
@ -207,6 +209,8 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
|
||||
business_profile,
|
||||
connector_mandate_reference_id.clone(),
|
||||
merchant_connector_id.clone(),
|
||||
vault_operation.clone(),
|
||||
payment_method_info.clone(),
|
||||
));
|
||||
|
||||
let is_connector_mandate = resp.request.customer_acceptance.is_some()
|
||||
@ -321,6 +325,8 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
|
||||
&business_profile,
|
||||
connector_mandate_reference_id,
|
||||
merchant_connector_id.clone(),
|
||||
vault_operation.clone(),
|
||||
payment_method_info.clone(),
|
||||
))
|
||||
.await;
|
||||
|
||||
@ -1178,7 +1184,8 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
|
||||
.connector_mandate_detail
|
||||
.as_ref()
|
||||
.map(|detail| ConnectorMandateReferenceId::foreign_from(detail.clone()));
|
||||
|
||||
let vault_operation = payment_data.vault_operation.clone();
|
||||
let payment_method_info = payment_data.payment_method_info.clone();
|
||||
let merchant_connector_id = payment_data.payment_attempt.merchant_connector_id.clone();
|
||||
let tokenization::SavePaymentMethodDataResponse {
|
||||
payment_method_id,
|
||||
@ -1196,6 +1203,8 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
|
||||
business_profile,
|
||||
connector_mandate_reference_id,
|
||||
merchant_connector_id.clone(),
|
||||
vault_operation,
|
||||
payment_method_info,
|
||||
))
|
||||
.await?;
|
||||
|
||||
|
||||
@ -13,12 +13,15 @@ use common_enums::{ConnectorMandateStatus, PaymentMethod};
|
||||
use common_utils::{
|
||||
crypto::Encryptable,
|
||||
ext_traits::{AsyncExt, Encode, ValueExt},
|
||||
id_type, pii,
|
||||
id_type,
|
||||
metrics::utils::record_operation_time,
|
||||
pii,
|
||||
};
|
||||
use error_stack::{report, ResultExt};
|
||||
#[cfg(feature = "v1")]
|
||||
use hyperswitch_domain_models::mandates::{
|
||||
CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord,
|
||||
use hyperswitch_domain_models::{
|
||||
mandates::{CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord},
|
||||
payment_method_data,
|
||||
};
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use router_env::{instrument, tracing};
|
||||
@ -42,7 +45,7 @@ use crate::{
|
||||
types::{
|
||||
self,
|
||||
api::{self, CardDetailFromLocker, CardDetailsPaymentMethod, PaymentMethodCreateExt},
|
||||
domain,
|
||||
domain, payment_methods as pm_types,
|
||||
storage::enums as storage_enums,
|
||||
},
|
||||
utils::{generate_id, OptionExt},
|
||||
@ -92,6 +95,8 @@ pub async fn save_payment_method<FData>(
|
||||
business_profile: &domain::Profile,
|
||||
mut original_connector_mandate_reference_id: Option<ConnectorMandateReferenceId>,
|
||||
merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
|
||||
vault_operation: Option<hyperswitch_domain_models::payments::VaultOperation>,
|
||||
payment_method_info: Option<domain::PaymentMethod>,
|
||||
) -> RouterResult<SavePaymentMethodDataResponse>
|
||||
where
|
||||
FData: mandate::MandateBehaviour + Clone,
|
||||
@ -194,9 +199,11 @@ where
|
||||
};
|
||||
|
||||
let pm_id = if customer_acceptance.is_some() {
|
||||
let payment_method_data =
|
||||
save_payment_method_data.request.get_payment_method_data();
|
||||
let payment_method_create_request =
|
||||
payment_methods::get_payment_method_create_request(
|
||||
Some(&save_payment_method_data.request.get_payment_method_data()),
|
||||
Some(&payment_method_data),
|
||||
Some(save_payment_method_data.payment_method),
|
||||
payment_method_type,
|
||||
&customer_id.clone(),
|
||||
@ -224,42 +231,22 @@ where
|
||||
.await?;
|
||||
((res, dc, None), None)
|
||||
} else {
|
||||
pm_status = Some(common_enums::PaymentMethodStatus::from(
|
||||
let payment_method_status = common_enums::PaymentMethodStatus::from(
|
||||
save_payment_method_data.attempt_status,
|
||||
));
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
);
|
||||
pm_status = Some(payment_method_status);
|
||||
save_card_and_network_token_in_locker(
|
||||
state,
|
||||
customer_id.clone(),
|
||||
payment_method_status,
|
||||
payment_method_data.clone(),
|
||||
vault_operation,
|
||||
payment_method_info,
|
||||
merchant_context,
|
||||
payment_method_create_request.to_owned(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
if is_network_tokenization_enabled {
|
||||
let pm_data = &save_payment_method_data.request.get_payment_method_data();
|
||||
match pm_data {
|
||||
domain::PaymentMethodData::Card(card) => {
|
||||
let (
|
||||
network_token_resp,
|
||||
_network_token_duplication_check, //the duplication check is discarded, since each card has only one token, handling card duplication check will be suffice
|
||||
network_token_requestor_ref_id,
|
||||
) = Box::pin(save_network_token_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
card,
|
||||
payment_method_create_request.clone(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
(
|
||||
(res, dc, network_token_requestor_ref_id),
|
||||
network_token_resp,
|
||||
)
|
||||
}
|
||||
_ => ((res, dc, None), None), //network_token_resp is None in case of other payment methods
|
||||
}
|
||||
} else {
|
||||
((res, dc, None), None)
|
||||
}
|
||||
payment_method_create_request.clone(),
|
||||
is_network_tokenization_enabled,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
let network_token_locker_id = match network_token_resp {
|
||||
Some(ref token_resp) => {
|
||||
@ -272,10 +259,7 @@ where
|
||||
None => None,
|
||||
};
|
||||
|
||||
let optional_pm_details = match (
|
||||
resp.card.as_ref(),
|
||||
save_payment_method_data.request.get_payment_method_data(),
|
||||
) {
|
||||
let optional_pm_details = match (resp.card.as_ref(), payment_method_data) {
|
||||
(Some(card), _) => Some(PaymentMethodsData::Card(
|
||||
CardDetailsPaymentMethod::from((card.clone(), co_badged_card_data)),
|
||||
)),
|
||||
@ -849,6 +833,80 @@ where
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub async fn pre_payment_tokenization(
|
||||
state: &SessionState,
|
||||
customer_id: id_type::CustomerId,
|
||||
card: &payment_method_data::Card,
|
||||
) -> RouterResult<(Option<pm_types::TokenResponse>, Option<String>)> {
|
||||
let network_tokenization_supported_card_networks = &state
|
||||
.conf
|
||||
.network_tokenization_supported_card_networks
|
||||
.card_networks;
|
||||
|
||||
if card
|
||||
.card_network
|
||||
.as_ref()
|
||||
.filter(|cn| network_tokenization_supported_card_networks.contains(cn))
|
||||
.is_some()
|
||||
{
|
||||
let optional_card_cvc = Some(card.card_cvc.clone());
|
||||
let card_detail = payment_method_data::CardDetail::from(card);
|
||||
match network_tokenization::make_card_network_tokenization_request(
|
||||
state,
|
||||
&card_detail,
|
||||
optional_card_cvc,
|
||||
&customer_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((_token_response, network_token_requestor_ref_id)) => {
|
||||
let network_tokenization_service = &state.conf.network_tokenization_service;
|
||||
match (
|
||||
network_token_requestor_ref_id.clone(),
|
||||
network_tokenization_service,
|
||||
) {
|
||||
(Some(token_ref), Some(network_tokenization_service)) => {
|
||||
let network_token = record_operation_time(
|
||||
async {
|
||||
network_tokenization::get_network_token(
|
||||
state,
|
||||
customer_id,
|
||||
token_ref,
|
||||
network_tokenization_service.get_inner(),
|
||||
)
|
||||
.await
|
||||
},
|
||||
&metrics::FETCH_NETWORK_TOKEN_TIME,
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
match network_token {
|
||||
Ok(token_response) => {
|
||||
Ok((Some(token_response), network_token_requestor_ref_id.clone()))
|
||||
}
|
||||
_ => {
|
||||
logger::error!(
|
||||
"Error while fetching token from tokenization service"
|
||||
);
|
||||
Ok((None, network_token_requestor_ref_id.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(token_ref), _) => Ok((None, Some(token_ref))),
|
||||
_ => Ok((None, None)),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
logger::error!("Failed to tokenize card: {:?}", err);
|
||||
Ok((None, None)) //None will be returned in case of error when calling network tokenization service
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok((None, None)) //None will be returned in case of unsupported card network.
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
@ -960,6 +1018,7 @@ pub async fn save_in_locker(
|
||||
state: &SessionState,
|
||||
merchant_context: &domain::MerchantContext,
|
||||
payment_method_request: api::PaymentMethodCreate,
|
||||
card_detail: Option<api::CardDetail>,
|
||||
) -> RouterResult<(
|
||||
api_models::payment_methods::PaymentMethodResponse,
|
||||
Option<payment_methods::transformers::DataDuplicationCheck>,
|
||||
@ -970,8 +1029,8 @@ pub async fn save_in_locker(
|
||||
.customer_id
|
||||
.clone()
|
||||
.get_required_value("customer_id")?;
|
||||
match payment_method_request.card.clone() {
|
||||
Some(card) => Box::pin(
|
||||
match (payment_method_request.card.clone(), card_detail) {
|
||||
(_, Some(card)) | (Some(card), _) => Box::pin(
|
||||
PmCards {
|
||||
state,
|
||||
merchant_context,
|
||||
@ -981,7 +1040,7 @@ pub async fn save_in_locker(
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card Failed"),
|
||||
None => {
|
||||
_ => {
|
||||
let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm");
|
||||
let payment_method_response = api::PaymentMethodResponse {
|
||||
merchant_id: merchant_id.clone(),
|
||||
@ -1038,7 +1097,8 @@ pub async fn save_network_token_in_locker(
|
||||
pub async fn save_network_token_in_locker(
|
||||
state: &SessionState,
|
||||
merchant_context: &domain::MerchantContext,
|
||||
card_data: &domain::Card,
|
||||
card_data: &payment_method_data::Card,
|
||||
network_token_data: Option<api::CardDetail>,
|
||||
payment_method_request: api::PaymentMethodCreate,
|
||||
) -> RouterResult<(
|
||||
Option<api_models::payment_methods::PaymentMethodResponse>,
|
||||
@ -1054,60 +1114,83 @@ pub async fn save_network_token_in_locker(
|
||||
.network_tokenization_supported_card_networks
|
||||
.card_networks;
|
||||
|
||||
if card_data
|
||||
.card_network
|
||||
.as_ref()
|
||||
.filter(|cn| network_tokenization_supported_card_networks.contains(cn))
|
||||
.is_some()
|
||||
{
|
||||
let optional_card_cvc = Some(card_data.card_cvc.clone());
|
||||
match network_tokenization::make_card_network_tokenization_request(
|
||||
state,
|
||||
&domain::CardDetail::from(card_data),
|
||||
optional_card_cvc,
|
||||
&customer_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((token_response, network_token_requestor_ref_id)) => {
|
||||
// Only proceed if the tokenization was successful
|
||||
let network_token_data = api::CardDetail {
|
||||
card_number: token_response.token.clone(),
|
||||
card_exp_month: token_response.token_expiry_month.clone(),
|
||||
card_exp_year: token_response.token_expiry_year.clone(),
|
||||
card_holder_name: None,
|
||||
nick_name: None,
|
||||
card_issuing_country: None,
|
||||
card_network: Some(token_response.card_brand.clone()),
|
||||
card_issuer: None,
|
||||
card_type: None,
|
||||
};
|
||||
match network_token_data {
|
||||
Some(nt_data) => {
|
||||
let (res, dc) = Box::pin(
|
||||
PmCards {
|
||||
state,
|
||||
merchant_context,
|
||||
}
|
||||
.add_card_to_locker(
|
||||
payment_method_request,
|
||||
&nt_data,
|
||||
&customer_id,
|
||||
None,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Network Token Failed")?;
|
||||
|
||||
let (res, dc) = Box::pin(
|
||||
PmCards {
|
||||
state,
|
||||
merchant_context,
|
||||
}
|
||||
.add_card_to_locker(
|
||||
payment_method_request,
|
||||
&network_token_data,
|
||||
&customer_id,
|
||||
None,
|
||||
),
|
||||
Ok((Some(res), dc, None))
|
||||
}
|
||||
None => {
|
||||
if card_data
|
||||
.card_network
|
||||
.as_ref()
|
||||
.filter(|cn| network_tokenization_supported_card_networks.contains(cn))
|
||||
.is_some()
|
||||
{
|
||||
let optional_card_cvc = Some(card_data.card_cvc.clone());
|
||||
match network_tokenization::make_card_network_tokenization_request(
|
||||
state,
|
||||
&domain::CardDetail::from(card_data),
|
||||
optional_card_cvc,
|
||||
&customer_id,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Network Token Failed")?;
|
||||
{
|
||||
Ok((token_response, network_token_requestor_ref_id)) => {
|
||||
// Only proceed if the tokenization was successful
|
||||
let network_token_data = api::CardDetail {
|
||||
card_number: token_response.token.clone(),
|
||||
card_exp_month: token_response.token_expiry_month.clone(),
|
||||
card_exp_year: token_response.token_expiry_year.clone(),
|
||||
card_holder_name: None,
|
||||
nick_name: None,
|
||||
card_issuing_country: None,
|
||||
card_network: Some(token_response.card_brand.clone()),
|
||||
card_issuer: None,
|
||||
card_type: None,
|
||||
};
|
||||
|
||||
Ok((Some(res), dc, network_token_requestor_ref_id))
|
||||
}
|
||||
Err(err) => {
|
||||
logger::error!("Failed to tokenize card: {:?}", err);
|
||||
Ok((None, None, None)) //None will be returned in case of error when calling network tokenization service
|
||||
let (res, dc) = Box::pin(
|
||||
PmCards {
|
||||
state,
|
||||
merchant_context,
|
||||
}
|
||||
.add_card_to_locker(
|
||||
payment_method_request,
|
||||
&network_token_data,
|
||||
&customer_id,
|
||||
None,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Network Token Failed")?;
|
||||
|
||||
Ok((Some(res), dc, network_token_requestor_ref_id))
|
||||
}
|
||||
Err(err) => {
|
||||
logger::error!("Failed to tokenize card: {:?}", err);
|
||||
Ok((None, None, None)) //None will be returned in case of error when calling network tokenization service
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok((None, None, None)) //None will be returned in case of unsupported card network.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok((None, None, None)) //None will be returned in case of unsupported card network.
|
||||
}
|
||||
}
|
||||
|
||||
@ -1450,3 +1533,171 @@ pub async fn add_token_for_payment_method(
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn save_card_and_network_token_in_locker(
|
||||
state: &SessionState,
|
||||
customer_id: id_type::CustomerId,
|
||||
payment_method_status: common_enums::PaymentMethodStatus,
|
||||
payment_method_data: domain::PaymentMethodData,
|
||||
vault_operation: Option<hyperswitch_domain_models::payments::VaultOperation>,
|
||||
payment_method_info: Option<domain::PaymentMethod>,
|
||||
merchant_context: &domain::MerchantContext,
|
||||
payment_method_create_request: api::PaymentMethodCreate,
|
||||
is_network_tokenization_enabled: bool,
|
||||
) -> RouterResult<(
|
||||
(
|
||||
api_models::payment_methods::PaymentMethodResponse,
|
||||
Option<payment_methods::transformers::DataDuplicationCheck>,
|
||||
Option<String>,
|
||||
),
|
||||
Option<api_models::payment_methods::PaymentMethodResponse>,
|
||||
)> {
|
||||
let network_token_requestor_reference_id = payment_method_info
|
||||
.and_then(|pm_info| pm_info.network_token_requestor_reference_id.clone());
|
||||
|
||||
match vault_operation {
|
||||
Some(hyperswitch_domain_models::payments::VaultOperation::SaveCardData(card)) => {
|
||||
let card_data = api::CardDetail::from(card.card_data.clone());
|
||||
if let (Some(nt_ref_id), Some(tokenization_service)) = (
|
||||
card.network_token_req_ref_id.clone(),
|
||||
&state.conf.network_tokenization_service,
|
||||
) {
|
||||
let _ = record_operation_time(
|
||||
async {
|
||||
network_tokenization::delete_network_token_from_tokenization_service(
|
||||
state,
|
||||
nt_ref_id.clone(),
|
||||
&customer_id,
|
||||
tokenization_service.get_inner(),
|
||||
)
|
||||
.await
|
||||
},
|
||||
&metrics::DELETE_NETWORK_TOKEN_TIME,
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
payment_method_create_request.to_owned(),
|
||||
Some(card_data),
|
||||
))
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card In Locker Failed")?;
|
||||
|
||||
Ok(((res, dc, None), None))
|
||||
}
|
||||
Some(hyperswitch_domain_models::payments::VaultOperation::SaveCardAndNetworkTokenData(
|
||||
save_card_and_network_token_data,
|
||||
)) => {
|
||||
let card_data =
|
||||
api::CardDetail::from(save_card_and_network_token_data.card_data.clone());
|
||||
|
||||
let network_token_data = api::CardDetail::from(
|
||||
save_card_and_network_token_data
|
||||
.network_token
|
||||
.network_token_data
|
||||
.clone(),
|
||||
);
|
||||
|
||||
if payment_method_status == common_enums::PaymentMethodStatus::Active {
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
payment_method_create_request.to_owned(),
|
||||
Some(card_data),
|
||||
))
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card In Locker Failed")?;
|
||||
|
||||
let (network_token_resp, _dc, _) = Box::pin(save_network_token_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
&save_card_and_network_token_data.card_data,
|
||||
Some(network_token_data),
|
||||
payment_method_create_request.clone(),
|
||||
))
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Network Token In Locker Failed")?;
|
||||
|
||||
Ok((
|
||||
(res, dc, network_token_requestor_reference_id),
|
||||
network_token_resp,
|
||||
))
|
||||
} else {
|
||||
if let (Some(nt_ref_id), Some(tokenization_service)) = (
|
||||
network_token_requestor_reference_id.clone(),
|
||||
&state.conf.network_tokenization_service,
|
||||
) {
|
||||
let _ = record_operation_time(
|
||||
async {
|
||||
network_tokenization::delete_network_token_from_tokenization_service(
|
||||
state,
|
||||
nt_ref_id.clone(),
|
||||
&customer_id,
|
||||
tokenization_service.get_inner(),
|
||||
)
|
||||
.await
|
||||
},
|
||||
&metrics::DELETE_NETWORK_TOKEN_TIME,
|
||||
&[],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
payment_method_create_request.to_owned(),
|
||||
Some(card_data),
|
||||
))
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card In Locker Failed")?;
|
||||
|
||||
Ok(((res, dc, None), None))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
payment_method_create_request.to_owned(),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
|
||||
if is_network_tokenization_enabled {
|
||||
match &payment_method_data {
|
||||
domain::PaymentMethodData::Card(card) => {
|
||||
let (
|
||||
network_token_resp,
|
||||
_network_token_duplication_check, //the duplication check is discarded, since each card has only one token, handling card duplication check will be suffice
|
||||
network_token_requestor_ref_id,
|
||||
) = Box::pin(save_network_token_in_locker(
|
||||
state,
|
||||
merchant_context,
|
||||
card,
|
||||
None,
|
||||
payment_method_create_request.clone(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
(res, dc, network_token_requestor_ref_id),
|
||||
network_token_resp,
|
||||
))
|
||||
}
|
||||
_ => Ok(((res, dc, None), None)), //network_token_resp is None in case of other payment methods
|
||||
}
|
||||
} else {
|
||||
Ok(((res, dc, None), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ use crate::{
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorTransactionId},
|
||||
domain,
|
||||
domain, payment_methods as pm_types,
|
||||
storage::{self, enums},
|
||||
transformers::{ForeignFrom, ForeignInto, ForeignTryFrom},
|
||||
MultipleCaptureRequestData,
|
||||
@ -5159,3 +5159,22 @@ impl ForeignFrom<(Self, Option<&api_models::payments::AdditionalPaymentData>)>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
impl From<pm_types::TokenResponse> for domain::NetworkTokenData {
|
||||
fn from(token_response: pm_types::TokenResponse) -> Self {
|
||||
Self {
|
||||
token_number: token_response.authentication_details.token,
|
||||
token_exp_month: token_response.token_details.exp_month,
|
||||
token_exp_year: token_response.token_details.exp_year,
|
||||
token_cryptogram: Some(token_response.authentication_details.cryptogram),
|
||||
card_issuer: None,
|
||||
card_network: Some(token_response.network),
|
||||
card_type: None,
|
||||
card_issuing_country: None,
|
||||
bank_code: None,
|
||||
nick_name: None,
|
||||
eci: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +195,7 @@ impl ForeignTryFrom<domain::Profile> for ProfileResponse {
|
||||
force_3ds_challenge: item.force_3ds_challenge,
|
||||
is_debit_routing_enabled: Some(item.is_debit_routing_enabled),
|
||||
merchant_business_country: item.merchant_business_country,
|
||||
is_pre_network_tokenization_enabled: item.is_pre_network_tokenization_enabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -443,5 +444,8 @@ pub async fn create_profile_from_merchant_account(
|
||||
is_debit_routing_enabled: request.is_debit_routing_enabled.unwrap_or_default(),
|
||||
merchant_business_country: request.merchant_business_country,
|
||||
is_iframe_redirection_enabled: request.is_iframe_redirection_enabled,
|
||||
is_pre_network_tokenization_enabled: request
|
||||
.is_pre_network_tokenization_enabled
|
||||
.unwrap_or_default(),
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user