mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 21:07:58 +08:00
feat(core): Add support for card network tokenization (#5599)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -21,7 +21,7 @@ use crate::{
|
||||
core::{
|
||||
errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt},
|
||||
mandate,
|
||||
payment_methods::{self, cards::create_encrypted_data},
|
||||
payment_methods::{self, cards::create_encrypted_data, network_tokenization},
|
||||
payments,
|
||||
},
|
||||
logger,
|
||||
@ -199,22 +199,65 @@ where
|
||||
.await?;
|
||||
let customer_id = customer_id.to_owned().get_required_value("customer_id")?;
|
||||
let merchant_id = merchant_account.get_id();
|
||||
let (mut resp, duplication_check) = if !state.conf.locker.locker_enabled {
|
||||
skip_saving_card_in_locker(
|
||||
let is_network_tokenization_enabled =
|
||||
business_profile.is_network_tokenization_enabled;
|
||||
let (
|
||||
(mut resp, duplication_check, network_token_requestor_ref_id),
|
||||
network_token_resp,
|
||||
) = if !state.conf.locker.locker_enabled {
|
||||
let (res, dc) = skip_saving_card_in_locker(
|
||||
merchant_account,
|
||||
payment_method_create_request.to_owned(),
|
||||
)
|
||||
.await?
|
||||
.await?;
|
||||
((res, dc, None), None)
|
||||
} else {
|
||||
pm_status = Some(common_enums::PaymentMethodStatus::from(
|
||||
save_payment_method_data.attempt_status,
|
||||
));
|
||||
Box::pin(save_in_locker(
|
||||
let (res, dc) = Box::pin(save_in_locker(
|
||||
state,
|
||||
merchant_account,
|
||||
payment_method_create_request.to_owned(),
|
||||
))
|
||||
.await?
|
||||
.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_account,
|
||||
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)
|
||||
}
|
||||
};
|
||||
let network_token_locker_id = match network_token_resp {
|
||||
Some(ref token_resp) => {
|
||||
if network_token_requestor_ref_id.is_some() {
|
||||
Some(token_resp.payment_method_id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let pm_card_details = resp.card.as_ref().map(|card| {
|
||||
@ -229,6 +272,24 @@ where
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to encrypt payment method data")?;
|
||||
|
||||
let pm_network_token_data_encrypted: Option<
|
||||
Encryptable<Secret<serde_json::Value>>,
|
||||
> = match network_token_resp {
|
||||
Some(token_resp) => {
|
||||
let pm_token_details = token_resp.card.as_ref().map(|card| {
|
||||
PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))
|
||||
});
|
||||
|
||||
pm_token_details
|
||||
.async_map(|pm_card| create_encrypted_data(state, key_store, pm_card))
|
||||
.await
|
||||
.transpose()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to encrypt payment method data")?
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let encrypted_payment_method_billing_address: Option<
|
||||
Encryptable<Secret<serde_json::Value>>,
|
||||
> = payment_method_billing_address
|
||||
@ -346,6 +407,9 @@ where
|
||||
card.card_network
|
||||
.map(|card_network| card_network.to_string())
|
||||
}),
|
||||
network_token_requestor_ref_id,
|
||||
network_token_locker_id,
|
||||
pm_network_token_data_encrypted.map(Into::into),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@ -449,6 +513,9 @@ where
|
||||
card_network.to_string()
|
||||
})
|
||||
}),
|
||||
network_token_requestor_ref_id,
|
||||
network_token_locker_id,
|
||||
pm_network_token_data_encrypted.map(Into::into),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@ -651,6 +718,9 @@ where
|
||||
card.card_network
|
||||
.map(|card_network| card_network.to_string())
|
||||
}),
|
||||
network_token_requestor_ref_id,
|
||||
network_token_locker_id,
|
||||
pm_network_token_data_encrypted.map(Into::into),
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
@ -861,6 +931,94 @@ pub async fn save_in_locker(
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
|
||||
pub async fn save_network_token_in_locker(
|
||||
_state: &SessionState,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
_card_data: &domain::Card,
|
||||
_payment_method_request: api::PaymentMethodCreate,
|
||||
) -> RouterResult<(
|
||||
Option<api_models::payment_methods::PaymentMethodResponse>,
|
||||
Option<payment_methods::transformers::DataDuplicationCheck>,
|
||||
Option<String>,
|
||||
)> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "v1", feature = "v2"),
|
||||
not(feature = "payment_methods_v2")
|
||||
))]
|
||||
pub async fn save_network_token_in_locker(
|
||||
state: &SessionState,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
card_data: &domain::Card,
|
||||
payment_method_request: api::PaymentMethodCreate,
|
||||
) -> RouterResult<(
|
||||
Option<api_models::payment_methods::PaymentMethodResponse>,
|
||||
Option<payment_methods::transformers::DataDuplicationCheck>,
|
||||
Option<String>,
|
||||
)> {
|
||||
let customer_id = payment_method_request
|
||||
.customer_id
|
||||
.clone()
|
||||
.get_required_value("customer_id")?;
|
||||
let network_tokenization_supported_card_networks = &state
|
||||
.conf
|
||||
.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()
|
||||
{
|
||||
match network_tokenization::make_card_network_tokenization_request(
|
||||
state,
|
||||
card_data,
|
||||
&customer_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((token_response, network_token_requestor_ref_id)) => {
|
||||
// Only proceed if the tokenization was successful
|
||||
let card_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,
|
||||
};
|
||||
|
||||
let (res, dc) = Box::pin(payment_methods::cards::add_card_to_locker(
|
||||
state,
|
||||
payment_method_request,
|
||||
&card_data,
|
||||
&customer_id,
|
||||
merchant_account,
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_payment_method_metadata(
|
||||
metadata: Option<&pii::SecretSerdeValue>,
|
||||
connector_token: Option<(String, String)>,
|
||||
|
||||
Reference in New Issue
Block a user