From ab9825b1e9496659e55c7414b1ac089d3c97655a Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Mon, 29 Apr 2024 09:25:52 +0530 Subject: [PATCH 1/6] fix(connector): send sdk information in authentication flow netcetera --- crates/diesel_models/src/schema.rs | 3 +- .../connector/netcetera/netcetera_types.rs | 53 ++++++++++++++++--- .../src/connector/netcetera/transformers.rs | 18 +++++-- crates/router/src/connector/utils.rs | 7 +++ .../down.sql | 2 + .../up.sql | 2 + 6 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 migrations/2024-04-28-095920_make_error_message_field_text/down.sql create mode 100644 migrations/2024-04-28-095920_make_error_message_field_text/up.sql diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 44429cc4cad..87f5b65ed6f 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -82,8 +82,7 @@ diesel::table! { authentication_lifecycle_status -> Varchar, created_at -> Timestamp, modified_at -> Timestamp, - #[max_length = 64] - error_message -> Nullable, + error_message -> Nullable, #[max_length = 64] error_code -> Nullable, connector_metadata -> Nullable, diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 3587d911ff4..42c79b5cfd1 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use common_utils::pii::Email; use serde::{Deserialize, Serialize}; -use crate::types::api::MessageCategory; +use crate::{connector::utils::AddressDetailsData, errors, types::api::MessageCategory}; #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] @@ -679,18 +679,19 @@ pub struct Cardholder { } impl - From<( + TryFrom<( api_models::payments::Address, Option, )> for Cardholder { - fn from( + type Error = error_stack::Report; + fn try_from( (billing_address, shipping_address): ( api_models::payments::Address, Option, ), - ) -> Self { - Self { + ) -> Result { + Ok(Self { addr_match: None, bill_addr_city: billing_address .address @@ -716,7 +717,8 @@ impl bill_addr_state: billing_address .address .as_ref() - .and_then(|add| add.state.clone()), + .and_then(|add| add.to_state_code_option().transpose()) + .transpose()?, email: billing_address.email, home_phone: billing_address.phone.clone().map(Into::into), mobile_phone: billing_address.phone.clone().map(Into::into), @@ -749,9 +751,10 @@ impl ship_addr_state: shipping_address .as_ref() .and_then(|shipping_add| shipping_add.address.as_ref()) - .and_then(|add| add.state.clone()), + .and_then(|add| add.to_state_code_option().transpose()) + .transpose()?, tax_id: None, - } + }) } } @@ -1377,6 +1380,7 @@ pub struct Sdk { /// /// Starting from EMV 3DS 2.3.1: /// In case of Browser-SDK, the SDK App ID value is not reliable, and may change for each transaction. + #[serde(rename = "sdkAppID")] sdk_app_id: Option, /// JWE Object as defined Section 6.2.2.1 containing data encrypted by the SDK for the DS to decrypt. This element is @@ -1404,6 +1408,7 @@ pub struct Sdk { /// Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. The field is /// limited to 36 characters and it shall be in a canonical format as defined in IETF RFC 4122. This may utilize any of /// the specified versions as long as the output meets specific requirements. + #[serde(rename = "sdkTransID")] sdk_trans_id: Option, /// Contains the JWS object(represented as a string) created by the Split-SDK Server for the AReq message. A @@ -1587,3 +1592,35 @@ pub enum ThreeDSReqAuthMethod { #[serde(untagged)] PsSpecificValue(String), } + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DeviceRenderingOptionsSupported { + pub sdk_interface: SdkInterface, + /// For Native UI SDK Interface accepted values are 01-04 and for HTML UI accepted values are 01-05. + pub sdk_ui_type: SdkUiType, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SdkInterface { + #[serde(rename = "01")] + Native, + #[serde(rename = "02")] + Html, + #[serde(rename = "03")] + Both, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SdkUiType { + #[serde(rename = "01")] + Text, + #[serde(rename = "02")] + SingleSelect, + #[serde(rename = "03")] + MultiSelect, + #[serde(rename = "04")] + Oob, + #[serde(rename = "05")] + HtmlOther, +} diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index 6123b891afb..24da20a79af 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -430,7 +430,7 @@ pub struct NetceteraAuthenticationRequest { pub acquirer: Option, pub merchant: Option, pub broad_info: Option, - pub device_render_options: Option, + pub device_render_options: Option, pub message_extension: Option>, pub challenge_message_extension: Option>, pub browser_information: Option, @@ -552,6 +552,16 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio }; let browser_information = request.browser_details.map(netcetera_types::Browser::from); let sdk_information = request.sdk_information.map(netcetera_types::Sdk::from); + let device_render_options = match request.device_channel { + api_models::payments::DeviceChannel::App => { + Some(netcetera_types::DeviceRenderingOptionsSupported { + // hard-coded until core provides these values. + sdk_interface: netcetera_types::SdkInterface::Native, + sdk_ui_type: netcetera_types::SdkUiType::Text, + }) + } + api_models::payments::DeviceChannel::Browser => None, + }; Ok(Self { preferred_protocol_version: Some(pre_authn_data.message_version), enforce_preferred_protocol_version: None, @@ -567,15 +577,15 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio three_ds_server_trans_id: pre_authn_data.threeds_server_transaction_id, three_ds_requestor_url: Some(request.three_ds_requestor_url), cardholder_account, - cardholder: Some(netcetera_types::Cardholder::from(( + cardholder: Some(netcetera_types::Cardholder::try_from(( request.billing_address, request.shipping_address, - ))), + ))?), purchase: Some(purchase), acquirer: Some(acquirer_details), merchant: Some(merchant_data), broad_info: None, - device_render_options: None, + device_render_options, message_extension: None, challenge_message_extension: None, browser_information, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 6d427c54198..8d5c2f2067b 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1228,6 +1228,7 @@ pub trait AddressDetailsData { fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>; fn get_combined_address_line(&self) -> Result, Error>; fn to_state_code(&self) -> Result, Error>; + fn to_state_code_option(&self) -> Result>, Error>; } impl AddressDetailsData for api::AddressDetails { @@ -1311,6 +1312,12 @@ impl AddressDetailsData for api::AddressDetails { _ => Ok(state.clone()), } } + fn to_state_code_option(&self) -> Result>, Error> { + self.state + .as_ref() + .map(|_| self.to_state_code()) + .transpose() + } } pub trait BankRedirectBillingData { diff --git a/migrations/2024-04-28-095920_make_error_message_field_text/down.sql b/migrations/2024-04-28-095920_make_error_message_field_text/down.sql new file mode 100644 index 00000000000..683d9660a52 --- /dev/null +++ b/migrations/2024-04-28-095920_make_error_message_field_text/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE authentication ALTER COLUMN error_message VARCHAR(64); \ No newline at end of file diff --git a/migrations/2024-04-28-095920_make_error_message_field_text/up.sql b/migrations/2024-04-28-095920_make_error_message_field_text/up.sql new file mode 100644 index 00000000000..55dfea3fb4f --- /dev/null +++ b/migrations/2024-04-28-095920_make_error_message_field_text/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE authentication ALTER COLUMN error_message TYPE TEXT; \ No newline at end of file From 40c2098c28a1b3605f64010f0fe499f216ba85ad Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Mon, 29 Apr 2024 11:38:12 +0530 Subject: [PATCH 2/6] fix: send country_code after trimming + prefix --- .../connector/netcetera/netcetera_types.rs | 35 ++++++++++++++----- crates/router/src/connector/utils.rs | 5 +++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 42c79b5cfd1..0ab976e7812 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -3,7 +3,11 @@ use std::collections::HashMap; use common_utils::pii::Email; use serde::{Deserialize, Serialize}; -use crate::{connector::utils::AddressDetailsData, errors, types::api::MessageCategory}; +use crate::{ + connector::utils::{AddressDetailsData, PhoneDetailsData}, + errors, + types::api::MessageCategory, +}; #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] @@ -720,9 +724,21 @@ impl .and_then(|add| add.to_state_code_option().transpose()) .transpose()?, email: billing_address.email, - home_phone: billing_address.phone.clone().map(Into::into), - mobile_phone: billing_address.phone.clone().map(Into::into), - work_phone: billing_address.phone.clone().map(Into::into), + home_phone: billing_address + .phone + .clone() + .map(TryInto::try_into) + .transpose()?, + mobile_phone: billing_address + .phone + .clone() + .map(TryInto::try_into) + .transpose()?, + work_phone: billing_address + .phone + .clone() + .map(TryInto::try_into) + .transpose()?, cardholder_name: billing_address .address .as_ref() @@ -767,12 +783,13 @@ pub struct PhoneNumber { subscriber: Option>, } -impl From for PhoneNumber { - fn from(value: api_models::payments::PhoneDetails) -> Self { - Self { - country_code: value.country_code, +impl TryFrom for PhoneNumber { + type Error = error_stack::Report; + fn try_from(value: api_models::payments::PhoneDetails) -> Result { + Ok(Self { + country_code: Some(value.get_country_code_without_plus()?), subscriber: value.number, - } + }) } } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 8d5c2f2067b..e5e418c6616 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1186,6 +1186,7 @@ pub trait PhoneDetailsData { fn get_country_code(&self) -> Result; fn get_number_with_country_code(&self) -> Result, Error>; fn get_number_with_hash_country_code(&self) -> Result, Error>; + fn get_country_code_without_plus(&self) -> Result; } impl PhoneDetailsData for api::PhoneDetails { @@ -1194,6 +1195,10 @@ impl PhoneDetailsData for api::PhoneDetails { .clone() .ok_or_else(missing_field_err("billing.phone.country_code")) } + fn get_country_code_without_plus(&self) -> Result { + self.get_country_code() + .map(|cc| cc.trim_start_matches('+').to_string()) + } fn get_number(&self) -> Result, Error> { self.number .clone() From b3b6fbce0b4b17155916517d4033011869bda8bf Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Mon, 29 Apr 2024 11:40:11 +0530 Subject: [PATCH 3/6] fix: down.sql query --- .../2024-04-28-095920_make_error_message_field_text/down.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/2024-04-28-095920_make_error_message_field_text/down.sql b/migrations/2024-04-28-095920_make_error_message_field_text/down.sql index 683d9660a52..89f4120d005 100644 --- a/migrations/2024-04-28-095920_make_error_message_field_text/down.sql +++ b/migrations/2024-04-28-095920_make_error_message_field_text/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -ALTER TABLE authentication ALTER COLUMN error_message VARCHAR(64); \ No newline at end of file +ALTER TABLE authentication ALTER COLUMN error_message TYPE VARCHAR(64); \ No newline at end of file From 23b03479be11bbfe029d5a72685d587020ac3cfd Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Mon, 29 Apr 2024 19:01:53 +0530 Subject: [PATCH 4/6] fix: fix app based authentication flow --- .../src/connector/netcetera/netcetera_types.rs | 2 +- .../src/connector/netcetera/transformers.rs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 0ab976e7812..adb71515635 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1615,7 +1615,7 @@ pub enum ThreeDSReqAuthMethod { pub struct DeviceRenderingOptionsSupported { pub sdk_interface: SdkInterface, /// For Native UI SDK Interface accepted values are 01-04 and for HTML UI accepted values are 01-05. - pub sdk_ui_type: SdkUiType, + pub sdk_ui_type: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index 24da20a79af..7c95578bfd6 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -154,7 +154,9 @@ impl .acs_reference_number, acs_trans_id: response.authentication_response.acs_trans_id, three_dsserver_trans_id: Some(response.three_ds_server_trans_id), - acs_signed_content: None, + acs_signed_content: response + .authentication_response + .acs_signed_content, }, )) } @@ -556,8 +558,14 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio api_models::payments::DeviceChannel::App => { Some(netcetera_types::DeviceRenderingOptionsSupported { // hard-coded until core provides these values. - sdk_interface: netcetera_types::SdkInterface::Native, - sdk_ui_type: netcetera_types::SdkUiType::Text, + sdk_interface: netcetera_types::SdkInterface::Both, + sdk_ui_type: vec![ + netcetera_types::SdkUiType::Text, + netcetera_types::SdkUiType::SingleSelect, + netcetera_types::SdkUiType::MultiSelect, + netcetera_types::SdkUiType::Oob, + netcetera_types::SdkUiType::HtmlOther, + ], }) } api_models::payments::DeviceChannel::Browser => None, @@ -635,6 +643,7 @@ pub struct AuthenticationResponse { pub acs_reference_number: Option, #[serde(rename = "acsTransID")] pub acs_trans_id: Option, + pub acs_signed_content: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] From 4ead2acf5017d16fd581ec9dfc677eddc941043a Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Tue, 30 Apr 2024 09:32:34 +0530 Subject: [PATCH 5/6] address comments --- .../src/connector/netcetera/netcetera_types.rs | 12 ++++++------ crates/router/src/connector/utils.rs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index adb71515635..89fa6d9d50c 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -721,23 +721,23 @@ impl bill_addr_state: billing_address .address .as_ref() - .and_then(|add| add.to_state_code_option().transpose()) + .and_then(|add| add.to_state_code_as_optional().transpose()) .transpose()?, email: billing_address.email, home_phone: billing_address .phone .clone() - .map(TryInto::try_into) + .map(PhoneNumber::try_from) .transpose()?, mobile_phone: billing_address .phone .clone() - .map(TryInto::try_into) + .map(PhoneNumber::try_from) .transpose()?, work_phone: billing_address .phone .clone() - .map(TryInto::try_into) + .map(PhoneNumber::try_from) .transpose()?, cardholder_name: billing_address .address @@ -767,7 +767,7 @@ impl ship_addr_state: shipping_address .as_ref() .and_then(|shipping_add| shipping_add.address.as_ref()) - .and_then(|add| add.to_state_code_option().transpose()) + .and_then(|add| add.to_state_code_as_optional().transpose()) .transpose()?, tax_id: None, }) @@ -787,7 +787,7 @@ impl TryFrom for PhoneNumber { type Error = error_stack::Report; fn try_from(value: api_models::payments::PhoneDetails) -> Result { Ok(Self { - country_code: Some(value.get_country_code_without_plus()?), + country_code: Some(value.extract_country_code()?), subscriber: value.number, }) } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index e5e418c6616..cf2eb6b18de 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1186,7 +1186,7 @@ pub trait PhoneDetailsData { fn get_country_code(&self) -> Result; fn get_number_with_country_code(&self) -> Result, Error>; fn get_number_with_hash_country_code(&self) -> Result, Error>; - fn get_country_code_without_plus(&self) -> Result; + fn extract_country_code(&self) -> Result; } impl PhoneDetailsData for api::PhoneDetails { @@ -1195,7 +1195,7 @@ impl PhoneDetailsData for api::PhoneDetails { .clone() .ok_or_else(missing_field_err("billing.phone.country_code")) } - fn get_country_code_without_plus(&self) -> Result { + fn extract_country_code(&self) -> Result { self.get_country_code() .map(|cc| cc.trim_start_matches('+').to_string()) } @@ -1233,7 +1233,7 @@ pub trait AddressDetailsData { fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>; fn get_combined_address_line(&self) -> Result, Error>; fn to_state_code(&self) -> Result, Error>; - fn to_state_code_option(&self) -> Result>, Error>; + fn to_state_code_as_optional(&self) -> Result>, Error>; } impl AddressDetailsData for api::AddressDetails { @@ -1317,7 +1317,7 @@ impl AddressDetailsData for api::AddressDetails { _ => Ok(state.clone()), } } - fn to_state_code_option(&self) -> Result>, Error> { + fn to_state_code_as_optional(&self) -> Result>, Error> { self.state .as_ref() .map(|_| self.to_state_code()) From fa42b0be2cb4ba0df82573cd676142e072f8a1c6 Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Tue, 30 Apr 2024 12:51:00 +0530 Subject: [PATCH 6/6] fix: do state to code conversion only if state is sent as full string --- crates/router/src/connector/utils.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index cf2eb6b18de..2a121abc533 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1320,7 +1320,13 @@ impl AddressDetailsData for api::AddressDetails { fn to_state_code_as_optional(&self) -> Result>, Error> { self.state .as_ref() - .map(|_| self.to_state_code()) + .map(|state| { + if state.peek().len() == 2 { + Ok(state.to_owned()) + } else { + self.to_state_code() + } + }) .transpose() } }