diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 59a85841212e..4d5b709bba39 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -244,10 +244,9 @@ impl Connector { | Self::Riskified | Self::Threedsecureio | Self::Netcetera - | Self::Cybersource | Self::Noon | Self::Stripe => false, - Self::Checkout | Self::Nmi => true, + Self::Checkout | Self::Nmi| Self::Cybersource => true, } } } diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index 9b7c4c96ee04..71a16dcb8635 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -42,6 +42,7 @@ pub struct Authentication { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub ds_trans_id: Option, pub directory_server_id: Option, } @@ -87,6 +88,7 @@ pub struct AuthenticationNew { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub ds_trans_id: Option, pub directory_server_id: Option, } @@ -115,6 +117,7 @@ pub enum AuthenticationUpdate { acs_trans_id: Option, acs_signed_content: Option, authentication_status: common_enums::AuthenticationStatus, + ds_trans_id: Option, }, PostAuthenticationUpdate { trans_status: common_enums::TransactionStatus, @@ -162,6 +165,7 @@ pub struct AuthenticationUpdateInternal { pub acs_reference_number: Option, pub acs_trans_id: Option, pub acs_signed_content: Option, + pub ds_trans_id: Option, pub directory_server_id: Option, } @@ -193,6 +197,7 @@ impl Default for AuthenticationUpdateInternal { acs_reference_number: Default::default(), acs_trans_id: Default::default(), acs_signed_content: Default::default(), + ds_trans_id: Default::default(), directory_server_id: Default::default(), } } @@ -226,6 +231,7 @@ impl AuthenticationUpdateInternal { acs_reference_number, acs_trans_id, acs_signed_content, + ds_trans_id, directory_server_id, } = self; Authentication { @@ -258,6 +264,7 @@ impl AuthenticationUpdateInternal { acs_reference_number: acs_reference_number.or(source.acs_reference_number), acs_trans_id: acs_trans_id.or(source.acs_trans_id), acs_signed_content: acs_signed_content.or(source.acs_signed_content), + ds_trans_id: ds_trans_id.or(source.ds_trans_id), directory_server_id: directory_server_id.or(source.directory_server_id), ..source } @@ -336,6 +343,7 @@ impl From for AuthenticationUpdateInternal { acs_trans_id, acs_signed_content, authentication_status, + ds_trans_id, } => Self { cavv: authentication_value, trans_status: Some(trans_status), @@ -346,6 +354,7 @@ impl From for AuthenticationUpdateInternal { acs_trans_id, acs_signed_content, authentication_status: Some(authentication_status), + ds_trans_id, ..Default::default() }, AuthenticationUpdate::PostAuthenticationUpdate { diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index de5b536dcda2..6074fdc10b77 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -115,6 +115,8 @@ diesel::table! { payment_id -> Nullable, #[max_length = 128] merchant_connector_id -> Varchar, + #[max_length = 64] + ds_trans_id -> Nullable, #[max_length = 128] directory_server_id -> Nullable, } diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 56a4ba739c00..c8e68586dade 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1,7 +1,7 @@ pub mod authentication; pub mod fraud_check; use api_models::payments::RequestSurchargeDetails; -use common_utils::{consts, errors, ext_traits::OptionExt, pii}; +use common_utils::{consts, errors, ext_traits::OptionExt, pii, types as common_types}; use diesel_models::enums as storage_enums; use error_stack::ResultExt; use masking::Secret; @@ -447,7 +447,8 @@ pub struct AuthenticationData { pub eci: Option, pub cavv: String, pub threeds_server_transaction_id: String, - pub message_version: String, + pub message_version: common_types::SemanticVersion, + pub ds_trans_id: Option, } #[derive(Debug, Clone)] diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 3d89163b3dad..57d579e1a0a1 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -219,6 +219,7 @@ pub enum AuthenticationResponseData { authn_flow_type: AuthNFlowType, authentication_value: Option, trans_status: common_enums::TransactionStatus, + ds_trans_id: Option, }, PostAuthNResponse { trans_status: common_enums::TransactionStatus, diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index c92d75f1de88..10918b7bc6e6 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -383,7 +383,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme eci: authentication_data.and_then(|auth| auth.eci.clone()), cryptogram: authentication_data.map(|auth| auth.cavv.clone()), xid: authentication_data.map(|auth| auth.threeds_server_transaction_id.clone()), - version: authentication_data.map(|auth| auth.message_version.clone()), + version: authentication_data.map(|auth| auth.message_version.to_string()), }, enums::AuthenticationType::NoThreeDs => CheckoutThreeDS { enabled: false, diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 43e5660d2b90..3cccb8dcf5c9 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -848,6 +848,7 @@ impl ConnectorIntegration, directory_server_transaction_id: Option>, specification_version: Option, + /// This field specifies the 3ds version + pa_specification_version: Option, + /// Verification response enrollment status. + /// + /// This field is supported only on Asia, Middle East, and Africa Gateway. + /// + /// For external authentication, this field will always be "Y" + veres_enrolled: Option, } + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct MerchantDefinedInformation { @@ -655,6 +664,19 @@ impl } else { (None, None, None) }; + // this logic is for external authenticated card + let commerce_indicator_for_external_authentication = item + .router_data + .request + .authentication_data + .as_ref() + .and_then(|authn_data| { + authn_data + .eci + .clone() + .map(|eci| get_commerce_indicator_for_external_authentication(network, eci)) + }); + Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, @@ -665,11 +687,62 @@ impl action_token_types, authorization_options, capture_options: None, - commerce_indicator, + commerce_indicator: commerce_indicator_for_external_authentication + .unwrap_or(commerce_indicator), }) } } +fn get_commerce_indicator_for_external_authentication( + card_network: Option, + eci: String, +) -> String { + let card_network_lower_case = card_network + .as_ref() + .map(|card_network| card_network.to_lowercase()); + match eci.as_str() { + "00" | "01" | "02" => { + if matches!( + card_network_lower_case.as_deref(), + Some("mastercard") | Some("maestro") + ) { + "spa" + } else { + "internet" + } + } + "05" => match card_network_lower_case.as_deref() { + Some("amex") => "aesk", + Some("discover") => "dipb", + Some("mastercard") => "spa", + Some("visa") => "vbv", + Some("diners") => "pb", + Some("upi") => "up3ds", + _ => "internet", + }, + "06" => match card_network_lower_case.as_deref() { + Some("amex") => "aesk_attempted", + Some("discover") => "dipb_attempted", + Some("mastercard") => "spa", + Some("visa") => "vbv_attempted", + Some("diners") => "pb_attempted", + Some("upi") => "up3ds_attempted", + _ => "internet", + }, + "07" => match card_network_lower_case.as_deref() { + Some("amex") => "internet", + Some("discover") => "internet", + Some("mastercard") => "spa", + Some("visa") => "vbv_failure", + Some("diners") => "internet", + Some("upi") => "up3ds_failure", + _ => "internet", + }, + _ => "vbv_failure", + } + .to_string() +} + impl From<( &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, @@ -852,12 +925,39 @@ impl Vec::::foreign_from(metadata.peek().to_owned()) }); + let consumer_authentication_information = item + .router_data + .request + .authentication_data + .as_ref() + .map(|authn_data| { + let (ucaf_authentication_data, cavv) = + if ccard.card_network == Some(common_enums::CardNetwork::Mastercard) { + (Some(Secret::new(authn_data.cavv.clone())), None) + } else { + (None, Some(authn_data.cavv.clone())) + }; + CybersourceConsumerAuthInformation { + ucaf_collection_indicator: None, + cavv, + ucaf_authentication_data, + xid: Some(authn_data.threeds_server_transaction_id.clone()), + directory_server_transaction_id: authn_data + .ds_trans_id + .clone() + .map(Secret::new), + specification_version: None, + pa_specification_version: Some(authn_data.message_version.clone()), + veres_enrolled: Some("Y".to_string()), + } + }); + Ok(Self { processing_information, payment_information, order_information, client_reference_information, - consumer_authentication_information: None, + consumer_authentication_information, merchant_defined_information, }) } @@ -922,6 +1022,8 @@ impl .three_ds_data .directory_server_transaction_id, specification_version: three_ds_info.three_ds_data.specification_version, + pa_specification_version: None, + veres_enrolled: None, }); let merchant_defined_information = @@ -1000,6 +1102,8 @@ impl xid: None, directory_server_transaction_id: None, specification_version: None, + pa_specification_version: None, + veres_enrolled: None, }), merchant_defined_information, }) @@ -1131,6 +1235,8 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> xid: None, directory_server_transaction_id: None, specification_version: None, + pa_specification_version: None, + veres_enrolled: None, }, ), }) diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index fffacc3ea4dd..25ed7c5271e0 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -167,6 +167,7 @@ impl authn_flow_type, authentication_value: response.authentication_value, trans_status: response.trans_status, + ds_trans_id: response.authentication_response.ds_trans_id, }, ) } @@ -646,6 +647,8 @@ pub struct AuthenticationResponse { pub acs_reference_number: Option, #[serde(rename = "acsTransID")] pub acs_trans_id: Option, + #[serde(rename = "dsTransID")] + pub ds_trans_id: Option, pub acs_signed_content: Option, } diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 2a8498bfbadf..44836002024d 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -625,7 +625,7 @@ impl TryFrom<(&domain::payments::Card, &types::PaymentsAuthorizeData)> for Payme cavv: Some(auth_data.cavv.clone()), eci: auth_data.eci.clone(), cardholder_auth: None, - three_ds_version: Some(auth_data.message_version.clone()), + three_ds_version: Some(auth_data.message_version.to_string()), directory_server_id: Some(auth_data.threeds_server_transaction_id.clone().into()), }; diff --git a/crates/router/src/connector/threedsecureio/transformers.rs b/crates/router/src/connector/threedsecureio/transformers.rs index fe26e509c120..242d0dee9468 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -187,6 +187,7 @@ impl types::authentication::AuthNFlowType::Frictionless }, authentication_value: response.authentication_value, + ds_trans_id: Some(response.ds_trans_id), }, ) } diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index 0152fb4321de..f67f66bfffb0 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -84,6 +84,7 @@ pub async fn update_trackers( authn_flow_type, authentication_value, trans_status, + ds_trans_id, } => { let authentication_status = common_enums::AuthenticationStatus::foreign_from(trans_status.clone()); @@ -97,6 +98,7 @@ pub async fn update_trackers( acs_signed_content: authn_flow_type.get_acs_signed_content(), authentication_type: authn_flow_type.get_decoupled_authentication_type(), authentication_status, + ds_trans_id, } } AuthenticationResponseData::PostAuthNResponse { @@ -183,6 +185,7 @@ pub async fn create_new_authentication( profile_id, payment_id, merchant_connector_id, + ds_trans_id: None, directory_server_id: None, }; state diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index d39527b4229a..92c9eec5ff80 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -369,7 +369,8 @@ impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { eci: authentication.eci.clone(), cavv, threeds_server_transaction_id, - message_version: message_version.to_string(), + message_version, + ds_trans_id: authentication.ds_trans_id.clone(), }) } else { Err(errors::ApiErrorResponse::PaymentAuthenticationFailed { data: None }.into()) diff --git a/crates/router/src/db/authentication.rs b/crates/router/src/db/authentication.rs index 4eff3dfdbfde..cd3bb663ec9b 100644 --- a/crates/router/src/db/authentication.rs +++ b/crates/router/src/db/authentication.rs @@ -147,6 +147,7 @@ impl AuthenticationInterface for MockDb { profile_id: authentication.profile_id, payment_id: authentication.payment_id, merchant_connector_id: authentication.merchant_connector_id, + ds_trans_id: authentication.ds_trans_id, directory_server_id: authentication.directory_server_id, }; authentications.push(authentication.clone()); diff --git a/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/down.sql b/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/down.sql new file mode 100644 index 000000000000..146b91185c25 --- /dev/null +++ b/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE authentication DROP COLUMN If EXISTS ds_trans_id; \ No newline at end of file diff --git a/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/up.sql b/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/up.sql new file mode 100644 index 000000000000..b02613777c8e --- /dev/null +++ b/migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE authentication ADD COLUMN IF NOT EXISTS ds_trans_id VARCHAR(64); \ No newline at end of file