From 3f9c0bad15ed2fa1fd698e2f07a7a8393b64d736 Mon Sep 17 00:00:00 2001 From: Sagnik Mitra Date: Wed, 18 Dec 2024 11:44:05 +0530 Subject: [PATCH] pre payment network tokenization --- crates/api_models/src/admin.rs | 22 ++ crates/diesel_models/src/business_profile.rs | 6 + crates/diesel_models/src/schema.rs | 1 + .../src/business_profile.rs | 27 +++ crates/router/src/core/admin.rs | 2 + .../payment_methods/network_tokenization.rs | 15 +- crates/router/src/core/payments.rs | 190 ++++++++++++++++-- .../payments/operations/payment_approve.rs | 2 +- .../payments/operations/payment_cancel.rs | 2 +- .../payments/operations/payment_capture.rs | 2 +- .../operations/payment_post_session_tokens.rs | 2 +- .../payments/operations/payment_status.rs | 2 +- .../payments/operations/payment_update.rs | 2 +- .../payments_incremental_authorization.rs | 2 +- .../payments/operations/tax_calculation.rs | 2 +- .../router/src/core/payments/tokenization.rs | 80 +++++++- .../router/src/core/payments/transformers.rs | 4 +- crates/router/src/types/api/admin.rs | 2 + .../down.sql | 2 + .../up.sql | 2 + 20 files changed, 330 insertions(+), 39 deletions(-) create mode 100644 migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/down.sql create mode 100644 migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/up.sql diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 76db62bbeaff..9849f727c90f 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1974,6 +1974,10 @@ pub struct ProfileCreate { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option>, + + /// Indicates if network tokenization before first payment is enabled or not + #[serde(default)] + pub is_tokenize_before_payment_enabled: bool, } #[nutype::nutype( @@ -2090,6 +2094,10 @@ pub struct ProfileCreate { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option>, + + /// Indicates if network tokenization before first payment is enabled or not + #[serde(default)] + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v1")] @@ -2226,6 +2234,9 @@ pub struct ProfileResponse { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option, + + /// Indicates if network tokenization before first payment is enabled or not + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v2")] @@ -2349,6 +2360,9 @@ pub struct ProfileResponse { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option, + + /// Indicates if network tokenization before first payment is enabled or not + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v1")] @@ -2479,6 +2493,10 @@ pub struct ProfileUpdate { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option>, + + /// Indicates if network tokenization before first payment is enabled or not + #[schema(default = false, example = false)] + pub is_tokenize_before_payment_enabled: Option, } #[cfg(feature = "v2")] @@ -2590,6 +2608,10 @@ pub struct ProfileUpdate { /// Product authentication ids #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] pub authentication_product_ids: Option>, + + /// Indicates if network tokenization before first payment is enabled or not + #[schema(default = false, example = false)] + pub is_tokenize_before_payment_enabled: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index dff3f174fc50..f6cbc0d34883 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -59,6 +59,7 @@ pub struct Profile { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v1")] @@ -104,6 +105,7 @@ pub struct ProfileNew { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: Option, } #[cfg(feature = "v1")] @@ -146,6 +148,7 @@ pub struct ProfileUpdateInternal { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: Option, } #[cfg(feature = "v1")] @@ -187,6 +190,7 @@ impl ProfileUpdateInternal { max_auto_retries_enabled, is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled, } = self; Profile { profile_id: source.profile_id, @@ -250,6 +254,8 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids .or(source.authentication_product_ids), + is_tokenize_before_payment_enabled: is_tokenize_before_payment_enabled + .unwrap_or(source.is_tokenize_before_payment_enabled), } } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 61a8a7e19b9e..0608748b3029 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -216,6 +216,7 @@ diesel::table! { max_auto_retries_enabled -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, + is_tokenize_before_payment_enabled -> Bool, } } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 1df474f18d5f..66cbf8dbee3b 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -60,6 +60,7 @@ pub struct Profile { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v1")] @@ -102,6 +103,7 @@ pub struct ProfileSetter { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v1")] @@ -151,6 +153,7 @@ impl From for Profile { max_auto_retries_enabled: value.max_auto_retries_enabled, is_click_to_pay_enabled: value.is_click_to_pay_enabled, authentication_product_ids: value.authentication_product_ids, + is_tokenize_before_payment_enabled: value.is_tokenize_before_payment_enabled, } } } @@ -202,6 +205,7 @@ pub struct ProfileGeneralUpdate { pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: Option, } #[cfg(feature = "v1")] @@ -266,6 +270,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled, is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled, } = *update; Self { @@ -305,6 +310,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled, is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -346,6 +352,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::DynamicRoutingAlgorithmUpdate { dynamic_routing_algorithm, @@ -385,6 +392,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -424,6 +432,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -463,6 +472,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -502,6 +512,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, } } @@ -560,6 +571,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: self.max_auto_retries_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + is_tokenize_before_payment_enabled: self.is_tokenize_before_payment_enabled, }) } @@ -630,6 +642,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: item.max_auto_retries_enabled, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, + is_tokenize_before_payment_enabled: item.is_network_tokenization_enabled, }) } .await @@ -684,6 +697,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: self.max_auto_retries_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + is_tokenize_before_payment_enabled: Some(self.is_tokenize_before_payment_enabled), }) } } @@ -730,6 +744,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v2")] @@ -772,6 +787,7 @@ pub struct ProfileSetter { pub is_network_tokenization_enabled: bool, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub is_tokenize_before_payment_enabled: bool, } #[cfg(feature = "v2")] @@ -821,6 +837,7 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_click_to_pay_enabled: value.is_click_to_pay_enabled, authentication_product_ids: value.authentication_product_ids, + is_tokenize_before_payment_enabled: value.is_tokenize_before_payment_enabled, } } } @@ -973,6 +990,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids, + is_tokenize_before_payment_enabled: None, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -1016,6 +1034,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -1057,6 +1076,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -1098,6 +1118,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::DefaultRoutingFallbackUpdate { default_fallback_routing, @@ -1139,6 +1160,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -1180,6 +1202,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, ProfileUpdate::CollectCvvDuringPaymentUpdate { should_collect_cvv_during_payment, @@ -1221,6 +1244,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + is_tokenize_before_payment_enabled: None, }, } } @@ -1282,6 +1306,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + is_tokenize_before_payment_enabled: self.is_tokenize_before_payment_enabled, }) } @@ -1352,6 +1377,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, + is_tokenize_before_payment_enabled: item.is_tokenize_before_payment_enabled, }) } .await @@ -1409,6 +1435,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + is_tokenize_before_payment_enabled: self.is_tokenize_before_payment_enabled, }) } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 8e6d74c83753..e7f5e947bb84 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3701,6 +3701,7 @@ impl ProfileCreateBridge for api::ProfileCreate { max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled: self.is_tokenize_before_payment_enabled, })) } @@ -4075,6 +4076,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate { max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled: self.is_tokenize_before_payment_enabled, }, ))) } diff --git a/crates/router/src/core/payment_methods/network_tokenization.rs b/crates/router/src/core/payment_methods/network_tokenization.rs index bb730caad2b5..deac9efbcc44 100644 --- a/crates/router/src/core/payment_methods/network_tokenization.rs +++ b/crates/router/src/core/payment_methods/network_tokenization.rs @@ -79,21 +79,22 @@ pub struct GetCardToken { } #[derive(Debug, Deserialize)] pub struct AuthenticationDetails { - cryptogram: Secret, - token: CardNumber, //network token + pub cryptogram: Secret, + pub token: CardNumber, //network token } #[derive(Debug, Serialize, Deserialize)] pub struct TokenDetails { - exp_month: Secret, - exp_year: Secret, + pub exp_month: Secret, + pub exp_year: Secret, } #[derive(Debug, Deserialize)] pub struct TokenResponse { - authentication_details: AuthenticationDetails, - network: api_enums::CardNetwork, - token_details: TokenDetails, + pub authentication_details: AuthenticationDetails, + pub network: api_enums::CardNetwork, + pub token_details: TokenDetails, + pub eci: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 5df436495c9b..994e86389227 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -435,6 +435,15 @@ where } _ => (), }; + let customer_acceptance = payment_data + .get_payment_attempt() + .customer_acceptance + .clone(); + let customer_id = payment_data.get_payment_intent().customer_id.clone(); + let payment_method_data = payment_data.get_payment_method_data(); + let is_pre_tokenization_enabled = business_profile.is_network_tokenization_enabled + && business_profile.is_tokenize_before_payment_enabled + && customer_acceptance.is_some(); payment_data = match connector_details { ConnectorCallType::PreDetermined(connector) => { #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -455,6 +464,62 @@ where } else { None }; + let filtered_nt_supported_connectors = + get_filtered_nt_supported_connectors(&state, [connector.clone()].to_vec()); + + let is_nt_supported_connector_available = + filtered_nt_supported_connectors.first().is_some(); + + if is_pre_tokenization_enabled && is_nt_supported_connector_available { + let pre_tokenization_response = tokenization::pre_payment_tokenization( + state, + customer_id, + payment_method_data, + ) + .await?; + let pm_data = payment_data.get_payment_method_data(); + match pre_tokenization_response { + (Some(token_response), Some(_token_ref)) => { + let token_data = domain::NetworkTokenData { + 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: token_response.eci, + }; + match pm_data { + Some(domain::PaymentMethodData::Card(card_data)) => { + let vault_data = VaultData { + card_data: card_data.clone(), + network_token_data: token_data.clone(), + }; + payment_data.set_vault_operation( + PaymentMethodDataAction::VaultData(vault_data.clone()), + ) + } + _ => (), + } + payment_data.set_payment_method_data(Some( + domain::PaymentMethodData::NetworkToken(token_data), + )); + } + _ => match pm_data { + Some(domain::PaymentMethodData::Card(card_data)) => payment_data + .set_vault_operation(PaymentMethodDataAction::SaveCardData( + card_data.clone(), + )), + _ => (), + }, + } + } let (router_data, mca) = call_connector_service( state, req_state.clone(), @@ -540,10 +605,66 @@ where .map_err(|e| logger::error!(routable_connector_error=?e)) .unwrap_or_default(); + let filtered_nt_supported_connectors = + get_filtered_nt_supported_connectors(&state, connectors.clone()); + let is_nt_supported_connector_available = + filtered_nt_supported_connectors.first().is_some(); + let mut connectors = connectors.into_iter(); let connector_data = get_connector_data(&mut connectors)?; + if is_pre_tokenization_enabled && is_nt_supported_connector_available { + let pre_tokenization_response = tokenization::pre_payment_tokenization( + state, + customer_id, + payment_method_data, + ) + .await?; + let pm_data = payment_data.get_payment_method_data(); + match pre_tokenization_response { + (Some(token_response), Some(_token_ref)) => { + let token_data = domain::NetworkTokenData { + 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: token_response.eci, + }; + match pm_data { + Some(domain::PaymentMethodData::Card(card_data)) => { + let vault_data = VaultData { + card_data: card_data.clone(), + network_token_data: token_data.clone(), + }; + payment_data.set_vault_operation( + PaymentMethodDataAction::VaultData(vault_data.clone()), + ) + } + _ => (), + } + payment_data.set_payment_method_data(Some( + domain::PaymentMethodData::NetworkToken(token_data), + )); + } + _ => match pm_data { + Some(domain::PaymentMethodData::Card(card_data)) => payment_data + .set_vault_operation(PaymentMethodDataAction::SaveCardData( + card_data.clone(), + )), + _ => (), + }, + } + } + let schedule_time = if should_add_task_to_process_tracker { payment_sync::get_sync_process_schedule_time( &*state.store, @@ -4403,8 +4524,10 @@ where } #[derive(Clone, serde::Serialize, Debug)] -pub enum PaymentMethodDataAction{ - VaultData(VaultData) +pub enum PaymentMethodDataAction { + SaveCardData(hyperswitch_domain_models::payment_method_data::Card), + SaveNetworkTokenData(hyperswitch_domain_models::payment_method_data::NetworkTokenData), + VaultData(VaultData), } #[derive(Clone, serde::Serialize, Debug)] @@ -5604,25 +5727,8 @@ where .get_required_value("payment_method_info")? .clone(); - //fetch connectors that support ntid flow - let ntid_supported_connectors = &state - .conf - .network_transaction_id_supported_connectors - .connector_list; - //filered connectors list with ntid_supported_connectors - let filtered_ntid_supported_connectors = - filter_ntid_supported_connectors(connectors.clone(), ntid_supported_connectors); - - //fetch connectors that support network tokenization flow - let network_tokenization_supported_connectors = &state - .conf - .network_tokenization_supported_connectors - .connector_list; - //filered connectors list with ntid_supported_connectors and network_tokenization_supported_connectors - let filtered_nt_supported_connectors = filter_network_tokenization_supported_connectors( - filtered_ntid_supported_connectors, - network_tokenization_supported_connectors, - ); + let filtered_nt_supported_connectors = + get_filtered_nt_supported_connectors(&state, connectors.clone()); let action_type = decide_action_type( state, @@ -5907,6 +6013,31 @@ pub fn filter_network_tokenization_supported_connectors( .collect() } +pub fn get_filtered_nt_supported_connectors( + state: &SessionState, + connectors: Vec, +) -> Vec { + //fetch connectors that support ntid flow + let ntid_supported_connectors = &state + .conf + .network_transaction_id_supported_connectors + .connector_list; + //filered connectors list with ntid_supported_connectors + let filtered_ntid_supported_connectors = + filter_ntid_supported_connectors(connectors.clone(), ntid_supported_connectors); + + //fetch connectors that support network tokenization flow + let network_tokenization_supported_connectors = &state + .conf + .network_tokenization_supported_connectors + .connector_list; + //filered connectors list with ntid_supported_connectors and network_tokenization_supported_connectors + filter_network_tokenization_supported_connectors( + filtered_ntid_supported_connectors, + network_tokenization_supported_connectors, + ) +} + #[cfg(feature = "v1")] pub async fn decide_action_type( state: &SessionState, @@ -6925,6 +7056,7 @@ pub trait OperationSessionSetters { straight_through_algorithm: serde_json::Value, ); fn set_connector_in_payment_attempt(&mut self, connector: Option); + fn set_vault_operation(&mut self, vault_operation: PaymentMethodDataAction); } #[cfg(feature = "v1")] @@ -7173,6 +7305,10 @@ impl OperationSessionSetters for PaymentData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { self.payment_attempt.connector = connector; } + + fn set_vault_operation(&mut self, vault_operation: PaymentMethodDataAction) { + self.vault_operation = Some(vault_operation); + } } #[cfg(feature = "v2")] @@ -7386,6 +7522,10 @@ impl OperationSessionSetters for PaymentIntentData { fn set_connector_in_payment_attempt(&mut self, _connector: Option) { todo!() } + + fn set_vault_operation(&mut self, vault_operation: PaymentMethodDataAction) { + todo!() + } } #[cfg(feature = "v2")] @@ -7599,6 +7739,10 @@ impl OperationSessionSetters for PaymentConfirmData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { self.payment_attempt.connector = connector; } + + fn set_vault_operation(&mut self, vault_operation: PaymentMethodDataAction) { + self.vault_operation = Some(vault_operation); + } } #[cfg(feature = "v2")] @@ -7812,6 +7956,10 @@ impl OperationSessionSetters for PaymentStatusData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { todo!() } + + fn set_vault_operation(&mut self, vault_operation: PaymentMethodDataAction) { + todo!() + } } #[cfg(feature = "v2")] diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index ef1aca0894ed..82da4cead21b 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -195,7 +195,7 @@ impl GetTracker, api::PaymentsCaptureR tax_data: None, session_id: None, service_details: None, - vault_operation:None + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 40527e66aa5b..2fa132ae9430 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -206,7 +206,7 @@ impl GetTracker, api::PaymentsCancelRe tax_data: None, session_id: None, service_details: None, - vault_operation:None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 631069b1f854..767325f225ae 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -255,7 +255,7 @@ impl GetTracker, api::Paymen tax_data: None, session_id: None, service_details: None, - vault_operation: None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs index 8f5f61a39ac3..d7be71213ebc 100644 --- a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs +++ b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs @@ -166,7 +166,7 @@ impl GetTracker, api::PaymentsPostSess tax_data: None, session_id: None, service_details: None, - vault_operation: None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { operation: Box::new(self), diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index b77360494950..66860f8caf76 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -522,7 +522,7 @@ async fn get_tracker_for_sync< tax_data: None, session_id: None, service_details: None, - vault_operation:None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 10bab35107b7..fb8482877f39 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -492,7 +492,7 @@ impl GetTracker, api::PaymentsRequest> tax_data: None, session_id: None, service_details: None, - vault_operation: None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 2c952f0de212..36fab81e7511 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -172,7 +172,7 @@ impl tax_data: None, session_id: None, service_details: None, - vault_operation: None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index 1d78f771feb7..3f1293c89b20 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -180,7 +180,7 @@ impl tax_data: Some(tax_data), session_id: request.session_id.clone(), service_details: None, - vault_operation: None, + vault_operation: None, }; let get_trackers_response = operations::GetTrackerResponse { operation: Box::new(self), diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 94f221dd722f..4fb468522ba8 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -12,7 +12,9 @@ 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}; use masking::{ExposeInterface, Secret}; @@ -773,6 +775,82 @@ where todo!() } +pub async fn pre_payment_tokenization( + state: &SessionState, + customer_id: Option, + payment_method_data: Option<&domain::PaymentMethodData>, +) -> RouterResult<(Option, Option)> { + let customer_id = customer_id.to_owned().get_required_value("customer_id")?; + match payment_method_data { + Some(domain::PaymentMethodData::Card(card)) => { + 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() + { + match network_tokenization::make_card_network_tokenization_request( + state, + card, + &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 + .inspect_err( + |e| logger::error!(error=?e, "Error while fetching token from tokenization service") + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Fetch network token failed") + }, + &metrics::FETCH_NETWORK_TOKEN_TIME, + &[], + ) + .await; + match network_token { + Ok(token_response) => Ok(( + Some(token_response), + network_token_requestor_ref_id.clone(), + )), + _ => Ok((None, None)), + } + } + _ => 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. + } + } + _ => Ok((None, None)), //network_token_resp is None in case of other payment methods + } +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 0cf32b99468b..d4c3877e5d97 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2803,7 +2803,7 @@ impl TryFrom> for types::PaymentsAuthoriz } else { None }; - let payment_method_data = payment_data.payment_method_data.or_else(|| { // + let payment_method_data = payment_data.payment_method_data.or_else(|| { if payment_data.mandate_id.is_some() { Some(domain::PaymentMethodData::MandatePayment) } else { @@ -2837,7 +2837,7 @@ impl TryFrom> for types::PaymentsAuthoriz let shipping_cost = payment_data.payment_intent.shipping_cost; Ok(Self { - payment_method_data: (payment_method_data.get_required_value("payment_method_data")?), // + payment_method_data: (payment_method_data.get_required_value("payment_method_data")?), setup_future_usage: payment_data.payment_intent.setup_future_usage, mandate_id: payment_data.mandate_id.clone(), off_session: payment_data.mandate_id.as_ref().map(|_| true), diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index ff72af484016..6d9eb86a555b 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -178,6 +178,7 @@ impl ForeignTryFrom for ProfileResponse { max_auto_retries_enabled: item.max_auto_retries_enabled, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, + is_tokenize_before_payment_enabled: item.is_tokenize_before_payment_enabled, }) } } @@ -384,5 +385,6 @@ pub async fn create_profile_from_merchant_account( max_auto_retries_enabled: request.max_auto_retries_enabled.map(i16::from), is_click_to_pay_enabled: request.is_click_to_pay_enabled, authentication_product_ids, + is_tokenize_before_payment_enabled: request.is_network_tokenization_enabled, })) } diff --git a/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/down.sql b/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/down.sql new file mode 100644 index 000000000000..56559f452ee4 --- /dev/null +++ b/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile DROP COLUMN IF EXISTS is_tokenize_before_payment_enabled; \ No newline at end of file diff --git a/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/up.sql b/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/up.sql new file mode 100644 index 000000000000..d481781c3a8a --- /dev/null +++ b/migrations/2024-12-02-113838_add_is_tokenize_before_payment_enabled_in_business_profile/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS is_tokenize_before_payment_enabled BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file