From 05db98ae6ddf8b6e71a6d4e56d993f3b3041b750 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Thu, 14 Mar 2024 23:51:14 +0530 Subject: [PATCH 1/5] feat(connector): add support for external authentication for cybersource --- crates/api_models/src/enums.rs | 3 +- crates/diesel_models/src/authentication.rs | 9 ++ crates/diesel_models/src/schema.rs | 1 + .../src/connector/checkout/transformers.rs | 2 +- crates/router/src/connector/cybersource.rs | 3 + .../src/connector/cybersource/transformers.rs | 89 ++++++++++++++++++- .../src/connector/netcetera/transformers.rs | 3 + .../router/src/connector/nmi/transformers.rs | 2 +- .../connector/threedsecureio/transformers.rs | 1 + .../router/src/core/authentication/utils.rs | 3 + crates/router/src/core/payments/types.rs | 6 +- crates/router/src/db/authentication.rs | 1 + crates/router/src/types/authentication.rs | 1 + .../down.sql | 2 + .../up.sql | 2 + 15 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/down.sql create mode 100644 migrations/2024-05-21-065403_add_ds_trans_id_to_authentication_table/up.sql diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 866db8cc18db..57d4a39f59c9 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 ce2f02d087fe..b5e04f19b939 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, } impl Authentication { @@ -86,6 +87,7 @@ pub struct AuthenticationNew { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub ds_trans_id: Option, } #[derive(Debug)] @@ -112,6 +114,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, @@ -159,6 +162,7 @@ pub struct AuthenticationUpdateInternal { pub acs_reference_number: Option, pub acs_trans_id: Option, pub acs_signed_content: Option, + pub ds_trans_id: Option, } impl Default for AuthenticationUpdateInternal { @@ -189,6 +193,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(), } } } @@ -221,6 +226,7 @@ impl AuthenticationUpdateInternal { acs_reference_number, acs_trans_id, acs_signed_content, + ds_trans_id, } = self; Authentication { connector_authentication_id: connector_authentication_id @@ -252,6 +258,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), ..source } } @@ -327,6 +334,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), @@ -337,6 +345,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 813fdd1f127b..33b0c3f0043d 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -115,6 +115,7 @@ diesel::table! { payment_id -> Nullable, #[max_length = 128] merchant_connector_id -> Varchar, + ds_trans_id -> Nullable, } } diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index ddf65d23aa41..ee4debb5d2c7 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)] +pub enum PaSpecificationVersion { + #[serde(rename = "1")] + ThreeDs1, + #[serde(rename = "2")] + ThreeDs2, +} + +impl From for PaSpecificationVersion { + fn from(value: SemanticVersion) -> Self { + if value.get_major() == 2 { + Self::ThreeDs2 + } else { + Self::ThreeDs1 + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct MerchantDefinedInformation { @@ -655,6 +682,25 @@ 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| { + dbg!(&authn_data); + authn_data.eci.as_ref().map(|eci| { + match eci.as_str() { + "05" => "vbv", + "06" => "vbv_attempted", + "07" => "vbv_failure", + _ => "vbv_failure", + } + .to_string() + }) + }); + Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, @@ -665,7 +711,8 @@ impl action_token_types, authorization_options, capture_options: None, - commerce_indicator, + commerce_indicator: commerce_indicator_for_external_authentication + .unwrap_or(commerce_indicator), }) } } @@ -852,12 +899,42 @@ 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()), + // xid: None, + directory_server_transaction_id: authn_data + .ds_trans_id + .clone() + .map(Secret::new), + specification_version: None, + pa_specification_version: Some(PaSpecificationVersion::from( + 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 +999,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 +1079,8 @@ impl xid: None, directory_server_transaction_id: None, specification_version: None, + pa_specification_version: None, + veres_enrolled: None, }), merchant_defined_information, }) @@ -1131,6 +1212,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 90cf465e8205..fdd917a2f468 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -164,6 +164,7 @@ impl authn_flow_type, authentication_value: response.authentication_value, trans_status: response.trans_status, + ds_trans_id: response.authentication_response.ds_trans_id, }, ) } @@ -643,6 +644,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 fdc65963fe8c..233606463630 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -623,7 +623,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 d6a77fef31eb..38ae93699bed 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -186,6 +186,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 e29c8b10232e..7041b57f5479 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -82,6 +82,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()); @@ -95,6 +96,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 { @@ -181,6 +183,7 @@ pub async fn create_new_authentication( profile_id, payment_id, merchant_connector_id, + ds_trans_id: None, }; state .store diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index b297470bce84..3223858a2009 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -380,7 +380,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, } impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { @@ -411,7 +412,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 398af72f8bd8..c1ff55f5e867 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, }; authentications.push(authentication.clone()); Ok(authentication) diff --git a/crates/router/src/types/authentication.rs b/crates/router/src/types/authentication.rs index 7ea937b9ffd8..434381bf049d 100644 --- a/crates/router/src/types/authentication.rs +++ b/crates/router/src/types/authentication.rs @@ -23,6 +23,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/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..162f034d6a7a --- /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 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..eed1264bdd31 --- /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 ds_trans_id VARCHAR; \ No newline at end of file From 71a5aa548fcaa47cbd5eb8a5df79373f614542ad Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 06:50:18 +0000 Subject: [PATCH 2/5] chore: update Cargo.lock --- Cargo.lock | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 89a0d9d50792..9411c009aef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3667,18 +3667,25 @@ dependencies = [ name = "hyperswitch_domain_models" version = "0.1.0" dependencies = [ + "actix-web", "api_models", "async-trait", + "cards", "common_enums", "common_utils", "diesel_models", "error-stack", "http 0.2.12", "masking", + "mime", + "router_derive", "serde", "serde_json", + "serde_with", "thiserror", "time", + "url", + "utoipa", ] [[package]] @@ -6051,6 +6058,7 @@ dependencies = [ "error-stack", "external_services", "futures 0.3.30", + "hyperswitch_domain_models", "masking", "num_cpus", "once_cell", From cb91da1200f78111ed60bc9015d5fae6d6acbc78 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Tue, 28 May 2024 22:30:56 +0530 Subject: [PATCH 3/5] address review comments --- .../src/connector/cybersource/transformers.rs | 69 +++++++++++-------- .../down.sql | 2 +- .../up.sql | 2 +- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 2a279b605bc2..ff64b51962aa 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -262,7 +262,7 @@ pub struct CybersourceConsumerAuthInformation { directory_server_transaction_id: Option>, specification_version: Option, /// This field specifies the 3ds version - pa_specification_version: Option, + pa_specification_version: Option, /// Verification response enrollment status. /// /// This field is supported only on Asia, Middle East, and Africa Gateway. @@ -271,24 +271,6 @@ pub struct CybersourceConsumerAuthInformation { veres_enrolled: Option, } -#[derive(Debug, Serialize)] -pub enum PaSpecificationVersion { - #[serde(rename = "1")] - ThreeDs1, - #[serde(rename = "2")] - ThreeDs2, -} - -impl From for PaSpecificationVersion { - fn from(value: SemanticVersion) -> Self { - if value.get_major() == 2 { - Self::ThreeDs2 - } else { - Self::ThreeDs1 - } - } -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct MerchantDefinedInformation { @@ -683,18 +665,54 @@ impl (None, None, None) }; // this logic is for external authenticated card + let card_network_lower_case = network + .as_ref() + .map(|card_network| card_network.to_lowercase()); let commerce_indicator_for_external_authentication = item .router_data .request .authentication_data .as_ref() .and_then(|authn_data| { - dbg!(&authn_data); authn_data.eci.as_ref().map(|eci| { match eci.as_str() { - "05" => "vbv", - "06" => "vbv_attempted", - "07" => "vbv_failure", + "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() @@ -916,15 +934,12 @@ impl cavv, ucaf_authentication_data, xid: Some(authn_data.threeds_server_transaction_id.clone()), - // xid: None, directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(PaSpecificationVersion::from( - authn_data.message_version.clone(), - )), + pa_specification_version: Some(authn_data.message_version.clone()), veres_enrolled: Some("Y".to_string()), } }); 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 index 162f034d6a7a..146b91185c25 100644 --- 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 @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -ALTER TABLE authentication DROP COLUMN ds_trans_id; \ No newline at end of file +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 index eed1264bdd31..b02613777c8e 100644 --- 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 @@ -1,2 +1,2 @@ -- Your SQL goes here -ALTER TABLE authentication ADD COLUMN ds_trans_id VARCHAR; \ No newline at end of file +ALTER TABLE authentication ADD COLUMN IF NOT EXISTS ds_trans_id VARCHAR(64); \ No newline at end of file From 7bb4499b378e9879edd22ad3c65580a8500cd638 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Wed, 29 May 2024 10:29:13 +0530 Subject: [PATCH 4/5] run diesel migration --- crates/diesel_models/src/schema.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 2e62c67bcd1d..6074fdc10b77 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -115,6 +115,7 @@ 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, From e1a99beff27ae4940347f5cbb2aa0eb16d16bbea Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Wed, 29 May 2024 13:05:30 +0530 Subject: [PATCH 5/5] refactor: move match logic to a function --- .../src/connector/cybersource/transformers.rs | 100 ++++++++++-------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index ff64b51962aa..4527ea3950a7 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -665,58 +665,16 @@ impl (None, None, None) }; // this logic is for external authenticated card - let card_network_lower_case = network - .as_ref() - .map(|card_network| card_network.to_lowercase()); let commerce_indicator_for_external_authentication = item .router_data .request .authentication_data .as_ref() .and_then(|authn_data| { - authn_data.eci.as_ref().map(|eci| { - 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() - }) + authn_data + .eci + .clone() + .map(|eci| get_commerce_indicator_for_external_authentication(network, eci)) }); Ok(Self { @@ -735,6 +693,56 @@ impl } } +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>,