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

feat(connector): add support for external authentication for cybersource #4714

Merged
merged 11 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions crates/diesel_models/src/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub struct Authentication {
pub profile_id: String,
pub payment_id: Option<String>,
pub merchant_connector_id: String,
pub ds_trans_id: Option<String>,
pub directory_server_id: Option<String>,
}

Expand Down Expand Up @@ -87,6 +88,7 @@ pub struct AuthenticationNew {
pub profile_id: String,
pub payment_id: Option<String>,
pub merchant_connector_id: String,
pub ds_trans_id: Option<String>,
pub directory_server_id: Option<String>,
}

Expand Down Expand Up @@ -115,6 +117,7 @@ pub enum AuthenticationUpdate {
acs_trans_id: Option<String>,
acs_signed_content: Option<String>,
authentication_status: common_enums::AuthenticationStatus,
ds_trans_id: Option<String>,
},
PostAuthenticationUpdate {
trans_status: common_enums::TransactionStatus,
Expand Down Expand Up @@ -162,6 +165,7 @@ pub struct AuthenticationUpdateInternal {
pub acs_reference_number: Option<String>,
pub acs_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
pub ds_trans_id: Option<String>,
pub directory_server_id: Option<String>,
}

Expand Down Expand Up @@ -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(),
}
}
Expand Down Expand Up @@ -226,6 +231,7 @@ impl AuthenticationUpdateInternal {
acs_reference_number,
acs_trans_id,
acs_signed_content,
ds_trans_id,
directory_server_id,
} = self;
Authentication {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -336,6 +343,7 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
acs_trans_id,
acs_signed_content,
authentication_status,
ds_trans_id,
} => Self {
cavv: authentication_value,
trans_status: Some(trans_status),
Expand All @@ -346,6 +354,7 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
acs_trans_id,
acs_signed_content,
authentication_status: Some(authentication_status),
ds_trans_id,
..Default::default()
},
AuthenticationUpdate::PostAuthenticationUpdate {
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ diesel::table! {
payment_id -> Nullable<Varchar>,
#[max_length = 128]
merchant_connector_id -> Varchar,
#[max_length = 64]
ds_trans_id -> Nullable<Varchar>,
#[max_length = 128]
directory_server_id -> Nullable<Varchar>,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod authentication;
pub mod fraud_check;
use api_models::payments::RequestSurchargeDetails;
use common_utils::{consts, errors, pii};
use common_utils::{consts, errors, pii, types as common_types};
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use masking::Secret;
Expand Down Expand Up @@ -296,7 +296,8 @@ pub struct AuthenticationData {
pub eci: Option<String>,
pub cavv: String,
pub threeds_server_transaction_id: String,
pub message_version: String,
pub message_version: common_types::SemanticVersion,
pub ds_trans_id: Option<String>,
}

#[derive(Debug, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/connector/checkout/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/router/src/connector/cybersource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
if req.is_three_ds()
&& req.request.is_card()
&& req.request.connector_mandate_id().is_none()
&& req.request.authentication_data.is_none()
{
Ok(format!(
"{}risk/v1/authentication-setups",
Expand Down Expand Up @@ -875,6 +876,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
if req.is_three_ds()
&& req.request.is_card()
&& req.request.connector_mandate_id().is_none()
&& req.request.authentication_data.is_none()
{
let connector_req =
cybersource::CybersourceAuthSetupRequest::try_from(&connector_router_data)?;
Expand Down Expand Up @@ -915,6 +917,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
if data.is_three_ds()
&& data.request.is_card()
&& data.request.connector_mandate_id().is_none()
&& data.request.authentication_data.is_none()
{
let response: cybersource::CybersourceAuthSetupResponse = res
.response
Expand Down
112 changes: 109 additions & 3 deletions crates/router/src/connector/cybersource/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use api_models::{
};
use base64::Engine;
use common_enums::FutureUsage;
use common_utils::{ext_traits::ValueExt, pii};
use common_utils::{ext_traits::ValueExt, pii, types::SemanticVersion};
use error_stack::ResultExt;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -261,7 +261,16 @@ pub struct CybersourceConsumerAuthInformation {
xid: Option<String>,
directory_server_transaction_id: Option<Secret<String>>,
specification_version: Option<String>,
/// This field specifies the 3ds version
pa_specification_version: Option<SemanticVersion>,
/// Verification response enrollment status.
///
/// This field is supported only on Asia, Middle East, and Africa Gateway.
Copy link
Contributor

Choose a reason for hiding this comment

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

What about the US gateway? Should we skip sending this value or external authentication itself is not available?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is a required field for Asia, Middle East, and Africa Gateway.

///
/// For external authentication, this field will always be "Y"
veres_enrolled: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDefinedInformation {
Expand Down Expand Up @@ -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,
Expand All @@ -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<String>,
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>,
Expand Down Expand Up @@ -852,12 +925,39 @@ impl
Vec::<MerchantDefinedInformation>::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,
})
}
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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,
})
Expand Down Expand Up @@ -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,
},
),
})
Expand Down
3 changes: 3 additions & 0 deletions crates/router/src/connector/netcetera/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
)
}
Expand Down Expand Up @@ -646,6 +647,8 @@ pub struct AuthenticationResponse {
pub acs_reference_number: Option<String>,
#[serde(rename = "acsTransID")]
pub acs_trans_id: Option<String>,
#[serde(rename = "dsTransID")]
pub ds_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
}

Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/connector/nmi/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ impl
types::authentication::AuthNFlowType::Frictionless
},
authentication_value: response.authentication_value,
ds_trans_id: Some(response.ds_trans_id),
},
)
}
Expand Down
3 changes: 3 additions & 0 deletions crates/router/src/core/authentication/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub async fn update_trackers<F: Clone, Req>(
authn_flow_type,
authentication_value,
trans_status,
ds_trans_id,
} => {
let authentication_status =
common_enums::AuthenticationStatus::foreign_from(trans_status.clone());
Expand All @@ -97,6 +98,7 @@ pub async fn update_trackers<F: Clone, Req>(
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 {
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion crates/router/src/core/payments/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,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())
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/db/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading
Loading