mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): add support for external authentication for cybersource (#4714)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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>, | ||||
| } | ||||
|  | ||||
| @ -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>, | ||||
| } | ||||
|  | ||||
| @ -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, | ||||
| @ -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>, | ||||
| } | ||||
|  | ||||
| @ -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(), | ||||
|         } | ||||
|     } | ||||
| @ -226,6 +231,7 @@ impl AuthenticationUpdateInternal { | ||||
|             acs_reference_number, | ||||
|             acs_trans_id, | ||||
|             acs_signed_content, | ||||
|             ds_trans_id, | ||||
|             directory_server_id, | ||||
|         } = self; | ||||
|         Authentication { | ||||
| @ -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 | ||||
|         } | ||||
| @ -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), | ||||
| @ -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 { | ||||
|  | ||||
| @ -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>, | ||||
|     } | ||||
|  | ||||
| @ -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; | ||||
| @ -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)] | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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", | ||||
| @ -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)?; | ||||
| @ -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 | ||||
|  | ||||
| @ -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}; | ||||
| @ -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 { | ||||
| @ -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, | ||||
| @ -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>, | ||||
| @ -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, | ||||
|         }) | ||||
|     } | ||||
| @ -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 = | ||||
| @ -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, | ||||
|         }) | ||||
| @ -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, | ||||
|                                             }, | ||||
|                                         ), | ||||
|                                     }) | ||||
|  | ||||
| @ -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, | ||||
|                     }, | ||||
|                 ) | ||||
|             } | ||||
| @ -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>, | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -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()), | ||||
|         }; | ||||
|  | ||||
|  | ||||
| @ -187,6 +187,7 @@ impl | ||||
|                             types::authentication::AuthNFlowType::Frictionless | ||||
|                         }, | ||||
|                         authentication_value: response.authentication_value, | ||||
|                         ds_trans_id: Some(response.ds_trans_id), | ||||
|                     }, | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
| @ -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()); | ||||
| @ -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 { | ||||
| @ -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 | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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()); | ||||
|  | ||||
| @ -0,0 +1,2 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
| ALTER TABLE authentication DROP COLUMN If EXISTS ds_trans_id; | ||||
| @ -0,0 +1,2 @@ | ||||
| -- Your SQL goes here | ||||
| ALTER TABLE authentication ADD COLUMN IF NOT EXISTS ds_trans_id VARCHAR(64); | ||||
		Reference in New Issue
	
	Block a user
	 Hrithikesh
					Hrithikesh