Skip to content

Commit

Permalink
feat(connector): add support for external authentication for cybersou…
Browse files Browse the repository at this point in the history
…rce (#4714)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
hrithikesh026 and hyperswitch-bot[bot] authored May 29, 2024
1 parent 0f7f3d9 commit 97f2ff0
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 10 deletions.
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
5 changes: 3 additions & 2 deletions crates/hyperswitch_domain_models/src/router_request_types.rs
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, ext_traits::OptionExt, pii};
use common_utils::{consts, errors, ext_traits::OptionExt, pii, types as common_types};
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use masking::Secret;
Expand Down Expand Up @@ -447,7 +447,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
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ pub enum AuthenticationResponseData {
authn_flow_type: AuthNFlowType,
authentication_value: Option<String>,
trans_status: common_enums::TransactionStatus,
ds_trans_id: Option<String>,
},
PostAuthNResponse {
trans_status: common_enums::TransactionStatus,
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.
///
/// 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
1 change: 1 addition & 0 deletions crates/router/src/connector/threedsecureio/transformers.rs
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 @@ -369,7 +369,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
Loading

0 comments on commit 97f2ff0

Please sign in to comment.