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): [UNIFIED_AUTHENTICATION_SERVICE] add support for external 3ds authentication #7065

Open
wants to merge 2 commits into
base: external-auth-through-uas-core-flow-changes
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -203,33 +203,39 @@ impl
req: &UasPreAuthenticationRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let transaction_details = req.request.transaction_details.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "transaction_details",
},
)?;
let amount = utils::convert_amount(
self.amount_converter,
transaction_details
.amount
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?,
transaction_details
.currency
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?,
)?;

let connector_router_data =
unified_authentication_service::UnifiedAuthenticationServiceRouterData::from((
amount, req,
));
let connector_req =
unified_authentication_service::UnifiedAuthenticationServicePreAuthenticateRequest::try_from(
&connector_router_data,
// Transaction details are mandatory if service details are present (For Click to Pay flow)
let connector_req = if req.request.service_details.is_some() {
let transaction_details = req.request.transaction_details.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "transaction_details",
},
)?;
let amount = utils::convert_amount(
self.amount_converter,
transaction_details
.amount
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?,
transaction_details.currency.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "currency",
},
)?,
)?;

let connector_router_data =
unified_authentication_service::UnifiedAuthenticationServiceRouterData::from((
amount, req,
));
unified_authentication_service::UnifiedAuthenticationServicePreAuthenticateRequest::try_from(
&connector_router_data,
)?
} else {
unified_authentication_service::UnifiedAuthenticationServicePreAuthenticateRequest::try_from(
req,
)?
};
Ok(RequestContent::Json(Box::new(connector_req)))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,20 @@ pub struct UnifiedAuthenticationServicePreAuthenticateRequest {
pub service_details: Option<CtpServiceDetails>,
pub customer_details: Option<CustomerDetails>,
pub pmt_details: Option<PaymentDetails>,
pub auth_creds: AuthType,
pub auth_creds: UnifiedAuthenticationServiceAuthType,
pub transaction_details: Option<TransactionDetails>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(tag = "auth_type")]
pub enum AuthType {
HeaderKey { api_key: Secret<String> },
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct PaymentDetails {
pub pan: cards::CardNumber,
pub digital_card_id: Option<String>,
pub payment_data_type: Option<String>,
pub encrypted_src_card_details: Option<String>,
pub card_expiry_date: Secret<String>,
pub cardholder_name: Secret<String>,
pub cardholder_name: Option<Secret<String>>,
pub card_token_number: Secret<String>,
pub account_type: u8,
pub account_type: Option<common_enums::CardNetwork>,
}

#[derive(Default, Debug, Serialize, PartialEq)]
Expand Down Expand Up @@ -226,9 +220,7 @@ impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouter
}),
customer_details: None,
pmt_details: None,
auth_creds: AuthType::HeaderKey {
api_key: auth_type.api_key,
},
auth_creds: auth_type,
transaction_details: Some(TransactionDetails {
amount: item.amount,
currency: item
Expand All @@ -255,18 +247,72 @@ impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouter
}
}

impl TryFrom<&UasPreAuthenticationRouterData>
for UnifiedAuthenticationServicePreAuthenticateRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &UasPreAuthenticationRouterData) -> Result<Self, Self::Error> {
let auth_type = UnifiedAuthenticationServiceAuthType::try_from(&item.connector_auth_type)?;
let authentication_id =
item.authentication_id
.clone()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "authentication_id",
})?;
Ok(Self {
authenticate_by: item.connector.clone(),
session_id: authentication_id.clone(),
source_authentication_id: authentication_id,
authentication_info: None,
service_details: None,
customer_details: None,
pmt_details: item
.request
.pmt_details
.clone()
.map(|details| PaymentDetails {
pan: details.pan,
digital_card_id: details.digital_card_id,
payment_data_type: details.payment_data_type,
encrypted_src_card_details: details.encrypted_src_card_details,
card_expiry_date: details.card_expiry_date,
cardholder_name: details.cardholder_name,
card_token_number: details.card_token_number,
account_type: details.account_type,
}),
auth_creds: auth_type,
transaction_details: None,
})
}
}

