Skip to content

Commit 8f0d4d4

Browse files
fix(connector): send valid sdk information in authentication flow netcetera (#4474)
1 parent dfa4b50 commit 8f0d4d4

File tree

6 files changed

+117
-23
lines changed

6 files changed

+117
-23
lines changed

crates/diesel_models/src/schema.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ diesel::table! {
8282
authentication_lifecycle_status -> Varchar,
8383
created_at -> Timestamp,
8484
modified_at -> Timestamp,
85-
#[max_length = 64]
86-
error_message -> Nullable<Varchar>,
85+
error_message -> Nullable<Text>,
8786
#[max_length = 64]
8887
error_code -> Nullable<Varchar>,
8988
connector_metadata -> Nullable<Jsonb>,

crates/router/src/connector/netcetera/netcetera_types.rs

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ use std::collections::HashMap;
33
use common_utils::pii::Email;
44
use serde::{Deserialize, Serialize};
55

6-
use crate::types::api::MessageCategory;
6+
use crate::{
7+
connector::utils::{AddressDetailsData, PhoneDetailsData},
8+
errors,
9+
types::api::MessageCategory,
10+
};
711

812
#[derive(Debug, Deserialize, Serialize, Clone)]
913
#[serde(untagged)]
@@ -679,18 +683,19 @@ pub struct Cardholder {
679683
}
680684

681685
impl
682-
From<(
686+
TryFrom<(
683687
api_models::payments::Address,
684688
Option<api_models::payments::Address>,
685689
)> for Cardholder
686690
{
687-
fn from(
691+
type Error = error_stack::Report<errors::ConnectorError>;
692+
fn try_from(
688693
(billing_address, shipping_address): (
689694
api_models::payments::Address,
690695
Option<api_models::payments::Address>,
691696
),
692-
) -> Self {
693-
Self {
697+
) -> Result<Self, Self::Error> {
698+
Ok(Self {
694699
addr_match: None,
695700
bill_addr_city: billing_address
696701
.address
@@ -716,11 +721,24 @@ impl
716721
bill_addr_state: billing_address
717722
.address
718723
.as_ref()
719-
.and_then(|add| add.state.clone()),
724+
.and_then(|add| add.to_state_code_as_optional().transpose())
725+
.transpose()?,
720726
email: billing_address.email,
721-
home_phone: billing_address.phone.clone().map(Into::into),
722-
mobile_phone: billing_address.phone.clone().map(Into::into),
723-
work_phone: billing_address.phone.clone().map(Into::into),
727+
home_phone: billing_address
728+
.phone
729+
.clone()
730+
.map(PhoneNumber::try_from)
731+
.transpose()?,
732+
mobile_phone: billing_address
733+
.phone
734+
.clone()
735+
.map(PhoneNumber::try_from)
736+
.transpose()?,
737+
work_phone: billing_address
738+
.phone
739+
.clone()
740+
.map(PhoneNumber::try_from)
741+
.transpose()?,
724742
cardholder_name: billing_address
725743
.address
726744
.as_ref()
@@ -749,9 +767,10 @@ impl
749767
ship_addr_state: shipping_address
750768
.as_ref()
751769
.and_then(|shipping_add| shipping_add.address.as_ref())
752-
.and_then(|add| add.state.clone()),
770+
.and_then(|add| add.to_state_code_as_optional().transpose())
771+
.transpose()?,
753772
tax_id: None,
754-
}
773+
})
755774
}
756775
}
757776

@@ -764,12 +783,13 @@ pub struct PhoneNumber {
764783
subscriber: Option<masking::Secret<String>>,
765784
}
766785

767-
impl From<api_models::payments::PhoneDetails> for PhoneNumber {
768-
fn from(value: api_models::payments::PhoneDetails) -> Self {
769-
Self {
770-
country_code: value.country_code,
786+
impl TryFrom<api_models::payments::PhoneDetails> for PhoneNumber {
787+
type Error = error_stack::Report<errors::ConnectorError>;
788+
fn try_from(value: api_models::payments::PhoneDetails) -> Result<Self, Self::Error> {
789+
Ok(Self {
790+
country_code: Some(value.extract_country_code()?),
771791
subscriber: value.number,
772-
}
792+
})
773793
}
774794
}
775795

@@ -1377,6 +1397,7 @@ pub struct Sdk {
13771397
///
13781398
/// Starting from EMV 3DS 2.3.1:
13791399
/// In case of Browser-SDK, the SDK App ID value is not reliable, and may change for each transaction.
1400+
#[serde(rename = "sdkAppID")]
13801401
sdk_app_id: Option<String>,
13811402

13821403
/// 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 +1425,7 @@ pub struct Sdk {
14041425
/// Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. The field is
14051426
/// limited to 36 characters and it shall be in a canonical format as defined in IETF RFC 4122. This may utilize any of
14061427
/// the specified versions as long as the output meets specific requirements.
1428+
#[serde(rename = "sdkTransID")]
14071429
sdk_trans_id: Option<String>,
14081430

14091431
/// Contains the JWS object(represented as a string) created by the Split-SDK Server for the AReq message. A
@@ -1587,3 +1609,35 @@ pub enum ThreeDSReqAuthMethod {
15871609
#[serde(untagged)]
15881610
PsSpecificValue(String),
15891611
}
1612+
1613+
#[derive(Serialize, Deserialize, Debug, Clone)]
1614+
#[serde(rename_all = "camelCase")]
1615+
pub struct DeviceRenderingOptionsSupported {
1616+
pub sdk_interface: SdkInterface,
1617+
/// For Native UI SDK Interface accepted values are 01-04 and for HTML UI accepted values are 01-05.
1618+
pub sdk_ui_type: Vec<SdkUiType>,
1619+
}
1620+
1621+
#[derive(Serialize, Deserialize, Debug, Clone)]
1622+
pub enum SdkInterface {
1623+
#[serde(rename = "01")]
1624+
Native,
1625+
#[serde(rename = "02")]
1626+
Html,
1627+
#[serde(rename = "03")]
1628+
Both,
1629+
}
1630+
1631+
#[derive(Serialize, Deserialize, Debug, Clone)]
1632+
pub enum SdkUiType {
1633+
#[serde(rename = "01")]
1634+
Text,
1635+
#[serde(rename = "02")]
1636+
SingleSelect,
1637+
#[serde(rename = "03")]
1638+
MultiSelect,
1639+
#[serde(rename = "04")]
1640+
Oob,
1641+
#[serde(rename = "05")]
1642+
HtmlOther,
1643+
}

crates/router/src/connector/netcetera/transformers.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ impl
154154
.acs_reference_number,
155155
acs_trans_id: response.authentication_response.acs_trans_id,
156156
three_dsserver_trans_id: Some(response.three_ds_server_trans_id),
157-
acs_signed_content: None,
157+
acs_signed_content: response
158+
.authentication_response
159+
.acs_signed_content,
158160
},
159161
))
160162
}
@@ -430,7 +432,7 @@ pub struct NetceteraAuthenticationRequest {
430432
pub acquirer: Option<netcetera_types::AcquirerData>,
431433
pub merchant: Option<netcetera_types::MerchantData>,
432434
pub broad_info: Option<String>,
433-
pub device_render_options: Option<String>,
435+
pub device_render_options: Option<netcetera_types::DeviceRenderingOptionsSupported>,
434436
pub message_extension: Option<Vec<netcetera_types::MessageExtensionAttribute>>,
435437
pub challenge_message_extension: Option<Vec<netcetera_types::MessageExtensionAttribute>>,
436438
pub browser_information: Option<netcetera_types::Browser>,
@@ -552,6 +554,22 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio
552554
};
553555
let browser_information = request.browser_details.map(netcetera_types::Browser::from);
554556
let sdk_information = request.sdk_information.map(netcetera_types::Sdk::from);
557+
let device_render_options = match request.device_channel {
558+
api_models::payments::DeviceChannel::App => {
559+
Some(netcetera_types::DeviceRenderingOptionsSupported {
560+
// hard-coded until core provides these values.
561+
sdk_interface: netcetera_types::SdkInterface::Both,
562+
sdk_ui_type: vec![
563+
netcetera_types::SdkUiType::Text,
564+
netcetera_types::SdkUiType::SingleSelect,
565+
netcetera_types::SdkUiType::MultiSelect,
566+
netcetera_types::SdkUiType::Oob,
567+
netcetera_types::SdkUiType::HtmlOther,
568+
],
569+
})
570+
}
571+
api_models::payments::DeviceChannel::Browser => None,
572+
};
555573
Ok(Self {
556574
preferred_protocol_version: Some(pre_authn_data.message_version),
557575
enforce_preferred_protocol_version: None,
@@ -567,15 +585,15 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio
567585
three_ds_server_trans_id: pre_authn_data.threeds_server_transaction_id,
568586
three_ds_requestor_url: Some(request.three_ds_requestor_url),
569587
cardholder_account,
570-
cardholder: Some(netcetera_types::Cardholder::from((
588+
cardholder: Some(netcetera_types::Cardholder::try_from((
571589
request.billing_address,
572590
request.shipping_address,
573-
))),
591+
))?),
574592
purchase: Some(purchase),
575593
acquirer: Some(acquirer_details),
576594
merchant: Some(merchant_data),
577595
broad_info: None,
578-
device_render_options: None,
596+
device_render_options,
579597
message_extension: None,
580598
challenge_message_extension: None,
581599
browser_information,
@@ -625,6 +643,7 @@ pub struct AuthenticationResponse {
625643
pub acs_reference_number: Option<String>,
626644
#[serde(rename = "acsTransID")]
627645
pub acs_trans_id: Option<String>,
646+
pub acs_signed_content: Option<String>,
628647
}
629648

630649
#[derive(Debug, Deserialize, Serialize, Clone)]

crates/router/src/connector/utils.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,7 @@ pub trait PhoneDetailsData {
12161216
fn get_country_code(&self) -> Result<String, Error>;
12171217
fn get_number_with_country_code(&self) -> Result<Secret<String>, Error>;
12181218
fn get_number_with_hash_country_code(&self) -> Result<Secret<String>, Error>;
1219+
fn extract_country_code(&self) -> Result<String, Error>;
12191220
}
12201221

12211222
impl PhoneDetailsData for api::PhoneDetails {
@@ -1224,6 +1225,10 @@ impl PhoneDetailsData for api::PhoneDetails {
12241225
.clone()
12251226
.ok_or_else(missing_field_err("billing.phone.country_code"))
12261227
}
1228+
fn extract_country_code(&self) -> Result<String, Error> {
1229+
self.get_country_code()
1230+
.map(|cc| cc.trim_start_matches('+').to_string())
1231+
}
12271232
fn get_number(&self) -> Result<Secret<String>, Error> {
12281233
self.number
12291234
.clone()
@@ -1258,6 +1263,7 @@ pub trait AddressDetailsData {
12581263
fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>;
12591264
fn get_combined_address_line(&self) -> Result<Secret<String>, Error>;
12601265
fn to_state_code(&self) -> Result<Secret<String>, Error>;
1266+
fn to_state_code_as_optional(&self) -> Result<Option<Secret<String>>, Error>;
12611267
}
12621268

12631269
impl AddressDetailsData for api::AddressDetails {
@@ -1341,6 +1347,18 @@ impl AddressDetailsData for api::AddressDetails {
13411347
_ => Ok(state.clone()),
13421348
}
13431349
}
1350+
fn to_state_code_as_optional(&self) -> Result<Option<Secret<String>>, Error> {
1351+
self.state
1352+
.as_ref()
1353+
.map(|state| {
1354+
if state.peek().len() == 2 {
1355+
Ok(state.to_owned())
1356+
} else {
1357+
self.to_state_code()
1358+
}
1359+
})
1360+
.transpose()
1361+
}
13441362
}
13451363

13461364
pub trait BankRedirectBillingData {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- This file should undo anything in `up.sql`
2+
ALTER TABLE authentication ALTER COLUMN error_message TYPE VARCHAR(64);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Your SQL goes here
2+
ALTER TABLE authentication ALTER COLUMN error_message TYPE TEXT;

0 commit comments

Comments
 (0)