Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(connector): send valid sdk information in authentication flow netcetera #4474

Merged
merged 6 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ diesel::table! {
authentication_lifecycle_status -> Varchar,
created_at -> Timestamp,
modified_at -> Timestamp,
#[max_length = 64]
error_message -> Nullable<Varchar>,
error_message -> Nullable<Text>,
#[max_length = 64]
error_code -> Nullable<Varchar>,
connector_metadata -> Nullable<Jsonb>,
Expand Down
86 changes: 70 additions & 16 deletions crates/router/src/connector/netcetera/netcetera_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use std::collections::HashMap;
use common_utils::pii::Email;
use serde::{Deserialize, Serialize};

use crate::types::api::MessageCategory;
use crate::{
connector::utils::{AddressDetailsData, PhoneDetailsData},
errors,
types::api::MessageCategory,
};

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
Expand Down Expand Up @@ -679,18 +683,19 @@ pub struct Cardholder {
}

impl
From<(
TryFrom<(
api_models::payments::Address,
Option<api_models::payments::Address>,
)> for Cardholder
{
fn from(
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(billing_address, shipping_address): (
api_models::payments::Address,
Option<api_models::payments::Address>,
),
) -> Self {
Self {
) -> Result<Self, Self::Error> {
Ok(Self {
addr_match: None,
bill_addr_city: billing_address
.address
Expand All @@ -716,11 +721,24 @@ impl
bill_addr_state: billing_address
.address
.as_ref()
.and_then(|add| add.state.clone()),
.and_then(|add| add.to_state_code_as_optional().transpose())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need not call to_state_code_as_optional here if it's already sent as state code, we should check and call this fn only if required (as state in our request is a String and any value can be sent there)

.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(PhoneNumber::try_from)
.transpose()?,
mobile_phone: billing_address
.phone
.clone()
.map(PhoneNumber::try_from)
.transpose()?,
work_phone: billing_address
.phone
.clone()
.map(PhoneNumber::try_from)
.transpose()?,
cardholder_name: billing_address
.address
.as_ref()
Expand Down Expand Up @@ -749,9 +767,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_as_optional().transpose())
.transpose()?,
tax_id: None,
}
})
}
}

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

impl From<api_models::payments::PhoneDetails> for PhoneNumber {
fn from(value: api_models::payments::PhoneDetails) -> Self {
Self {
country_code: value.country_code,
impl TryFrom<api_models::payments::PhoneDetails> for PhoneNumber {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: api_models::payments::PhoneDetails) -> Result<Self, Self::Error> {
Ok(Self {
country_code: Some(value.extract_country_code()?),
subscriber: value.number,
}
})
}
}

Expand Down Expand Up @@ -1377,6 +1397,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<String>,

/// JWE Object as defined Section 6.2.2.1 containing data encrypted by the SDK for the DS to decrypt. This element is
Expand Down Expand Up @@ -1404,6 +1425,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<String>,

/// Contains the JWS object(represented as a string) created by the Split-SDK Server for the AReq message. A
Expand Down Expand Up @@ -1587,3 +1609,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: Vec<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,
}
29 changes: 24 additions & 5 deletions crates/router/src/connector/netcetera/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
))
}
Expand Down Expand Up @@ -430,7 +432,7 @@ pub struct NetceteraAuthenticationRequest {
pub acquirer: Option<netcetera_types::AcquirerData>,
pub merchant: Option<netcetera_types::MerchantData>,
pub broad_info: Option<String>,
pub device_render_options: Option<String>,
pub device_render_options: Option<netcetera_types::DeviceRenderingOptionsSupported>,
pub message_extension: Option<Vec<netcetera_types::MessageExtensionAttribute>>,
pub challenge_message_extension: Option<Vec<netcetera_types::MessageExtensionAttribute>>,
pub browser_information: Option<netcetera_types::Browser>,
Expand Down Expand Up @@ -552,6 +554,22 @@ 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::Both,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should always be Native for us

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per netcetera we are supposed to send this value.

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,
};
Ok(Self {
preferred_protocol_version: Some(pre_authn_data.message_version),
enforce_preferred_protocol_version: None,
Expand All @@ -567,15 +585,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,
Expand Down Expand Up @@ -625,6 +643,7 @@ pub struct AuthenticationResponse {
pub acs_reference_number: Option<String>,
#[serde(rename = "acsTransID")]
pub acs_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
Expand Down
18 changes: 18 additions & 0 deletions crates/router/src/connector/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,7 @@ pub trait PhoneDetailsData {
fn get_country_code(&self) -> Result<String, Error>;
fn get_number_with_country_code(&self) -> Result<Secret<String>, Error>;
fn get_number_with_hash_country_code(&self) -> Result<Secret<String>, Error>;
fn extract_country_code(&self) -> Result<String, Error>;
}

impl PhoneDetailsData for api::PhoneDetails {
Expand All @@ -1194,6 +1195,10 @@ impl PhoneDetailsData for api::PhoneDetails {
.clone()
.ok_or_else(missing_field_err("billing.phone.country_code"))
}
fn extract_country_code(&self) -> Result<String, Error> {
self.get_country_code()
.map(|cc| cc.trim_start_matches('+').to_string())
}
fn get_number(&self) -> Result<Secret<String>, Error> {
self.number
.clone()
Expand Down Expand Up @@ -1228,6 +1233,7 @@ pub trait AddressDetailsData {
fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>;
fn get_combined_address_line(&self) -> Result<Secret<String>, Error>;
fn to_state_code(&self) -> Result<Secret<String>, Error>;
fn to_state_code_as_optional(&self) -> Result<Option<Secret<String>>, Error>;
}

impl AddressDetailsData for api::AddressDetails {
Expand Down Expand Up @@ -1311,6 +1317,18 @@ impl AddressDetailsData for api::AddressDetails {
_ => Ok(state.clone()),
}
}
fn to_state_code_as_optional(&self) -> Result<Option<Secret<String>>, Error> {
self.state
.as_ref()
.map(|state| {
if state.peek().len() == 2 {
Ok(state.to_owned())
} else {
self.to_state_code()
}
})
.transpose()
}
}

pub trait BankRedirectBillingData {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE authentication ALTER COLUMN error_message TYPE VARCHAR(64);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE authentication ALTER COLUMN error_message TYPE TEXT;
Loading