//TODO: Fill the struct with respective fields
// Auth Struct
pub struct UnifiedAuthenticationServiceAuthType {
pub(super) api_key: Secret<String>,
#[derive(Debug, Serialize, PartialEq)]
#[serde(tag = "auth_type")]
pub enum UnifiedAuthenticationServiceAuthType {
HeaderKey {
api_key: Secret<String>,
},
CertificateAuth {
certificate: Secret<String>,
private_key: Secret<String>,
},
}

impl TryFrom<&ConnectorAuthType> for UnifiedAuthenticationServiceAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
api_key: api_key.to_owned(),
ConnectorAuthType::HeaderKey { api_key } => Ok(Self::HeaderKey {
api_key: api_key.clone(),
}),
ConnectorAuthType::CertificateAuth {
certificate,
private_key,
} => Ok(Self::CertificateAuth {
certificate: certificate.clone(),
private_key: private_key.clone(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
}
Expand All @@ -283,6 +329,107 @@ pub enum UnifiedAuthenticationServicePreAuthenticateStatus {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnifiedAuthenticationServicePreAuthenticateResponse {
status: UnifiedAuthenticationServicePreAuthenticateStatus,
pub eligibility: Option<Eligibility>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "eligibility")]
pub enum Eligibility {
None,
TokenEligibilityResponse {
token_eligibility_response: Box<TokenEligibilityResponse>,
},
ThreeDsEligibilityResponse {
three_ds_eligibility_response: Box<ThreeDsEligibilityResponse>,
},
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TokenEligibilityResponse {
pub network_request_id: Option<String>,
pub network_client_id: Option<String>,
pub nonce: Option<String>,
pub payment_method_details: Option<PaymentMethodDetails>,
pub network_pan_enrollment_id: Option<String>,
pub ignore_00_field: Option<String>,
pub token_details: Option<TokenDetails>,
pub network_provisioned_token_id: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PaymentMethodDetails {
pub ignore_01_field: String,
pub cvv2_printed_ind: String,
pub last4: String,
pub exp_date_printed_ind: String,
pub payment_account_reference: String,
pub exp_year: String,
pub exp_month: String,
pub verification_results: VerificationResults,
pub enabled_services: EnabledServices,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VerificationResults {
pub address_verification_code: String,
pub cvv2_verification_code: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EnabledServices {
pub merchant_presented_qr: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ThreeDsEligibilityResponse {
pub three_ds_server_trans_id: String,
pub scheme_id: Option<String>,
pub acs_protocol_versions: Option<Vec<AcsProtocolVersion>>,
pub ds_protocol_versions: Option<Vec<String>>,
pub three_ds_method_data_form: ThreeDsMethodDataForm,
pub three_ds_method_data: Option<ThreeDsMethodData>,
pub error_details: Option<String>,
pub is_card_found_in_2x_ranges: bool,
pub directory_server_id: Option<String>,
}

impl ThreeDsEligibilityResponse {
pub fn get_max_acs_protocol_version_if_available(&self) -> Option<AcsProtocolVersion> {
let max_acs_version =
self.acs_protocol_versions
.as_ref()
.and_then(|acs_protocol_versions| {
acs_protocol_versions
.iter()
.max_by_key(|acs_protocol_versions| acs_protocol_versions.version.clone())
});
max_acs_version.cloned()
}
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct AcsProtocolVersion {
pub version: common_utils::types::SemanticVersion,
pub acs_info_ind: Vec<String>,
pub three_ds_method_url: Option<String>,
pub supported_msg_ext: Option<Vec<SupportedMsgExt>>,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SupportedMsgExt {
pub id: String,
pub version: String,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct ThreeDsMethodDataForm {
pub three_ds_method_data: Option<String>,
}

#[derive(Default, Clone, Serialize, Deserialize, Debug)]
pub struct ThreeDsMethodData {
pub three_ds_method_notification_url: String,
pub server_transaction_id: String,
}

impl<F, T>
Expand All @@ -304,17 +451,47 @@ impl<F, T>
UasAuthenticationResponseData,
>,
) -> Result<Self, Self::Error> {
let three_ds_eligibility_response = if let Some(Eligibility::ThreeDsEligibilityResponse {
three_ds_eligibility_response,
}) = item.response.eligibility
{
Some(three_ds_eligibility_response)
} else {
None
};
let max_acs_protocol_version = three_ds_eligibility_response
.as_ref()
.and_then(|response| response.get_max_acs_protocol_version_if_available());
let maximum_supported_3ds_version = max_acs_protocol_version
.as_ref()
.map(|acs_protocol_version| acs_protocol_version.version.clone());
let three_ds_method_data =
three_ds_eligibility_response
.as_ref()
.and_then(|three_ds_eligibility_response| {
three_ds_eligibility_response
.three_ds_method_data_form
.three_ds_method_data
.clone()
});
let three_ds_method_url = max_acs_protocol_version
.and_then(|acs_protocol_version| acs_protocol_version.three_ds_method_url);
Ok(Self {
response: Ok(UasAuthenticationResponseData::PreAuthentication {
authentication_details: PreAuthenticationDetails {
threeds_server_transaction_id: None,
maximum_supported_3ds_version: None,
connector_authentication_id: None,
three_ds_method_data: None,
three_ds_method_url: None,
message_version: None,
threeds_server_transaction_id: three_ds_eligibility_response
.as_ref()
.map(|response| response.three_ds_server_trans_id.clone()),
maximum_supported_3ds_version: maximum_supported_3ds_version.clone(),
connector_authentication_id: three_ds_eligibility_response
.as_ref()
.map(|response| response.three_ds_server_trans_id.clone()),
three_ds_method_data,
three_ds_method_url,
message_version: maximum_supported_3ds_version,
connector_metadata: None,
directory_server_id: None,
directory_server_id: three_ds_eligibility_response
.and_then(|response| response.directory_server_id),
},
}),
..item.data
Expand All @@ -326,7 +503,7 @@ impl<F, T>
pub struct UnifiedAuthenticationServicePostAuthenticateRequest {
pub authenticate_by: String,
pub source_authentication_id: String,
pub auth_creds: AuthType,
pub auth_creds: UnifiedAuthenticationServiceAuthType,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -369,9 +546,7 @@ impl TryFrom<&UasPostAuthenticationRouterData>
field_name: "authentication_id",
},
)?,
auth_creds: AuthType::HeaderKey {
api_key: auth_type.api_key,
},
auth_creds: auth_type,
})
}
}
Expand Down
Loading