From 0b972e38abd08380b75165dfd755087769f35a62 Mon Sep 17 00:00:00 2001
From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com>
Date: Fri, 14 Feb 2025 18:10:42 +0530
Subject: [PATCH] feat(payment_methods_v2): add support for network
tokenization (#7145)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
---
api-reference-v2/openapi_spec.json | 8 +
crates/api_models/src/payment_methods.rs | 14 +-
crates/cards/src/lib.rs | 2 +-
crates/cards/src/validate.rs | 80 +++
crates/hyperswitch_connectors/Cargo.toml | 1 +
.../connectors/cybersource/transformers.rs | 11 +-
crates/hyperswitch_connectors/src/utils.rs | 61 +++
crates/hyperswitch_domain_models/src/lib.rs | 1 +
.../src/network_tokenization.rs | 231 ++++++++
.../src/payment_method_data.rs | 120 ++++-
.../src/router_data.rs | 7 +-
crates/router/Cargo.toml | 4 +-
.../src/connector/adyen/transformers.rs | 36 +-
crates/router/src/connector/utils.rs | 79 +++
crates/router/src/core/errors.rs | 2 +
crates/router/src/core/payment_methods.rs | 188 ++++++-
.../payment_methods/network_tokenization.rs | 492 ++++++++++++------
.../src/core/payment_methods/transformers.rs | 1 +
crates/router/src/routes/payment_methods.rs | 1 +
crates/router/src/types/domain.rs | 5 +
crates/router/src/types/payment_methods.rs | 4 +
21 files changed, 1159 insertions(+), 189 deletions(-)
create mode 100644 crates/hyperswitch_domain_models/src/network_tokenization.rs
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index 3a43b1bfca..cedd4abcca 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -13781,6 +13781,14 @@
}
],
"nullable": true
+ },
+ "network_tokenization": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/NetworkTokenization"
+ }
+ ],
+ "nullable": true
}
},
"additionalProperties": false
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index ba18cbba50..a228f5fbda 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -135,6 +135,10 @@ pub struct PaymentMethodCreate {
/// The billing details of the payment method
#[schema(value_type = Option
)]
pub billing: Option,
+
+ /// The network tokenization configuration if applicable
+ #[schema(value_type = Option)]
+ pub network_tokenization: Option,
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
@@ -547,7 +551,15 @@ pub struct CardDetail {
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[derive(
- Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, strum::EnumString, strum::Display,
+ Debug,
+ serde::Deserialize,
+ serde::Serialize,
+ Clone,
+ ToSchema,
+ strum::EnumString,
+ strum::Display,
+ Eq,
+ PartialEq,
)]
#[serde(rename_all = "snake_case")]
pub enum CardType {
diff --git a/crates/cards/src/lib.rs b/crates/cards/src/lib.rs
index 0c0fb9d47a..765fa7c6f8 100644
--- a/crates/cards/src/lib.rs
+++ b/crates/cards/src/lib.rs
@@ -7,7 +7,7 @@ use masking::{PeekInterface, StrongSecret};
use serde::{de, Deserialize, Serialize};
use time::{util::days_in_year_month, Date, Duration, PrimitiveDateTime, Time};
-pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr};
+pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr, NetworkToken};
#[derive(Serialize)]
pub struct CardSecurityCode(StrongSecret);
diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs
index 725d05b480..b8b9cdbf18 100644
--- a/crates/cards/src/validate.rs
+++ b/crates/cards/src/validate.rs
@@ -24,6 +24,10 @@ pub struct CardNumberValidationErr(&'static str);
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct CardNumber(StrongSecret);
+//Network Token
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
+pub struct NetworkToken(StrongSecret);
+
impl CardNumber {
pub fn get_card_isin(&self) -> String {
self.0.peek().chars().take(6).collect::()
@@ -102,6 +106,30 @@ impl CardNumber {
}
}
+impl NetworkToken {
+ pub fn get_card_isin(&self) -> String {
+ self.0.peek().chars().take(6).collect::()
+ }
+
+ pub fn get_extended_card_bin(&self) -> String {
+ self.0.peek().chars().take(8).collect::()
+ }
+ pub fn get_card_no(&self) -> String {
+ self.0.peek().chars().collect::()
+ }
+ pub fn get_last4(&self) -> String {
+ self.0
+ .peek()
+ .chars()
+ .rev()
+ .take(4)
+ .collect::()
+ .chars()
+ .rev()
+ .collect::()
+ }
+}
+
impl FromStr for CardNumber {
type Err = CardNumberValidationErr;
@@ -131,6 +159,35 @@ impl FromStr for CardNumber {
}
}
+impl FromStr for NetworkToken {
+ type Err = CardNumberValidationErr;
+
+ fn from_str(network_token: &str) -> Result {
+ // Valid test cards for threedsecureio
+ let valid_test_network_tokens = vec![
+ "4000100511112003",
+ "6000100611111203",
+ "3000100811111072",
+ "9000100111111111",
+ ];
+ #[cfg(not(target_arch = "wasm32"))]
+ let valid_test_network_tokens = match router_env_which() {
+ Env::Development | Env::Sandbox => valid_test_network_tokens,
+ Env::Production => vec![],
+ };
+
+ let network_token = network_token.split_whitespace().collect::();
+
+ let is_network_token_valid = sanitize_card_number(&network_token)?;
+
+ if valid_test_network_tokens.contains(&network_token.as_str()) || is_network_token_valid {
+ Ok(Self(StrongSecret::new(network_token)))
+ } else {
+ Err(CardNumberValidationErr("network token invalid"))
+ }
+ }
+}
+
pub fn sanitize_card_number(card_number: &str) -> Result {
let is_card_number_valid = Ok(card_number)
.and_then(validate_card_number_chars)
@@ -195,6 +252,14 @@ impl TryFrom for CardNumber {
}
}
+impl TryFrom for NetworkToken {
+ type Error = CardNumberValidationErr;
+
+ fn try_from(value: String) -> Result {
+ Self::from_str(&value)
+ }
+}
+
impl Deref for CardNumber {
type Target = StrongSecret;
@@ -203,6 +268,14 @@ impl Deref for CardNumber {
}
}
+impl Deref for NetworkToken {
+ type Target = StrongSecret;
+
+ fn deref(&self) -> &StrongSecret {
+ &self.0
+ }
+}
+
impl<'de> Deserialize<'de> for CardNumber {
fn deserialize>(d: D) -> Result {
let s = String::deserialize(d)?;
@@ -210,6 +283,13 @@ impl<'de> Deserialize<'de> for CardNumber {
}
}
+impl<'de> Deserialize<'de> for NetworkToken {
+ fn deserialize>(d: D) -> Result {
+ let s = String::deserialize(d)?;
+ Self::from_str(&s).map_err(serde::de::Error::custom)
+ }
+}
+
pub enum CardNumberStrategy {}
impl Strategy for CardNumberStrategy
diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml
index 6b76e6ac19..13d5266dab 100644
--- a/crates/hyperswitch_connectors/Cargo.toml
+++ b/crates/hyperswitch_connectors/Cargo.toml
@@ -9,6 +9,7 @@ license.workspace = true
frm = ["hyperswitch_domain_models/frm", "hyperswitch_interfaces/frm"]
payouts = ["hyperswitch_domain_models/payouts", "api_models/payouts", "hyperswitch_interfaces/payouts"]
v1 = ["api_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"]
+v2 = ["api_models/v2", "hyperswitch_domain_models/v2", "common_utils/v2"]
[dependencies]
actix-http = "3.6.0"
diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
index b42c02606c..b953fa7375 100644
--- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
+++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
@@ -18,6 +18,7 @@ use hyperswitch_domain_models::{
types::PayoutsRouterData,
};
use hyperswitch_domain_models::{
+ network_tokenization::NetworkTokenNumber,
payment_method_data::{
ApplePayWalletData, GooglePayWalletData, NetworkTokenData, PaymentMethodData,
SamsungPayWalletData, WalletData,
@@ -430,7 +431,7 @@ pub struct CaptureOptions {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkTokenizedCard {
- number: cards::CardNumber,
+ number: NetworkTokenNumber,
expiration_month: Secret,
expiration_year: Secret,
cryptogram: Option>,
@@ -1400,10 +1401,10 @@ impl
let payment_information =
PaymentInformation::NetworkToken(Box::new(NetworkTokenPaymentInformation {
tokenized_card: NetworkTokenizedCard {
- number: token_data.token_number,
- expiration_month: token_data.token_exp_month,
- expiration_year: token_data.token_exp_year,
- cryptogram: token_data.token_cryptogram.clone(),
+ number: token_data.get_network_token(),
+ expiration_month: token_data.get_network_token_expiry_month(),
+ expiration_year: token_data.get_network_token_expiry_year(),
+ cryptogram: token_data.get_cryptogram().clone(),
transaction_type: "1".to_string(),
},
}));
diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs
index 6c39667de4..b14abec9d3 100644
--- a/crates/hyperswitch_connectors/src/utils.rs
+++ b/crates/hyperswitch_connectors/src/utils.rs
@@ -19,6 +19,7 @@ use common_utils::{
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
address::{Address, AddressDetails, PhoneDetails},
+ network_tokenization::NetworkTokenNumber,
payment_method_data::{self, Card, CardDetailsForNetworkTransactionId, PaymentMethodData},
router_data::{
ApplePayPredecryptData, ErrorResponse, PaymentMethodToken, RecurringMandatePaymentData,
@@ -2957,13 +2958,24 @@ impl CardData for api_models::payouts::CardPayout {
pub trait NetworkTokenData {
fn get_card_issuer(&self) -> Result;
fn get_expiry_year_4_digit(&self) -> Secret;
+ fn get_network_token(&self) -> NetworkTokenNumber;
+ fn get_network_token_expiry_month(&self) -> Secret;
+ fn get_network_token_expiry_year(&self) -> Secret;
+ fn get_cryptogram(&self) -> Option>;
}
impl NetworkTokenData for payment_method_data::NetworkTokenData {
+ #[cfg(feature = "v1")]
fn get_card_issuer(&self) -> Result {
get_card_issuer(self.token_number.peek())
}
+ #[cfg(feature = "v2")]
+ fn get_card_issuer(&self) -> Result {
+ get_card_issuer(self.network_token.peek())
+ }
+
+ #[cfg(feature = "v1")]
fn get_expiry_year_4_digit(&self) -> Secret {
let mut year = self.token_exp_year.peek().clone();
if year.len() == 2 {
@@ -2971,4 +2983,53 @@ impl NetworkTokenData for payment_method_data::NetworkTokenData {
}
Secret::new(year)
}
+
+ #[cfg(feature = "v2")]
+ fn get_expiry_year_4_digit(&self) -> Secret {
+ let mut year = self.network_token_exp_year.peek().clone();
+ if year.len() == 2 {
+ year = format!("20{}", year);
+ }
+ Secret::new(year)
+ }
+
+ #[cfg(feature = "v1")]
+ fn get_network_token(&self) -> NetworkTokenNumber {
+ self.token_number.clone()
+ }
+
+ #[cfg(feature = "v2")]
+ fn get_network_token(&self) -> NetworkTokenNumber {
+ self.network_token.clone()
+ }
+
+ #[cfg(feature = "v1")]
+ fn get_network_token_expiry_month(&self) -> Secret {
+ self.token_exp_month.clone()
+ }
+
+ #[cfg(feature = "v2")]
+ fn get_network_token_expiry_month(&self) -> Secret {
+ self.network_token_exp_month.clone()
+ }
+
+ #[cfg(feature = "v1")]
+ fn get_network_token_expiry_year(&self) -> Secret {
+ self.token_exp_year.clone()
+ }
+
+ #[cfg(feature = "v2")]
+ fn get_network_token_expiry_year(&self) -> Secret {
+ self.network_token_exp_year.clone()
+ }
+
+ #[cfg(feature = "v1")]
+ fn get_cryptogram(&self) -> Option> {
+ self.token_cryptogram.clone()
+ }
+
+ #[cfg(feature = "v2")]
+ fn get_cryptogram(&self) -> Option> {
+ self.cryptogram.clone()
+ }
}
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index b2546e3a04..6c8f499388 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -11,6 +11,7 @@ pub mod mandates;
pub mod merchant_account;
pub mod merchant_connector_account;
pub mod merchant_key_store;
+pub mod network_tokenization;
pub mod payment_address;
pub mod payment_method_data;
pub mod payment_methods;
diff --git a/crates/hyperswitch_domain_models/src/network_tokenization.rs b/crates/hyperswitch_domain_models/src/network_tokenization.rs
new file mode 100644
index 0000000000..bf87c75050
--- /dev/null
+++ b/crates/hyperswitch_domain_models/src/network_tokenization.rs
@@ -0,0 +1,231 @@
+use std::fmt::Debug;
+
+use api_models::enums as api_enums;
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+use cards::CardNumber;
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use cards::{CardNumber, NetworkToken};
+use common_utils::id_type;
+use masking::Secret;
+use serde::{Deserialize, Serialize};
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CardData {
+ pub card_number: CardNumber,
+ pub exp_month: Secret,
+ pub exp_year: Secret,
+ pub card_security_code: Secret,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CardData {
+ pub card_number: CardNumber,
+ pub exp_month: Secret,
+ pub exp_year: Secret,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub card_security_code: Option>,
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct OrderData {
+ pub consent_id: String,
+ pub customer_id: id_type::CustomerId,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct OrderData {
+ pub consent_id: String,
+ pub customer_id: id_type::GlobalCustomerId,
+}
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ApiPayload {
+ pub service: String,
+ pub card_data: Secret, //encrypted card data
+ pub order_data: OrderData,
+ pub key_id: String,
+ pub should_send_token: bool,
+}
+
+#[derive(Debug, Deserialize, Eq, PartialEq)]
+pub struct CardNetworkTokenResponse {
+ pub payload: Secret, //encrypted payload
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CardNetworkTokenResponsePayload {
+ pub card_brand: api_enums::CardNetwork,
+ pub card_fingerprint: Option>,
+ pub card_reference: String,
+ pub correlation_id: String,
+ pub customer_id: String,
+ pub par: String,
+ pub token: CardNumber,
+ pub token_expiry_month: Secret,
+ pub token_expiry_year: Secret,
+ pub token_isin: String,
+ pub token_last_four: String,
+ pub token_status: String,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GenerateNetworkTokenResponsePayload {
+ pub card_brand: api_enums::CardNetwork,
+ pub card_fingerprint: Option>,
+ pub card_reference: String,
+ pub correlation_id: String,
+ pub customer_id: String,
+ pub par: String,
+ pub token: NetworkToken,
+ pub token_expiry_month: Secret,
+ pub token_expiry_year: Secret,
+ pub token_isin: String,
+ pub token_last_four: String,
+ pub token_status: String,
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Serialize)]
+pub struct GetCardToken {
+ pub card_reference: String,
+ pub customer_id: id_type::CustomerId,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Serialize)]
+pub struct GetCardToken {
+ pub card_reference: String,
+ pub customer_id: id_type::GlobalCustomerId,
+}
+#[derive(Debug, Deserialize)]
+pub struct AuthenticationDetails {
+ pub cryptogram: Secret,
+ pub token: CardNumber, //network token
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct TokenDetails {
+ pub exp_month: Secret,
+ pub exp_year: Secret,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct TokenResponse {
+ pub authentication_details: AuthenticationDetails,
+ pub network: api_enums::CardNetwork,
+ pub token_details: TokenDetails,
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct DeleteCardToken {
+ pub card_reference: String, //network token requestor ref id
+ pub customer_id: id_type::CustomerId,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct DeleteCardToken {
+ pub card_reference: String, //network token requestor ref id
+ pub customer_id: id_type::GlobalCustomerId,
+}
+
+#[derive(Debug, Deserialize, Eq, PartialEq)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum DeleteNetworkTokenStatus {
+ Success,
+}
+
+#[derive(Debug, Deserialize, Eq, PartialEq)]
+pub struct NetworkTokenErrorInfo {
+ pub code: String,
+ pub developer_message: String,
+}
+
+#[derive(Debug, Deserialize, Eq, PartialEq)]
+pub struct NetworkTokenErrorResponse {
+ pub error_message: String,
+ pub error_info: NetworkTokenErrorInfo,
+}
+
+#[derive(Debug, Deserialize, Eq, PartialEq)]
+pub struct DeleteNetworkTokenResponse {
+ pub status: DeleteNetworkTokenStatus,
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CheckTokenStatus {
+ pub card_reference: String,
+ pub customer_id: id_type::CustomerId,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct CheckTokenStatus {
+ pub card_reference: String,
+ pub customer_id: id_type::GlobalCustomerId,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "UPPERCASE")]
+pub enum TokenStatus {
+ Active,
+ Inactive,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CheckTokenStatusResponsePayload {
+ pub token_expiry_month: Secret,
+ pub token_expiry_year: Secret,
+ pub token_status: TokenStatus,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct CheckTokenStatusResponse {
+ pub payload: CheckTokenStatusResponsePayload,
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
+pub type NetworkTokenNumber = CardNumber;
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub type NetworkTokenNumber = NetworkToken;
diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs
index e96368080d..2f8375b47c 100644
--- a/crates/hyperswitch_domain_models/src/payment_method_data.rs
+++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs
@@ -1,5 +1,6 @@
use api_models::{
- mandates, payment_methods,
+ mandates,
+ payment_methods::{self},
payments::{
additional_info as payment_additional_types, AmazonPayRedirectData, ExtendedCardInfo,
},
@@ -587,6 +588,10 @@ pub struct SepaAndBacsBillingDetails {
pub name: Secret,
}
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
pub struct NetworkTokenData {
pub token_number: cards::CardNumber,
@@ -602,6 +607,37 @@ pub struct NetworkTokenData {
pub eci: Option,
}
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
+pub struct NetworkTokenData {
+ pub network_token: cards::NetworkToken,
+ pub network_token_exp_month: Secret,
+ pub network_token_exp_year: Secret,
+ pub cryptogram: Option>,
+ pub card_issuer: Option, //since network token is tied to card, so its issuer will be same as card issuer
+ pub card_network: Option,
+ pub card_type: Option,
+ pub card_issuing_country: Option,
+ pub bank_code: Option,
+ pub card_holder_name: Option>,
+ pub nick_name: Option>,
+ pub eci: Option,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
+pub struct NetworkTokenDetails {
+ pub network_token: cards::NetworkToken,
+ pub network_token_exp_month: Secret,
+ pub network_token_exp_year: Secret,
+ pub card_issuer: Option, //since network token is tied to card, so its issuer will be same as card issuer
+ pub card_network: Option,
+ pub card_type: Option,
+ pub card_issuing_country: Option,
+ pub card_holder_name: Option>,
+ pub nick_name: Option>,
+}
+
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MobilePaymentData {
@@ -1726,3 +1762,85 @@ impl From for payment_methods::PaymentMethodDataWalletInfo
}
}
}
+
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+pub enum PaymentMethodsData {
+ Card(CardDetailsPaymentMethod),
+ BankDetails(payment_methods::PaymentMethodDataBankCreds), //PaymentMethodDataBankCreds and its transformations should be moved to the domain models
+ WalletDetails(payment_methods::PaymentMethodDataWalletInfo), //PaymentMethodDataWalletInfo and its transformations should be moved to the domain models
+ NetworkToken(NetworkTokenDetailsPaymentMethod),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+pub struct NetworkTokenDetailsPaymentMethod {
+ pub last4_digits: Option,
+ pub issuer_country: Option,
+ pub network_token_expiry_month: Option>,
+ pub network_token_expiry_year: Option>,
+ pub nick_name: Option>,
+ pub card_holder_name: Option>,
+ pub card_isin: Option,
+ pub card_issuer: Option,
+ pub card_network: Option,
+ pub card_type: Option,
+ #[serde(default = "saved_in_locker_default")]
+ pub saved_to_locker: bool,
+}
+
+fn saved_in_locker_default() -> bool {
+ true
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+pub struct CardDetailsPaymentMethod {
+ pub last4_digits: Option,
+ pub issuer_country: Option,
+ pub expiry_month: Option>,
+ pub expiry_year: Option>,
+ pub nick_name: Option>,
+ pub card_holder_name: Option>,
+ pub card_isin: Option,
+ pub card_issuer: Option,
+ pub card_network: Option,
+ pub card_type: Option,
+ #[serde(default = "saved_in_locker_default")]
+ pub saved_to_locker: bool,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+impl From for CardDetailsPaymentMethod {
+ fn from(item: payment_methods::CardDetail) -> Self {
+ Self {
+ issuer_country: item.card_issuing_country.map(|c| c.to_string()),
+ last4_digits: Some(item.card_number.get_last4()),
+ expiry_month: Some(item.card_exp_month),
+ expiry_year: Some(item.card_exp_year),
+ card_holder_name: item.card_holder_name,
+ nick_name: item.nick_name,
+ card_isin: None,
+ card_issuer: item.card_issuer,
+ card_network: item.card_network,
+ card_type: item.card_type.map(|card| card.to_string()),
+ saved_to_locker: true,
+ }
+ }
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+impl From for NetworkTokenDetailsPaymentMethod {
+ fn from(item: NetworkTokenDetails) -> Self {
+ Self {
+ issuer_country: item.card_issuing_country.map(|c| c.to_string()),
+ last4_digits: Some(item.network_token.get_last4()),
+ network_token_expiry_month: Some(item.network_token_exp_month),
+ network_token_expiry_year: Some(item.network_token_exp_year),
+ card_holder_name: item.card_holder_name,
+ nick_name: item.nick_name,
+ card_isin: None,
+ card_issuer: item.card_issuer,
+ card_network: item.card_network,
+ card_type: item.card_type.map(|card| card.to_string()),
+ saved_to_locker: true,
+ }
+ }
+}
diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs
index 0c90b0d024..e3a8264217 100644
--- a/crates/hyperswitch_domain_models/src/router_data.rs
+++ b/crates/hyperswitch_domain_models/src/router_data.rs
@@ -9,7 +9,10 @@ use common_utils::{
use error_stack::ResultExt;
use masking::{ExposeInterface, Secret};
-use crate::{payment_address::PaymentAddress, payment_method_data, payments};
+use crate::{
+ network_tokenization::NetworkTokenNumber, payment_address::PaymentAddress, payment_method_data,
+ payments,
+};
#[cfg(feature = "v2")]
use crate::{
payments::{
@@ -294,7 +297,7 @@ pub struct PazeDecryptedData {
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeToken {
- pub payment_token: cards::CardNumber,
+ pub payment_token: NetworkTokenNumber,
pub token_expiration_month: Secret,
pub token_expiration_year: Secret,
pub payment_account_reference: Secret,
diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml
index 6257d3e083..d590797d64 100644
--- a/crates/router/Cargo.toml
+++ b/crates/router/Cargo.toml
@@ -33,8 +33,8 @@ payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_connectors
payout_retry = ["payouts"]
recon = ["email", "api_models/recon"]
retry = []
-v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2"]
-v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1"]
+v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2"]
+v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1", "hyperswitch_connectors/v1"]
customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"]
payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"]
dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"]
diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs
index 465730cca6..c9bfdbf3d4 100644
--- a/crates/router/src/connector/adyen/transformers.rs
+++ b/crates/router/src/connector/adyen/transformers.rs
@@ -4,7 +4,9 @@ use api_models::{enums, payments, webhooks};
use cards::CardNumber;
use common_utils::{errors::ParsingError, ext_traits::Encode, id_type, pii, types::MinorUnit};
use error_stack::{report, ResultExt};
-use hyperswitch_domain_models::router_request_types::SubmitEvidenceRequestData;
+use hyperswitch_domain_models::{
+ network_tokenization::NetworkTokenNumber, router_request_types::SubmitEvidenceRequestData,
+};
use masking::{ExposeInterface, PeekInterface};
use reqwest::Url;
use serde::{Deserialize, Serialize};
@@ -637,6 +639,7 @@ pub enum AdyenPaymentMethod<'a> {
PayEasy(Box),
Pix(Box),
NetworkToken(Box),
+ AdyenPaze(Box),
}
#[derive(Debug, Clone, Serialize)]
@@ -1142,6 +1145,21 @@ pub struct AdyenCard {
network_payment_reference: Option>,
}
+#[serde_with::skip_serializing_none]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AdyenPazeData {
+ #[serde(rename = "type")]
+ payment_type: PaymentType,
+ number: NetworkTokenNumber,
+ expiry_month: Secret,
+ expiry_year: Secret,
+ cvc: Option>,
+ holder_name: Option>,
+ brand: Option, //Mandatory for mandate using network_txns_id
+ network_payment_reference: Option>,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CardBrand {
@@ -1241,7 +1259,7 @@ pub struct AdyenApplePay {
pub struct AdyenNetworkTokenData {
#[serde(rename = "type")]
payment_type: PaymentType,
- number: CardNumber,
+ number: NetworkTokenNumber,
expiry_month: Secret,
expiry_year: Secret,
holder_name: Option>,
@@ -2200,7 +2218,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)>
}
domain::WalletData::Paze(_) => match item.payment_method_token.clone() {
Some(types::PaymentMethodToken::PazeDecrypt(paze_decrypted_data)) => {
- let data = AdyenCard {
+ let data = AdyenPazeData {
payment_type: PaymentType::NetworkToken,
number: paze_decrypted_data.token.payment_token,
expiry_month: paze_decrypted_data.token.token_expiration_month,
@@ -2214,7 +2232,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)>
.and_then(get_adyen_card_network),
network_payment_reference: None,
};
- Ok(AdyenPaymentMethod::AdyenCard(Box::new(data)))
+ Ok(AdyenPaymentMethod::AdyenPaze(Box::new(data)))
}
_ => Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Cybersource"),
@@ -2725,8 +2743,8 @@ impl
let card_holder_name = item.router_data.get_optional_billing_full_name();
let adyen_network_token = AdyenNetworkTokenData {
payment_type: PaymentType::NetworkToken,
- number: token_data.token_number.clone(),
- expiry_month: token_data.token_exp_month.clone(),
+ number: token_data.get_network_token(),
+ expiry_month: token_data.get_network_token_expiry_month(),
expiry_year: token_data.get_expiry_year_4_digit(),
holder_name: card_holder_name,
brand: Some(brand), // FIXME: Remove hardcoding
@@ -5577,8 +5595,8 @@ impl TryFrom<(&domain::NetworkTokenData, Option>)> for AdyenPayme
) -> Result {
let adyen_network_token = AdyenNetworkTokenData {
payment_type: PaymentType::NetworkToken,
- number: token_data.token_number.clone(),
- expiry_month: token_data.token_exp_month.clone(),
+ number: token_data.get_network_token(),
+ expiry_month: token_data.get_network_token_expiry_month(),
expiry_year: token_data.get_expiry_year_4_digit(),
holder_name: card_holder_name,
brand: None, // FIXME: Remove hardcoding
@@ -5627,7 +5645,7 @@ impl
directory_response: "Y".to_string(),
authentication_response: "Y".to_string(),
token_authentication_verification_value: token_data
- .token_cryptogram
+ .get_cryptogram()
.clone()
.unwrap_or_default(),
eci: Some("02".to_string()),
diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs
index 28f5d016e5..0aca263b5c 100644
--- a/crates/router/src/connector/utils.rs
+++ b/crates/router/src/connector/utils.rs
@@ -22,6 +22,7 @@ use diesel_models::{enums, types::OrderDetailsWithAmount};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
mandates,
+ network_tokenization::NetworkTokenNumber,
payments::payment_attempt::PaymentAttempt,
router_request_types::{
AuthoriseIntegrityObject, CaptureIntegrityObject, RefundIntegrityObject,
@@ -3181,13 +3182,30 @@ pub fn get_refund_integrity_object(
pub trait NetworkTokenData {
fn get_card_issuer(&self) -> Result;
fn get_expiry_year_4_digit(&self) -> Secret;
+ fn get_network_token(&self) -> NetworkTokenNumber;
+ fn get_network_token_expiry_month(&self) -> Secret;
+ fn get_network_token_expiry_year(&self) -> Secret;
+ fn get_cryptogram(&self) -> Option>;
}
impl NetworkTokenData for domain::NetworkTokenData {
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
fn get_card_issuer(&self) -> Result {
get_card_issuer(self.token_number.peek())
}
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_card_issuer(&self) -> Result {
+ get_card_issuer(self.network_token.peek())
+ }
+
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
fn get_expiry_year_4_digit(&self) -> Secret {
let mut year = self.token_exp_year.peek().clone();
if year.len() == 2 {
@@ -3195,6 +3213,67 @@ impl NetworkTokenData for domain::NetworkTokenData {
}
Secret::new(year)
}
+
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_expiry_year_4_digit(&self) -> Secret {
+ let mut year = self.network_token_exp_year.peek().clone();
+ if year.len() == 2 {
+ year = format!("20{}", year);
+ }
+ Secret::new(year)
+ }
+
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
+ fn get_network_token(&self) -> NetworkTokenNumber {
+ self.token_number.clone()
+ }
+
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_network_token(&self) -> NetworkTokenNumber {
+ self.network_token.clone()
+ }
+
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
+ fn get_network_token_expiry_month(&self) -> Secret {
+ self.token_exp_month.clone()
+ }
+
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_network_token_expiry_month(&self) -> Secret {
+ self.network_token_exp_month.clone()
+ }
+
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
+ fn get_network_token_expiry_year(&self) -> Secret {
+ self.token_exp_year.clone()
+ }
+
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_network_token_expiry_year(&self) -> Secret {
+ self.network_token_exp_year.clone()
+ }
+
+ #[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+ ))]
+ fn get_cryptogram(&self) -> Option> {
+ self.token_cryptogram.clone()
+ }
+
+ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+ fn get_cryptogram(&self) -> Option> {
+ self.cryptogram.clone()
+ }
}
pub fn convert_uppercase<'de, D, T>(v: D) -> Result
diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs
index 506ce55719..3dbaa99921 100644
--- a/crates/router/src/core/errors.rs
+++ b/crates/router/src/core/errors.rs
@@ -442,4 +442,6 @@ pub enum NetworkTokenizationError {
DeleteNetworkTokenFailed,
#[error("Network token service not configured")]
NetworkTokenizationServiceNotConfigured,
+ #[error("Failed while calling Network Token Service API")]
+ ApiError,
}
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index 5bdb0f0819..6ebc7bde5e 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -47,9 +47,9 @@ use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData};
feature = "customer_v2"
))]
use hyperswitch_domain_models::mandates::CommonMandateReference;
-use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent};
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
-use masking::ExposeInterface;
+use hyperswitch_domain_models::payment_method_data;
+use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent};
use masking::{PeekInterface, Secret};
use router_env::{instrument, tracing};
use time::Duration;
@@ -737,6 +737,7 @@ pub(crate) async fn get_payment_method_create_request(
card_detail,
),
billing: None,
+ network_tokenization: None,
};
Ok(payment_method_request)
}
@@ -853,6 +854,7 @@ pub async fn create_payment_method(
req: api::PaymentMethodCreate,
merchant_account: &domain::MerchantAccount,
key_store: &domain::MerchantKeyStore,
+ profile: &domain::Profile,
) -> RouterResponse {
use common_utils::ext_traits::ValueExt;
@@ -910,6 +912,9 @@ pub async fn create_payment_method(
let payment_method_data = pm_types::PaymentMethodVaultingData::from(req.payment_method_data);
+ let payment_method_data =
+ populate_bin_details_for_payment_method(state, &payment_method_data).await;
+
let vaulting_result = vault_payment_method(
state,
&payment_method_data,
@@ -919,6 +924,25 @@ pub async fn create_payment_method(
)
.await;
+ let network_tokenization_resp = network_tokenize_and_vault_the_pmd(
+ state,
+ &payment_method_data,
+ merchant_account,
+ key_store,
+ req.network_tokenization,
+ profile.is_network_tokenization_enabled,
+ &customer_id,
+ )
+ .await
+ .map_err(|e| {
+ services::logger::error!(
+ "Failed to network tokenize the payment method for customer: {}. Error: {} ",
+ customer_id.get_string_repr(),
+ e
+ );
+ })
+ .ok();
+
let response = match vaulting_result {
Ok((vaulting_resp, fingerprint_id)) => {
let pm_update = create_pm_additional_data_update(
@@ -929,6 +953,7 @@ pub async fn create_payment_method(
Some(req.payment_method_type),
Some(req.payment_method_subtype),
Some(fingerprint_id),
+ network_tokenization_resp,
)
.await
.attach_printable("Unable to create Payment method data")?;
@@ -972,6 +997,145 @@ pub async fn create_payment_method(
Ok(services::ApplicationResponse::Json(response))
}
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Clone, Debug)]
+pub struct NetworkTokenPaymentMethodDetails {
+ network_token_requestor_reference_id: String,
+ network_token_locker_id: String,
+ network_token_pmd: Encryptable>,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn network_tokenize_and_vault_the_pmd(
+ state: &SessionState,
+ payment_method_data: &pm_types::PaymentMethodVaultingData,
+ merchant_account: &domain::MerchantAccount,
+ key_store: &domain::MerchantKeyStore,
+ network_tokenization: Option,
+ network_tokenization_enabled_for_profile: bool,
+ customer_id: &id_type::GlobalCustomerId,
+) -> RouterResult {
+ when(!network_tokenization_enabled_for_profile, || {
+ Err(report!(errors::ApiErrorResponse::NotSupported {
+ message: "Network Tokenization is not enabled for this payment method".to_string()
+ }))
+ })?;
+
+ let is_network_tokenization_enabled_for_pm = network_tokenization
+ .as_ref()
+ .map(|nt| matches!(nt.enable, common_enums::NetworkTokenizationToggle::Enable))
+ .unwrap_or(false);
+
+ let card_data = match payment_method_data {
+ pm_types::PaymentMethodVaultingData::Card(data)
+ if is_network_tokenization_enabled_for_pm =>
+ {
+ Ok(data)
+ }
+ _ => Err(report!(errors::ApiErrorResponse::NotSupported {
+ message: "Network Tokenization is not supported for this payment method".to_string()
+ })),
+ }?;
+
+ let (resp, network_token_req_ref_id) =
+ network_tokenization::make_card_network_tokenization_request(state, card_data, customer_id)
+ .await
+ .change_context(errors::ApiErrorResponse::InternalServerError)
+ .attach_printable("Failed to generate network token")?;
+
+ let network_token_vaulting_data = pm_types::PaymentMethodVaultingData::NetworkToken(resp);
+ let vaulting_resp = vault::add_payment_method_to_vault(
+ state,
+ merchant_account,
+ &network_token_vaulting_data,
+ None,
+ )
+ .await
+ .change_context(errors::ApiErrorResponse::InternalServerError)
+ .attach_printable("Failed to vault the network token data")?;
+
+ let key_manager_state = &(state).into();
+ let network_token = match network_token_vaulting_data {
+ pm_types::PaymentMethodVaultingData::Card(card) => {
+ payment_method_data::PaymentMethodsData::Card(
+ payment_method_data::CardDetailsPaymentMethod::from(card.clone()),
+ )
+ }
+ pm_types::PaymentMethodVaultingData::NetworkToken(network_token) => {
+ payment_method_data::PaymentMethodsData::NetworkToken(
+ payment_method_data::NetworkTokenDetailsPaymentMethod::from(network_token.clone()),
+ )
+ }
+ };
+
+ let network_token_pmd =
+ cards::create_encrypted_data(key_manager_state, key_store, network_token)
+ .await
+ .change_context(errors::ApiErrorResponse::InternalServerError)
+ .attach_printable("Unable to encrypt Payment method data")?;
+
+ Ok(NetworkTokenPaymentMethodDetails {
+ network_token_requestor_reference_id: network_token_req_ref_id,
+ network_token_locker_id: vaulting_resp.vault_id.get_string_repr().clone(),
+ network_token_pmd,
+ })
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn populate_bin_details_for_payment_method(
+ state: &SessionState,
+ payment_method_data: &pm_types::PaymentMethodVaultingData,
+) -> pm_types::PaymentMethodVaultingData {
+ match payment_method_data {
+ pm_types::PaymentMethodVaultingData::Card(card) => {
+ let card_isin = card.card_number.get_card_isin();
+
+ if card.card_issuer.is_some()
+ && card.card_network.is_some()
+ && card.card_type.is_some()
+ && card.card_issuing_country.is_some()
+ {
+ pm_types::PaymentMethodVaultingData::Card(card.clone())
+ } else {
+ let card_info = state
+ .store
+ .get_card_info(&card_isin)
+ .await
+ .map_err(|error| services::logger::error!(card_info_error=?error))
+ .ok()
+ .flatten();
+
+ pm_types::PaymentMethodVaultingData::Card(payment_methods::CardDetail {
+ card_number: card.card_number.clone(),
+ card_exp_month: card.card_exp_month.clone(),
+ card_exp_year: card.card_exp_year.clone(),
+ card_holder_name: card.card_holder_name.clone(),
+ nick_name: card.nick_name.clone(),
+ card_issuing_country: card_info.as_ref().and_then(|val| {
+ val.card_issuing_country
+ .as_ref()
+ .map(|c| api_enums::CountryAlpha2::from_str(c))
+ .transpose()
+ .ok()
+ .flatten()
+ }),
+ card_network: card_info.as_ref().and_then(|val| val.card_network.clone()),
+ card_issuer: card_info.as_ref().and_then(|val| val.card_issuer.clone()),
+ card_type: card_info.as_ref().and_then(|val| {
+ val.card_type
+ .as_ref()
+ .map(|c| payment_methods::CardType::from_str(c))
+ .transpose()
+ .ok()
+ .flatten()
+ }),
+ })
+ }
+ }
+ _ => payment_method_data.clone(),
+ }
+}
+
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[instrument(skip_all)]
pub async fn payment_method_intent_create(
@@ -1293,6 +1457,7 @@ pub async fn create_payment_method_for_intent(
Ok(response)
}
+#[allow(clippy::too_many_arguments)]
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
pub async fn create_pm_additional_data_update(
pmd: &pm_types::PaymentMethodVaultingData,
@@ -1302,10 +1467,18 @@ pub async fn create_pm_additional_data_update(
payment_method_type: Option,
payment_method_subtype: Option,
vault_fingerprint_id: Option,
+ nt_data: Option,
) -> RouterResult {
let card = match pmd {
pm_types::PaymentMethodVaultingData::Card(card) => {
- api::PaymentMethodsData::Card(card.clone().into())
+ payment_method_data::PaymentMethodsData::Card(
+ payment_method_data::CardDetailsPaymentMethod::from(card.clone()),
+ )
+ }
+ pm_types::PaymentMethodVaultingData::NetworkToken(network_token) => {
+ payment_method_data::PaymentMethodsData::NetworkToken(
+ payment_method_data::NetworkTokenDetailsPaymentMethod::from(network_token.clone()),
+ )
}
};
let key_manager_state = &(state).into();
@@ -1321,9 +1494,11 @@ pub async fn create_pm_additional_data_update(
payment_method_type_v2: payment_method_type,
payment_method_subtype,
payment_method_data: Some(pmd.into()),
- network_token_requestor_reference_id: None,
- network_token_locker_id: None,
- network_token_payment_method_data: None,
+ network_token_requestor_reference_id: nt_data
+ .clone()
+ .map(|data| data.network_token_requestor_reference_id),
+ network_token_locker_id: nt_data.clone().map(|data| data.network_token_locker_id),
+ network_token_payment_method_data: nt_data.map(|data| data.network_token_pmd.into()),
locker_fingerprint_id: vault_fingerprint_id,
};
@@ -1633,6 +1808,7 @@ pub async fn update_payment_method_core(
payment_method.get_payment_method_type(),
payment_method.get_payment_method_subtype(),
Some(fingerprint_id),
+ None,
)
.await
.attach_printable("Unable to create Payment method data")?;
diff --git a/crates/router/src/core/payment_methods/network_tokenization.rs b/crates/router/src/core/payment_methods/network_tokenization.rs
index bb730caad2..6799a1cf5b 100644
--- a/crates/router/src/core/payment_methods/network_tokenization.rs
+++ b/crates/router/src/core/payment_methods/network_tokenization.rs
@@ -1,7 +1,9 @@
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
use std::fmt::Debug;
-use api_models::{enums as api_enums, payment_methods::PaymentMethodsData};
-use cards::CardNumber;
+use api_models::payment_methods as api_payment_methods;
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use cards::{CardNumber, NetworkToken};
use common_utils::{
errors::CustomResult,
ext_traits::{BytesExt, Encode},
@@ -9,10 +11,17 @@ use common_utils::{
metrics::utils::record_operation_time,
request::RequestContent,
};
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
use error_stack::ResultExt;
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use error_stack::{report, ResultExt};
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use hyperswitch_domain_models::payment_method_data::NetworkTokenDetails;
use josekit::jwe;
use masking::{ExposeInterface, Mask, PeekInterface, Secret};
-use serde::{Deserialize, Serialize};
use super::transformers::DeleteCardResp;
use crate::{
@@ -24,142 +33,21 @@ use crate::{
types::{api, domain},
};
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CardData {
- card_number: CardNumber,
- exp_month: Secret,
- exp_year: Secret,
- card_security_code: Secret,
-}
-
-#[derive(Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct OrderData {
- consent_id: String,
- customer_id: id_type::CustomerId,
-}
-
-#[derive(Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ApiPayload {
- service: String,
- card_data: Secret, //encrypted card data
- order_data: OrderData,
- key_id: String,
- should_send_token: bool,
-}
-
-#[derive(Debug, Deserialize, Eq, PartialEq)]
-pub struct CardNetworkTokenResponse {
- payload: Secret, //encrypted payload
-}
-
-#[derive(Debug, Clone, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CardNetworkTokenResponsePayload {
- pub card_brand: api_enums::CardNetwork,
- pub card_fingerprint: Option>,
- pub card_reference: String,
- pub correlation_id: String,
- pub customer_id: String,
- pub par: String,
- pub token: CardNumber,
- pub token_expiry_month: Secret,
- pub token_expiry_year: Secret,
- pub token_isin: String,
- pub token_last_four: String,
- pub token_status: String,
-}
-
-#[derive(Debug, Serialize)]
-pub struct GetCardToken {
- card_reference: String,
- customer_id: id_type::CustomerId,
-}
-#[derive(Debug, Deserialize)]
-pub struct AuthenticationDetails {
- cryptogram: Secret,
- token: CardNumber, //network token
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct TokenDetails {
- exp_month: Secret,
- exp_year: Secret,
-}
-
-#[derive(Debug, Deserialize)]
-pub struct TokenResponse {
- authentication_details: AuthenticationDetails,
- network: api_enums::CardNetwork,
- token_details: TokenDetails,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct DeleteCardToken {
- card_reference: String, //network token requestor ref id
- customer_id: id_type::CustomerId,
-}
-
-#[derive(Debug, Deserialize, Eq, PartialEq)]
-#[serde(rename_all = "UPPERCASE")]
-pub enum DeleteNetworkTokenStatus {
- Success,
-}
-
-#[derive(Debug, Deserialize, Eq, PartialEq)]
-pub struct NetworkTokenErrorInfo {
- code: String,
- developer_message: String,
-}
-
-#[derive(Debug, Deserialize, Eq, PartialEq)]
-pub struct NetworkTokenErrorResponse {
- error_message: String,
- error_info: NetworkTokenErrorInfo,
-}
-
-#[derive(Debug, Deserialize, Eq, PartialEq)]
-pub struct DeleteNetworkTokenResponse {
- status: DeleteNetworkTokenStatus,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-pub struct CheckTokenStatus {
- card_reference: String,
- customer_id: id_type::CustomerId,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "UPPERCASE")]
-pub enum TokenStatus {
- Active,
- Inactive,
-}
-
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CheckTokenStatusResponsePayload {
- token_expiry_month: Secret,
- token_expiry_year: Secret,
- token_status: TokenStatus,
-}
-
-#[derive(Debug, Deserialize)]
-pub struct CheckTokenStatusResponse {
- payload: CheckTokenStatusResponsePayload,
-}
-
pub const NETWORK_TOKEN_SERVICE: &str = "NETWORK_TOKEN";
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
pub async fn mk_tokenization_req(
state: &routes::SessionState,
payload_bytes: &[u8],
customer_id: id_type::CustomerId,
tokenization_service: &settings::NetworkTokenizationService,
-) -> CustomResult<(CardNetworkTokenResponsePayload, Option), errors::NetworkTokenizationError>
-{
+) -> CustomResult<
+ (domain::CardNetworkTokenResponsePayload, Option),
+ errors::NetworkTokenizationError,
+> {
let enc_key = tokenization_service.public_key.peek().clone();
let key_id = tokenization_service.key_id.clone();
@@ -174,12 +62,12 @@ pub async fn mk_tokenization_req(
.change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
.attach_printable("Error on jwe encrypt")?;
- let order_data = OrderData {
+ let order_data = domain::OrderData {
consent_id: uuid::Uuid::new_v4().to_string(),
customer_id,
};
- let api_payload = ApiPayload {
+ let api_payload = domain::ApiPayload {
service: NETWORK_TOKEN_SERVICE.to_string(),
card_data: Secret::new(jwt),
order_data,
@@ -208,14 +96,14 @@ pub async fn mk_tokenization_req(
let response = services::call_connector_api(state, request, "generate_token")
.await
- .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed);
+ .change_context(errors::NetworkTokenizationError::ApiError);
let res = response
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
.attach_printable("Error while receiving response")
.and_then(|inner| match inner {
Err(err_res) => {
- let parsed_error: NetworkTokenErrorResponse = err_res
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
.response
.parse_struct("Card Network Tokenization Response")
.change_context(
@@ -236,11 +124,10 @@ pub async fn mk_tokenization_req(
logger::error!("Error while deserializing response: {:?}", err);
})?;
- let network_response: CardNetworkTokenResponse = res
+ let network_response: domain::CardNetworkTokenResponse = res
.response
.parse_struct("Card Network Tokenization Response")
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
- logger::debug!("Network Token Response: {:?}", network_response); //added for debugging, will be removed
let dec_key = tokenization_service.private_key.peek().clone();
@@ -256,19 +143,137 @@ pub async fn mk_tokenization_req(
"Failed to decrypt the tokenization response from the tokenization service",
)?;
- let cn_response: CardNetworkTokenResponsePayload =
+ let cn_response: domain::CardNetworkTokenResponsePayload =
serde_json::from_str(&card_network_token_response)
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
Ok((cn_response.clone(), Some(cn_response.card_reference)))
}
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn generate_network_token(
+ state: &routes::SessionState,
+ payload_bytes: &[u8],
+ customer_id: id_type::GlobalCustomerId,
+ tokenization_service: &settings::NetworkTokenizationService,
+) -> CustomResult<
+ (domain::GenerateNetworkTokenResponsePayload, String),
+ errors::NetworkTokenizationError,
+> {
+ let enc_key = tokenization_service.public_key.peek().clone();
+
+ let key_id = tokenization_service.key_id.clone();
+
+ let jwt = encryption::encrypt_jwe(
+ payload_bytes,
+ enc_key,
+ services::EncryptionAlgorithm::A128GCM,
+ Some(key_id.as_str()),
+ )
+ .await
+ .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
+ .attach_printable("Error on jwe encrypt")?;
+
+ let order_data = domain::OrderData {
+ consent_id: uuid::Uuid::new_v4().to_string(),
+ customer_id,
+ };
+
+ let api_payload = domain::ApiPayload {
+ service: NETWORK_TOKEN_SERVICE.to_string(),
+ card_data: Secret::new(jwt),
+ order_data,
+ key_id,
+ should_send_token: true,
+ };
+
+ let mut request = services::Request::new(
+ services::Method::Post,
+ tokenization_service.generate_token_url.as_str(),
+ );
+ request.add_header(headers::CONTENT_TYPE, "application/json".into());
+ request.add_header(
+ headers::AUTHORIZATION,
+ tokenization_service
+ .token_service_api_key
+ .peek()
+ .clone()
+ .into_masked(),
+ );
+ request.add_default_headers();
+
+ request.set_body(RequestContent::Json(Box::new(api_payload)));
+
+ logger::info!("Request to generate token: {:?}", request);
+
+ let response = services::call_connector_api(state, request, "generate_token")
+ .await
+ .change_context(errors::NetworkTokenizationError::ApiError);
+
+ let res = response
+ .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
+ .attach_printable("Error while receiving response")
+ .and_then(|inner| match inner {
+ Err(err_res) => {
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
+ .response
+ .parse_struct("Card Network Tokenization Response")
+ .change_context(
+ errors::NetworkTokenizationError::ResponseDeserializationFailed,
+ )?;
+ logger::error!(
+ error_code = %parsed_error.error_info.code,
+ developer_message = %parsed_error.error_info.developer_message,
+ "Network tokenization error: {}",
+ parsed_error.error_message
+ );
+ Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
+ .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
+ }
+ Ok(res) => Ok(res),
+ })
+ .inspect_err(|err| {
+ logger::error!("Error while deserializing response: {:?}", err);
+ })?;
+
+ let network_response: domain::CardNetworkTokenResponse = res
+ .response
+ .parse_struct("Card Network Tokenization Response")
+ .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
+ logger::debug!("Network Token Response: {:?}", network_response);
+
+ let dec_key = tokenization_service.private_key.peek().clone();
+
+ let card_network_token_response = services::decrypt_jwe(
+ network_response.payload.peek(),
+ services::KeyIdCheck::SkipKeyIdCheck,
+ dec_key,
+ jwe::RSA_OAEP_256,
+ )
+ .await
+ .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
+ .attach_printable(
+ "Failed to decrypt the tokenization response from the tokenization service",
+ )?;
+
+ let cn_response: domain::GenerateNetworkTokenResponsePayload =
+ serde_json::from_str(&card_network_token_response)
+ .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
+ Ok((cn_response.clone(), cn_response.card_reference))
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
pub async fn make_card_network_tokenization_request(
state: &routes::SessionState,
card: &domain::Card,
customer_id: &id_type::CustomerId,
-) -> CustomResult<(CardNetworkTokenResponsePayload, Option), errors::NetworkTokenizationError>
-{
- let card_data = CardData {
+) -> CustomResult<
+ (domain::CardNetworkTokenResponsePayload, Option),
+ errors::NetworkTokenizationError,
+> {
+ let card_data = domain::CardData {
card_number: card.card_number.clone(),
exp_month: card.card_exp_month.clone(),
exp_year: card.card_exp_year.clone(),
@@ -308,18 +313,74 @@ pub async fn make_card_network_tokenization_request(
}
}
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn make_card_network_tokenization_request(
+ state: &routes::SessionState,
+ card: &api_payment_methods::CardDetail,
+ customer_id: &id_type::GlobalCustomerId,
+) -> CustomResult<(NetworkTokenDetails, String), errors::NetworkTokenizationError> {
+ let card_data = domain::CardData {
+ card_number: card.card_number.clone(),
+ exp_month: card.card_exp_month.clone(),
+ exp_year: card.card_exp_year.clone(),
+ card_security_code: None,
+ };
+
+ let payload = card_data
+ .encode_to_string_of_json()
+ .and_then(|x| x.encode_to_string_of_json())
+ .change_context(errors::NetworkTokenizationError::RequestEncodingFailed)?;
+
+ let payload_bytes = payload.as_bytes();
+ let network_tokenization_service = match &state.conf.network_tokenization_service {
+ Some(nt_service) => Ok(nt_service.get_inner()),
+ None => Err(report!(
+ errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured
+ )),
+ }?;
+
+ let (resp, network_token_req_ref_id) = record_operation_time(
+ async {
+ generate_network_token(
+ state,
+ payload_bytes,
+ customer_id.clone(),
+ network_tokenization_service,
+ )
+ .await
+ .inspect_err(|e| logger::error!(error=?e, "Error while making tokenization request"))
+ },
+ &metrics::GENERATE_NETWORK_TOKEN_TIME,
+ router_env::metric_attributes!(("locker", "rust")),
+ )
+ .await?;
+
+ let network_token_details = NetworkTokenDetails {
+ network_token: resp.token,
+ network_token_exp_month: resp.token_expiry_month,
+ network_token_exp_year: resp.token_expiry_year,
+ card_issuer: card.card_issuer.clone(),
+ card_network: Some(resp.card_brand),
+ card_type: card.card_type.clone(),
+ card_issuing_country: card.card_issuing_country,
+ card_holder_name: card.card_holder_name.clone(),
+ nick_name: card.nick_name.clone(),
+ };
+ Ok((network_token_details, network_token_req_ref_id))
+}
+
#[cfg(feature = "v1")]
pub async fn get_network_token(
state: &routes::SessionState,
customer_id: id_type::CustomerId,
network_token_requestor_ref_id: String,
tokenization_service: &settings::NetworkTokenizationService,
-) -> CustomResult {
+) -> CustomResult {
let mut request = services::Request::new(
services::Method::Post,
tokenization_service.fetch_token_url.as_str(),
);
- let payload = GetCardToken {
+ let payload = domain::GetCardToken {
card_reference: network_token_requestor_ref_id,
customer_id,
};
@@ -342,14 +403,14 @@ pub async fn get_network_token(
// Send the request using `call_connector_api`
let response = services::call_connector_api(state, request, "get network token")
.await
- .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed);
+ .change_context(errors::NetworkTokenizationError::ApiError);
let res = response
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
.attach_printable("Error while receiving response")
.and_then(|inner| match inner {
Err(err_res) => {
- let parsed_error: NetworkTokenErrorResponse = err_res
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
.response
.parse_struct("Card Network Tokenization Response")
.change_context(
@@ -367,7 +428,75 @@ pub async fn get_network_token(
Ok(res) => Ok(res),
})?;
- let token_response: TokenResponse = res
+ let token_response: domain::TokenResponse = res
+ .response
+ .parse_struct("Get Network Token Response")
+ .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
+ logger::info!("Fetch Network Token Response: {:?}", token_response);
+
+ Ok(token_response)
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn get_network_token(
+ state: &routes::SessionState,
+ customer_id: &id_type::GlobalCustomerId,
+ network_token_requestor_ref_id: String,
+ tokenization_service: &settings::NetworkTokenizationService,
+) -> CustomResult {
+ let mut request = services::Request::new(
+ services::Method::Post,
+ tokenization_service.fetch_token_url.as_str(),
+ );
+ let payload = domain::GetCardToken {
+ card_reference: network_token_requestor_ref_id,
+ customer_id: customer_id.clone(),
+ };
+
+ request.add_header(headers::CONTENT_TYPE, "application/json".into());
+ request.add_header(
+ headers::AUTHORIZATION,
+ tokenization_service
+ .token_service_api_key
+ .clone()
+ .peek()
+ .clone()
+ .into_masked(),
+ );
+ request.add_default_headers();
+ request.set_body(RequestContent::Json(Box::new(payload)));
+
+ logger::info!("Request to fetch network token: {:?}", request);
+
+ // Send the request using `call_connector_api`
+ let response = services::call_connector_api(state, request, "get network token")
+ .await
+ .change_context(errors::NetworkTokenizationError::ApiError);
+
+ let res = response
+ .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
+ .attach_printable("Error while receiving response")
+ .and_then(|inner| match inner {
+ Err(err_res) => {
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
+ .response
+ .parse_struct("Card Network Tokenization Response")
+ .change_context(
+ errors::NetworkTokenizationError::ResponseDeserializationFailed,
+ )?;
+ logger::error!(
+ error_code = %parsed_error.error_info.code,
+ developer_message = %parsed_error.error_info.developer_message,
+ "Network tokenization error: {}",
+ parsed_error.error_message
+ );
+ Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
+ .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
+ }
+ Ok(res) => Ok(res),
+ })?;
+
+ let token_response: domain::TokenResponse = res
.response
.parse_struct("Get Network Token Response")
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
@@ -415,9 +544,11 @@ pub async fn get_token_from_tokenization_service(
.network_token_payment_method_data
.clone()
.map(|x| x.into_inner().expose())
- .and_then(|v| serde_json::from_value::(v).ok())
+ .and_then(|v| serde_json::from_value::(v).ok())
.and_then(|pmd| match pmd {
- PaymentMethodsData::Card(token) => Some(api::CardDetailFromLocker::from(token)),
+ api_payment_methods::PaymentMethodsData::Card(token) => {
+ Some(api::CardDetailFromLocker::from(token))
+ }
_ => None,
})
.ok_or(errors::ApiErrorResponse::InternalServerError)
@@ -452,9 +583,11 @@ pub async fn do_status_check_for_network_token(
.network_token_payment_method_data
.clone()
.map(|x| x.into_inner().expose())
- .and_then(|v| serde_json::from_value::(v).ok())
+ .and_then(|v| serde_json::from_value::(v).ok())
.and_then(|pmd| match pmd {
- PaymentMethodsData::Card(token) => Some(api::CardDetailFromLocker::from(token)),
+ api_payment_methods::PaymentMethodsData::Card(token) => {
+ Some(api::CardDetailFromLocker::from(token))
+ }
_ => None,
});
let network_token_requestor_reference_id = payment_method_info
@@ -507,6 +640,10 @@ pub async fn do_status_check_for_network_token(
}
}
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
pub async fn check_token_status_with_tokenization_service(
state: &routes::SessionState,
customer_id: &id_type::CustomerId,
@@ -518,7 +655,7 @@ pub async fn check_token_status_with_tokenization_service(
services::Method::Post,
tokenization_service.check_token_status_url.as_str(),
);
- let payload = CheckTokenStatus {
+ let payload = domain::CheckTokenStatus {
card_reference: network_token_requestor_reference_id,
customer_id: customer_id.clone(),
};
@@ -539,13 +676,13 @@ pub async fn check_token_status_with_tokenization_service(
// Send the request using `call_connector_api`
let response = services::call_connector_api(state, request, "Check Network token Status")
.await
- .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed);
+ .change_context(errors::NetworkTokenizationError::ApiError);
let res = response
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
.attach_printable("Error while receiving response")
.and_then(|inner| match inner {
Err(err_res) => {
- let parsed_error: NetworkTokenErrorResponse = err_res
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
.response
.parse_struct("Delete Network Tokenization Response")
.change_context(
@@ -566,20 +703,35 @@ pub async fn check_token_status_with_tokenization_service(
logger::error!("Error while deserializing response: {:?}", err);
})?;
- let check_token_status_response: CheckTokenStatusResponse = res
+ let check_token_status_response: domain::CheckTokenStatusResponse = res
.response
.parse_struct("Delete Network Tokenization Response")
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
match check_token_status_response.payload.token_status {
- TokenStatus::Active => Ok((
+ domain::TokenStatus::Active => Ok((
Some(check_token_status_response.payload.token_expiry_month),
Some(check_token_status_response.payload.token_expiry_year),
)),
- TokenStatus::Inactive => Ok((None, None)),
+ domain::TokenStatus::Inactive => Ok((None, None)),
}
}
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn check_token_status_with_tokenization_service(
+ _state: &routes::SessionState,
+ _customer_id: &id_type::GlobalCustomerId,
+ _network_token_requestor_reference_id: String,
+ _tokenization_service: &settings::NetworkTokenizationService,
+) -> CustomResult<(Option>, Option>), errors::NetworkTokenizationError>
+{
+ todo!()
+}
+
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
pub async fn delete_network_token_from_locker_and_token_service(
state: &routes::SessionState,
customer_id: &id_type::CustomerId,
@@ -624,6 +776,10 @@ pub async fn delete_network_token_from_locker_and_token_service(
Ok(resp)
}
+#[cfg(all(
+ any(feature = "v1", feature = "v2"),
+ not(feature = "payment_methods_v2")
+))]
pub async fn delete_network_token_from_tokenization_service(
state: &routes::SessionState,
network_token_requestor_reference_id: String,
@@ -634,7 +790,7 @@ pub async fn delete_network_token_from_tokenization_service(
services::Method::Post,
tokenization_service.delete_token_url.as_str(),
);
- let payload = DeleteCardToken {
+ let payload = domain::DeleteCardToken {
card_reference: network_token_requestor_reference_id,
customer_id: customer_id.clone(),
};
@@ -657,13 +813,13 @@ pub async fn delete_network_token_from_tokenization_service(
// Send the request using `call_connector_api`
let response = services::call_connector_api(state, request, "delete network token")
.await
- .change_context(errors::NetworkTokenizationError::DeleteNetworkTokenFailed);
+ .change_context(errors::NetworkTokenizationError::ApiError);
let res = response
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
.attach_printable("Error while receiving response")
.and_then(|inner| match inner {
Err(err_res) => {
- let parsed_error: NetworkTokenErrorResponse = err_res
+ let parsed_error: domain::NetworkTokenErrorResponse = err_res
.response
.parse_struct("Delete Network Tokenization Response")
.change_context(
@@ -684,17 +840,29 @@ pub async fn delete_network_token_from_tokenization_service(
logger::error!("Error while deserializing response: {:?}", err);
})?;
- let delete_token_response: DeleteNetworkTokenResponse = res
+ let delete_token_response: domain::DeleteNetworkTokenResponse = res
.response
.parse_struct("Delete Network Tokenization Response")
.change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
logger::info!("Delete Network Token Response: {:?}", delete_token_response);
- if delete_token_response.status == DeleteNetworkTokenStatus::Success {
+ if delete_token_response.status == domain::DeleteNetworkTokenStatus::Success {
Ok(true)
} else {
Err(errors::NetworkTokenizationError::DeleteNetworkTokenFailed)
.attach_printable("Delete Token at Token service failed")
}
}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn delete_network_token_from_locker_and_token_service(
+ _state: &routes::SessionState,
+ _customer_id: &id_type::GlobalCustomerId,
+ _merchant_id: &id_type::MerchantId,
+ _payment_method_id: String,
+ _network_token_locker_id: Option,
+ _network_token_requestor_reference_id: String,
+) -> errors::RouterResult {
+ todo!()
+}
diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs
index c4d3eafe46..947ae97b11 100644
--- a/crates/router/src/core/payment_methods/transformers.rs
+++ b/crates/router/src/core/payment_methods/transformers.rs
@@ -545,6 +545,7 @@ pub fn generate_pm_vaulting_req_from_update_request(
card_holder_name: update_card.card_holder_name,
nick_name: update_card.nick_name,
}),
+ _ => todo!(), //todo! - since support for network tokenization is not added PaymentMethodUpdateData. should be handled later.
}
}
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index 0b6246d448..036646f719 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -85,6 +85,7 @@ pub async fn create_payment_method_api(
req,
&auth.merchant_account,
&auth.key_store,
+ &auth.profile,
))
.await
},
diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs
index 9a87b6d28b..270ed3ca64 100644
--- a/crates/router/src/types/domain.rs
+++ b/crates/router/src/types/domain.rs
@@ -20,6 +20,10 @@ mod callback_mapper {
pub use hyperswitch_domain_models::callback_mapper::CallbackMapper;
}
+mod network_tokenization {
+ pub use hyperswitch_domain_models::network_tokenization::*;
+}
+
pub use customers::*;
pub use merchant_account::*;
@@ -48,6 +52,7 @@ pub use consts::*;
pub use event::*;
pub use merchant_connector_account::*;
pub use merchant_key_store::*;
+pub use network_tokenization::*;
pub use payment_methods::*;
pub use payments::*;
#[cfg(feature = "olap")]
diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs
index c40d6fedfe..d639bc44bc 100644
--- a/crates/router/src/types/payment_methods.rs
+++ b/crates/router/src/types/payment_methods.rs
@@ -1,6 +1,8 @@
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
use common_utils::generate_id;
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use hyperswitch_domain_models::payment_method_data::NetworkTokenDetails;
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
use masking::Secret;
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
@@ -115,6 +117,7 @@ impl VaultingInterface for VaultDelete {
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub enum PaymentMethodVaultingData {
Card(api::CardDetail),
+ NetworkToken(NetworkTokenDetails),
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
@@ -122,6 +125,7 @@ impl VaultingDataInterface for PaymentMethodVaultingData {
fn get_vaulting_data_key(&self) -> String {
match &self {
Self::Card(card) => card.card_number.to_string(),
+ Self::NetworkToken(network_token) => network_token.network_token.to_string(),
}
}
}