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:
Prasunna Soppa
2024-09-16 16:43:15 +05:30
committed by GitHub
parent 90ebd54ec9
commit 61e2ca9b17
63 changed files with 2369 additions and 224 deletions

View File

@ -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)>,