refactor(core): move authentication data fields to authentication table (#4093)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: sai-harsha-vardhan <harsha111hero@gmail.com>
Co-authored-by: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com>
This commit is contained in:
Hrithikesh
2024-03-19 15:10:18 +05:30
committed by GitHub
parent ab1ec2ad4e
commit a3dec0b6bc
29 changed files with 1175 additions and 895 deletions

869
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -2307,7 +2307,7 @@ pub enum ThreeDsMethodData {
/// Whether ThreeDS method data submission is required
three_ds_method_data_submission: bool,
/// ThreeDS method data
three_ds_method_data: String,
three_ds_method_data: Option<String>,
/// ThreeDS method url
three_ds_method_url: Option<String>,
},
@@ -3637,40 +3637,12 @@ pub struct SdkInformation {
pub sdk_max_timeout: u8,
}
#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, ToSchema)]
pub enum TransactionStatus {
/// Authentication/ Account Verification Successful
#[serde(rename = "Y")]
Success,
/// Not Authenticated /Account Not Verified; Transaction denied
#[default]
#[serde(rename = "N")]
Failure,
/// Authentication/ Account Verification Could Not Be Performed; Technical or other problem, as indicated in Authentication Response(ARes) or Result Request (RReq)
#[serde(rename = "U")]
VerificationNotPerformed,
/// Attempts Processing Performed; Not Authenticated/Verified , but a proof of attempted authentication/verification is provided
#[serde(rename = "A")]
NotVerified,
/// Authentication/ Account Verification Rejected; Issuer is rejecting authentication/verification and request that authorisation not be attempted.
#[serde(rename = "R")]
Rejected,
/// Challenge Required; Additional authentication is required using the Challenge Request (CReq) / Challenge Response (CRes)
#[serde(rename = "C")]
ChallengeRequired,
/// Challenge Required; Decoupled Authentication confirmed.
#[serde(rename = "D")]
ChallengeRequiredDecoupledAuthentication,
/// Informational Only; 3DS Requestor challenge preference acknowledged.
#[serde(rename = "I")]
InformationOnly,
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub struct PaymentsExternalAuthenticationResponse {
/// Indicates the trans status
#[serde(rename = "trans_status")]
pub transaction_status: TransactionStatus,
#[schema(value_type = TransactionStatus)]
pub transaction_status: common_enums::TransactionStatus,
/// Access Server URL to be used for challenge submission
pub acs_url: Option<String>,
/// Challenge request which should be sent to acs_url

View File

@@ -2363,6 +2363,47 @@ pub enum RoleScope {
Organization,
}
#[derive(
Clone,
Default,
Debug,
serde::Serialize,
serde::Deserialize,
Eq,
PartialEq,
ToSchema,
strum::Display,
strum::EnumString,
)]
#[router_derive::diesel_enum(storage_type = "text")]
pub enum TransactionStatus {
/// Authentication/ Account Verification Successful
#[serde(rename = "Y")]
Success,
/// Not Authenticated /Account Not Verified; Transaction denied
#[default]
#[serde(rename = "N")]
Failure,
/// Authentication/ Account Verification Could Not Be Performed; Technical or other problem, as indicated in Authentication Response(ARes) or Result Request (RReq)
#[serde(rename = "U")]
VerificationNotPerformed,
/// Attempts Processing Performed; Not Authenticated/Verified , but a proof of attempted authentication/verification is provided
#[serde(rename = "A")]
NotVerified,
/// Authentication/ Account Verification Rejected; Issuer is rejecting authentication/verification and request that authorisation not be attempted.
#[serde(rename = "R")]
Rejected,
/// Challenge Required; Additional authentication is required using the Challenge Request (CReq) / Challenge Response (CRes)
#[serde(rename = "C")]
ChallengeRequired,
/// Challenge Required; Decoupled Authentication confirmed.
#[serde(rename = "D")]
ChallengeRequiredDecoupledAuthentication,
/// Informational Only; 3DS Requestor challenge preference acknowledged.
#[serde(rename = "I")]
InformationOnly,
}
#[derive(
Clone,
Copy,

View File

@@ -38,6 +38,7 @@ strum = { version = "0.24.1", features = ["derive"] }
thiserror = "1.0.40"
time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] }
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"], optional = true }
semver = { version = "1.0.22", features = ["serde"] }
uuid = { version = "1.7.0", features = ["v7"] }
# First party crates

View File

@@ -1,12 +1,21 @@
//! Types that can be used in other crates
use std::{fmt::Display, str::FromStr};
use diesel::{
backend::Backend,
deserialize::FromSql,
serialize::{Output, ToSql},
sql_types::Jsonb,
AsExpression, FromSqlRow,
};
use error_stack::{IntoReport, ResultExt};
use semver::Version;
use serde::{de::Visitor, Deserialize, Deserializer};
use crate::{
consts,
errors::{CustomResult, PercentageError},
errors::{CustomResult, ParsingError, PercentageError},
};
/// Represents Percentage Value between 0 and 100 both inclusive
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize)]
pub struct Percentage<const PRECISION: u8> {
@@ -149,3 +158,56 @@ pub enum Surcharge {
/// Surcharge percentage
Rate(Percentage<{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH }>),
}
/// This struct lets us represent a semantic version type
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression)]
#[diesel(sql_type = Jsonb)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SemanticVersion(#[serde(with = "Version")] Version);
impl SemanticVersion {
/// returns major version number
pub fn get_major(&self) -> u64 {
self.0.major
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for SemanticVersion {
type Err = error_stack::Report<ParsingError>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Version::from_str(s).into_report().change_context(
ParsingError::StructParseFailure("SemanticVersion"),
)?))
}
}
impl<DB: Backend> FromSql<Jsonb, DB> for SemanticVersion
where
serde_json::Value: FromSql<Jsonb, DB>,
{
fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
let value = <serde_json::Value as FromSql<Jsonb, DB>>::from_sql(bytes)?;
Ok(serde_json::from_value(value)?)
}
}
impl ToSql<Jsonb, diesel::pg::Pg> for SemanticVersion
where
serde_json::Value: ToSql<Jsonb, diesel::pg::Pg>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> diesel::serialize::Result {
let value = serde_json::to_value(self)?;
// the function `reborrow` only works in case of `Pg` backend. But, in case of other backends
// please refer to the diesel migration blog:
// https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations
<serde_json::Value as ToSql<Jsonb, diesel::pg::Pg>>::to_sql(&value, &mut out.reborrow())
}
}

View File

@@ -23,6 +23,31 @@ pub struct Authentication {
pub error_message: Option<String>,
pub error_code: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
pub threeds_server_transaction_id: Option<String>,
pub cavv: Option<String>,
pub authentication_flow_type: Option<String>,
pub message_version: Option<common_utils::types::SemanticVersion>,
pub eci: Option<String>,
pub trans_status: Option<common_enums::TransactionStatus>,
pub acquirer_bin: Option<String>,
pub acquirer_merchant_id: Option<String>,
pub three_ds_method_data: Option<String>,
pub three_ds_method_url: Option<String>,
pub acs_url: Option<String>,
pub challenge_request: Option<String>,
pub acs_reference_number: Option<String>,
pub acs_trans_id: Option<String>,
pub three_ds_server_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
}
impl Authentication {
pub fn is_separate_authn_required(&self) -> bool {
self.maximum_supported_version
.as_ref()
.is_some_and(|version| version.get_major() == 2)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Insertable)]
@@ -32,7 +57,7 @@ pub struct AuthenticationNew {
pub merchant_id: String,
pub authentication_connector: String,
pub connector_authentication_id: Option<String>,
pub authentication_data: Option<serde_json::Value>,
// pub authentication_data: Option<serde_json::Value>,
pub payment_method_id: String,
pub authentication_type: Option<common_enums::DecoupledAuthenticationType>,
pub authentication_status: common_enums::AuthenticationStatus,
@@ -40,21 +65,56 @@ pub struct AuthenticationNew {
pub error_message: Option<String>,
pub error_code: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
pub threeds_server_transaction_id: Option<String>,
pub cavv: Option<String>,
pub authentication_flow_type: Option<String>,
pub message_version: Option<common_utils::types::SemanticVersion>,
pub eci: Option<String>,
pub trans_status: Option<common_enums::TransactionStatus>,
pub acquirer_bin: Option<String>,
pub acquirer_merchant_id: Option<String>,
pub three_ds_method_data: Option<String>,
pub three_ds_method_url: Option<String>,
pub acs_url: Option<String>,
pub challenge_request: Option<String>,
pub acs_reference_number: Option<String>,
pub acs_trans_id: Option<String>,
pub three_dsserver_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
}
#[derive(Debug)]
pub enum AuthenticationUpdate {
AuthenticationDataUpdate {
authentication_data: Option<serde_json::Value>,
connector_authentication_id: Option<String>,
payment_method_id: Option<String>,
authentication_type: Option<common_enums::DecoupledAuthenticationType>,
authentication_status: Option<common_enums::AuthenticationStatus>,
authentication_lifecycle_status: Option<common_enums::AuthenticationLifecycleStatus>,
PreAuthenticationUpdate {
threeds_server_transaction_id: String,
maximum_supported_3ds_version: common_utils::types::SemanticVersion,
connector_authentication_id: String,
three_ds_method_data: String,
three_ds_method_url: Option<String>,
message_version: common_utils::types::SemanticVersion,
connector_metadata: Option<serde_json::Value>,
authentication_status: common_enums::AuthenticationStatus,
payment_method_id: Option<String>,
acquirer_bin: Option<String>,
acquirer_merchant_id: Option<String>,
},
PostAuthorizationUpdate {
authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus,
AuthenticationUpdate {
authentication_value: Option<String>,
trans_status: common_enums::TransactionStatus,
authentication_type: common_enums::DecoupledAuthenticationType,
acs_url: Option<String>,
challenge_request: Option<String>,
acs_reference_number: Option<String>,
acs_trans_id: Option<String>,
acs_signed_content: Option<String>,
authentication_status: common_enums::AuthenticationStatus,
},
PostAuthenticationUpdate {
trans_status: common_enums::TransactionStatus,
authentication_value: Option<String>,
eci: Option<String>,
authentication_status: common_enums::AuthenticationStatus,
},
ErrorUpdate {
error_message: Option<String>,
@@ -62,13 +122,16 @@ pub enum AuthenticationUpdate {
authentication_status: common_enums::AuthenticationStatus,
connector_authentication_id: Option<String>,
},
PostAuthorizationUpdate {
authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus,
},
}
#[derive(Clone, Debug, Eq, PartialEq, AsChangeset, Serialize, Deserialize)]
#[diesel(table_name = authentication)]
pub struct AuthenticationUpdateInternal {
pub connector_authentication_id: Option<String>,
pub authentication_data: Option<serde_json::Value>,
// pub authentication_data: Option<serde_json::Value>,
pub payment_method_id: Option<String>,
pub authentication_type: Option<common_enums::DecoupledAuthenticationType>,
pub authentication_status: Option<common_enums::AuthenticationStatus>,
@@ -77,13 +140,62 @@ pub struct AuthenticationUpdateInternal {
pub error_message: Option<String>,
pub error_code: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
pub threeds_server_transaction_id: Option<String>,
pub cavv: Option<String>,
pub authentication_flow_type: Option<String>,
pub message_version: Option<common_utils::types::SemanticVersion>,
pub eci: Option<String>,
pub trans_status: Option<common_enums::TransactionStatus>,
pub acquirer_bin: Option<String>,
pub acquirer_merchant_id: Option<String>,
pub three_ds_method_data: Option<String>,
pub three_ds_method_url: Option<String>,
pub acs_url: Option<String>,
pub challenge_request: Option<String>,
pub acs_reference_number: Option<String>,
pub acs_trans_id: Option<String>,
pub three_dsserver_trans_id: Option<String>,
pub acs_signed_content: Option<String>,
}
impl Default for AuthenticationUpdateInternal {
fn default() -> Self {
Self {
connector_authentication_id: Default::default(),
payment_method_id: Default::default(),
authentication_type: Default::default(),
authentication_status: Default::default(),
authentication_lifecycle_status: Default::default(),
modified_at: common_utils::date_time::now(),
error_message: Default::default(),
error_code: Default::default(),
connector_metadata: Default::default(),
maximum_supported_version: Default::default(),
threeds_server_transaction_id: Default::default(),
cavv: Default::default(),
authentication_flow_type: Default::default(),
message_version: Default::default(),
eci: Default::default(),
trans_status: Default::default(),
acquirer_bin: Default::default(),
acquirer_merchant_id: Default::default(),
three_ds_method_data: Default::default(),
three_ds_method_url: Default::default(),
acs_url: Default::default(),
challenge_request: Default::default(),
acs_reference_number: Default::default(),
acs_trans_id: Default::default(),
three_dsserver_trans_id: Default::default(),
acs_signed_content: Default::default(),
}
}
}
impl AuthenticationUpdateInternal {
pub fn apply_changeset(self, source: Authentication) -> Authentication {
let Self {
connector_authentication_id,
authentication_data,
payment_method_id,
authentication_type,
authentication_status,
@@ -92,11 +204,27 @@ impl AuthenticationUpdateInternal {
error_code,
error_message,
connector_metadata,
maximum_supported_version,
threeds_server_transaction_id,
cavv,
authentication_flow_type,
message_version,
eci,
trans_status,
acquirer_bin,
acquirer_merchant_id,
three_ds_method_data,
three_ds_method_url,
acs_url,
challenge_request,
acs_reference_number,
acs_trans_id,
three_dsserver_trans_id,
acs_signed_content,
} = self;
Authentication {
connector_authentication_id: connector_authentication_id
.or(source.connector_authentication_id),
authentication_data: authentication_data.or(source.authentication_data),
payment_method_id: payment_method_id.unwrap_or(source.payment_method_id),
authentication_type: authentication_type.or(source.authentication_type),
authentication_status: authentication_status.unwrap_or(source.authentication_status),
@@ -106,6 +234,25 @@ impl AuthenticationUpdateInternal {
error_code: error_code.or(source.error_code),
error_message: error_message.or(source.error_message),
connector_metadata: connector_metadata.or(source.connector_metadata),
maximum_supported_version: maximum_supported_version
.or(source.maximum_supported_version),
threeds_server_transaction_id: threeds_server_transaction_id
.or(source.threeds_server_transaction_id),
cavv: cavv.or(source.cavv),
authentication_flow_type: authentication_flow_type.or(source.authentication_flow_type),
message_version: message_version.or(source.message_version),
eci: eci.or(source.eci),
trans_status: trans_status.or(source.trans_status),
acquirer_bin: acquirer_bin.or(source.acquirer_bin),
acquirer_merchant_id: acquirer_merchant_id.or(source.acquirer_merchant_id),
three_ds_method_data: three_ds_method_data.or(source.three_ds_method_data),
three_ds_method_url: three_ds_method_url.or(source.three_ds_method_url),
acs_url: acs_url.or(source.acs_url),
challenge_request: challenge_request.or(source.challenge_request),
acs_reference_number: acs_reference_number.or(source.acs_reference_number),
acs_trans_id: acs_trans_id.or(source.acs_trans_id),
three_ds_server_trans_id: three_dsserver_trans_id.or(source.three_ds_server_trans_id),
acs_signed_content: acs_signed_content.or(source.acs_signed_content),
..source
}
}
@@ -114,26 +261,6 @@ impl AuthenticationUpdateInternal {
impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
fn from(auth_update: AuthenticationUpdate) -> Self {
match auth_update {
AuthenticationUpdate::AuthenticationDataUpdate {
authentication_data,
connector_authentication_id,
authentication_type,
authentication_status,
payment_method_id,
authentication_lifecycle_status,
connector_metadata,
} => Self {
authentication_data,
connector_authentication_id,
authentication_type,
authentication_status,
authentication_lifecycle_status,
modified_at: common_utils::date_time::now(),
payment_method_id,
error_message: None,
error_code: None,
connector_metadata,
},
AuthenticationUpdate::ErrorUpdate {
error_message,
error_code,
@@ -143,19 +270,20 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
error_code,
error_message,
authentication_status: Some(authentication_status),
authentication_data: None,
connector_authentication_id,
authentication_type: None,
authentication_lifecycle_status: None,
modified_at: common_utils::date_time::now(),
payment_method_id: None,
connector_metadata: None,
..Default::default()
},
AuthenticationUpdate::PostAuthorizationUpdate {
authentication_lifecycle_status,
} => Self {
connector_authentication_id: None,
authentication_data: None,
payment_method_id: None,
authentication_type: None,
authentication_status: None,
@@ -164,6 +292,67 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
error_message: None,
error_code: None,
connector_metadata: None,
..Default::default()
},
AuthenticationUpdate::PreAuthenticationUpdate {
threeds_server_transaction_id,
maximum_supported_3ds_version,
connector_authentication_id,
three_ds_method_data,
three_ds_method_url,
message_version,
connector_metadata,
authentication_status,
payment_method_id,
acquirer_bin,
acquirer_merchant_id,
} => Self {
threeds_server_transaction_id: Some(threeds_server_transaction_id),
maximum_supported_version: Some(maximum_supported_3ds_version),
connector_authentication_id: Some(connector_authentication_id),
three_ds_method_data: Some(three_ds_method_data),
three_ds_method_url,
message_version: Some(message_version),
connector_metadata,
authentication_status: Some(authentication_status),
payment_method_id,
acquirer_bin,
acquirer_merchant_id,
..Default::default()
},
AuthenticationUpdate::AuthenticationUpdate {
authentication_value,
trans_status,
authentication_type,
acs_url,
challenge_request,
acs_reference_number,
acs_trans_id,
acs_signed_content,
authentication_status,
} => Self {
cavv: authentication_value,
trans_status: Some(trans_status),
authentication_type: Some(authentication_type),
acs_url,
challenge_request,
acs_reference_number,
acs_trans_id,
acs_signed_content,
authentication_status: Some(authentication_status),
..Default::default()
},
AuthenticationUpdate::PostAuthenticationUpdate {
trans_status,
authentication_value,
eci,
authentication_status,
} => Self {
trans_status: Some(trans_status),
cavv: authentication_value,
eci,
authentication_status: Some(authentication_status),
..Default::default()
},
}
}

View File

@@ -87,6 +87,30 @@ diesel::table! {
#[max_length = 64]
error_code -> Nullable<Varchar>,
connector_metadata -> Nullable<Jsonb>,
maximum_supported_version -> Nullable<Jsonb>,
#[max_length = 64]
threeds_server_transaction_id -> Nullable<Varchar>,
#[max_length = 64]
cavv -> Nullable<Varchar>,
#[max_length = 64]
authentication_flow_type -> Nullable<Varchar>,
message_version -> Nullable<Jsonb>,
#[max_length = 64]
eci -> Nullable<Varchar>,
#[max_length = 64]
trans_status -> Nullable<Varchar>,
#[max_length = 64]
acquirer_bin -> Nullable<Varchar>,
#[max_length = 64]
acquirer_merchant_id -> Nullable<Varchar>,
three_ds_method_data -> Nullable<Varchar>,
three_ds_method_url -> Nullable<Varchar>,
acs_url -> Nullable<Varchar>,
challenge_request -> Nullable<Varchar>,
acs_reference_number -> Nullable<Varchar>,
acs_trans_id -> Nullable<Varchar>,
three_dsserver_trans_id -> Nullable<Varchar>,
acs_signed_content -> Nullable<Varchar>,
}
}

View File

@@ -384,7 +384,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::SdkInformation,
api_models::payments::DeviceChannel,
api_models::payments::ThreeDsCompletionIndicator,
api_models::payments::TransactionStatus,
api_models::enums::TransactionStatus,
api_models::payments::BrowserInformation,
api_models::payments::PaymentCreatePaymentLinkConfig,
api_models::payments::ThreeDsData,

View File

@@ -391,8 +391,8 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme
enums::AuthenticationType::ThreeDs => CheckoutThreeDS {
enabled: true,
force_3ds: true,
eci: authentication_data.and_then(|auth| auth.eci.clone()),
cryptogram: authentication_data.and_then(|auth| auth.cavv.clone()),
eci: authentication_data.map(|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()),
},

View File

@@ -457,11 +457,7 @@ impl
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let req_obj = threedsecureio::ThreedsecureioPostAuthenticationRequest {
three_ds_server_trans_id: req
.request
.authentication_data
.threeds_server_transaction_id
.clone(),
three_ds_server_trans_id: req.request.threeds_server_transaction_id.clone(),
};
Ok(RequestContent::Json(Box::new(req_obj)))
}

View File

@@ -1,7 +1,9 @@
use std::str::FromStr;
use api_models::payments::{DeviceChannel, ThreeDsCompletionIndicator};
use base64::Engine;
use common_utils::date_time;
use error_stack::{report, IntoReport, ResultExt};
use error_stack::{IntoReport, ResultExt};
use iso_currency::Currency;
use isocountry;
use masking::{ExposeInterface, Secret};
@@ -16,7 +18,6 @@ use crate::{
self,
api::{self, MessageCategory},
authentication::ChallengeParams,
transformers::ForeignTryFrom,
},
utils::OptionExt,
};
@@ -100,13 +101,18 @@ impl
threeds_server_transaction_id: pre_authn_response
.threeds_server_trans_id
.clone(),
maximum_supported_3ds_version: ForeignTryFrom::foreign_try_from(
pre_authn_response.acs_end_protocol_version.clone(),
)?,
maximum_supported_3ds_version:
common_utils::types::SemanticVersion::from_str(
&pre_authn_response.acs_end_protocol_version,
)
.change_context(errors::ConnectorError::ParsingFailed)?,
connector_authentication_id: pre_authn_response.threeds_server_trans_id,
three_ds_method_data: three_ds_method_data_base64,
three_ds_method_url: pre_authn_response.threeds_method_url,
message_version: pre_authn_response.acs_end_protocol_version.clone(),
message_version: common_utils::types::SemanticVersion::from_str(
&pre_authn_response.acs_end_protocol_version,
)
.change_context(errors::ConnectorError::ParsingFailed)?,
connector_metadata: Some(connector_metadata),
},
)
@@ -307,7 +313,7 @@ impl TryFrom<&ThreedsecureioRouterData<&types::authentication::ConnectorAuthenti
.parse_value("ThreeDSecureIoMetaData")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
let authentication_data = &request.authentication_data.0;
let pre_authentication_data = &request.pre_authentication_data;
let sdk_information = match request.device_channel {
DeviceChannel::App => Some(item.router_data.request.sdk_information.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
@@ -316,21 +322,24 @@ impl TryFrom<&ThreedsecureioRouterData<&types::authentication::ConnectorAuthenti
)?),
DeviceChannel::Browser => None,
};
let acquirer_details = authentication_data
.acquirer_details
let (acquirer_bin, acquirer_merchant_id) = pre_authentication_data
.acquirer_bin
.clone()
.zip(pre_authentication_data.acquirer_merchant_id.clone())
.get_required_value("acquirer_details")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "acquirer_details",
})?;
let meta: ThreeDSecureIoConnectorMetaData =
to_connector_meta(request.authentication_data.1.connector_metadata.clone())?;
to_connector_meta(request.pre_authentication_data.connector_metadata.clone())?;
Ok(Self {
ds_start_protocol_version: meta.ds_start_protocol_version.clone(),
ds_end_protocol_version: meta.ds_end_protocol_version.clone(),
acs_start_protocol_version: meta.acs_start_protocol_version.clone(),
acs_end_protocol_version: meta.acs_end_protocol_version.clone(),
three_dsserver_trans_id: authentication_data.threeds_server_transaction_id.clone(),
three_dsserver_trans_id: pre_authentication_data
.threeds_server_transaction_id
.clone(),
acct_number: card_details.card_number.clone(),
notification_url: request
.return_url
@@ -342,8 +351,8 @@ impl TryFrom<&ThreedsecureioRouterData<&types::authentication::ConnectorAuthenti
request.threeds_method_comp_ind.clone(),
),
three_dsrequestor_url: request.three_ds_requestor_url.clone(),
acquirer_bin: acquirer_details.acquirer_bin,
acquirer_merchant_id: acquirer_details.acquirer_merchant_id,
acquirer_bin,
acquirer_merchant_id,
card_expiry_date: card_details.get_expiry_date_as_yymm()?.expose(),
bill_addr_city: billing_address
.city
@@ -410,7 +419,7 @@ impl TryFrom<&ThreedsecureioRouterData<&types::authentication::ConnectorAuthenti
merchant_country_code: connector_meta_data.merchant_country_code,
merchant_name: connector_meta_data.merchant_name,
message_type: "AReq".to_string(),
message_version: authentication_data.message_version.clone(),
message_version: pre_authentication_data.message_version.clone(),
purchase_amount: item.amount.clone(),
purchase_currency: purchase_currency.numeric().to_string(),
trans_type: "01".to_string(),
@@ -644,7 +653,7 @@ impl From<ThreeDsCompletionIndicator> for ThreeDSecureIoThreeDsCompletionIndicat
}
}
impl From<ThreedsecureioTransStatus> for api_models::payments::TransactionStatus {
impl From<ThreedsecureioTransStatus> for common_enums::TransactionStatus {
fn from(value: ThreedsecureioTransStatus) -> Self {
match value {
ThreedsecureioTransStatus::Y => Self::Success,
@@ -707,41 +716,3 @@ impl TryFrom<&ThreedsecureioRouterData<&types::authentication::PreAuthNRouterDat
})
}
}
impl ForeignTryFrom<String> for (i64, i64, i64) {
type Error = error_stack::Report<errors::ConnectorError>;
fn foreign_try_from(value: String) -> Result<Self, Self::Error> {
let mut split_version = value.split('.');
let version_string = {
let major_version = split_version.next().ok_or(report!(
errors::ConnectorError::ResponseDeserializationFailed
))?;
let minor_version = split_version.next().ok_or(report!(
errors::ConnectorError::ResponseDeserializationFailed
))?;
let patch_version = split_version.next().ok_or(report!(
errors::ConnectorError::ResponseDeserializationFailed
))?;
(major_version, minor_version, patch_version)
};
let int_representation = {
let major_version = version_string
.0
.parse()
.into_report()
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let minor_version = version_string
.1
.parse()
.into_report()
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let patch_version = version_string
.2
.parse()
.into_report()
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
(major_version, minor_version, patch_version)
};
Ok(int_representation)
}
}

View File

@@ -32,7 +32,7 @@ pub async fn perform_authentication(
currency: Option<Currency>,
message_category: api::authentication::MessageCategory,
device_channel: payments::DeviceChannel,
authentication_data: (types::AuthenticationData, storage::Authentication),
authentication_data: storage::Authentication,
return_url: Option<String>,
sdk_information: Option<payments::SdkInformation>,
threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
@@ -59,8 +59,7 @@ pub async fn perform_authentication(
)?;
let response =
utils::do_auth_connector_call(state, authentication_connector.clone(), router_data).await?;
let (_authentication, _authentication_data) =
utils::update_trackers(state, response.clone(), authentication_data.1, None, None).await?;
utils::update_trackers(state, response.clone(), authentication_data, None, None).await?;
let authentication_response =
response
.response
@@ -115,42 +114,37 @@ pub async fn perform_post_authentication<F: Clone + Send>(
match authentication_flow_input {
types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
authentication_data: (authentication, authentication_data),
authentication,
should_continue_confirm_transaction,
} => {
// let (auth, authentication_data) = authentication;
let updated_authentication =
let authentication_status =
if !authentication.authentication_status.is_terminal_status() {
let router_data = transformers::construct_post_authentication_router_data(
authentication_connector.clone(),
business_profile,
merchant_connector_account,
authentication_data,
&authentication,
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector, router_data)
.await?;
let (updated_authentication, updated_authentication_data) =
utils::update_trackers(
state,
router_data,
authentication,
payment_data.token.clone(),
None,
)
.await?;
payment_data.authentication = Some((
updated_authentication.clone(),
updated_authentication_data.unwrap_or_default(),
));
updated_authentication
let updated_authentication = utils::update_trackers(
state,
router_data,
authentication,
payment_data.token.clone(),
None,
)
.await?;
let authentication_status = updated_authentication.authentication_status;
payment_data.authentication = Some(updated_authentication);
authentication_status
} else {
authentication
authentication.authentication_status
};
//If authentication is not successful, skip the payment connector flows and mark the payment as failure
if !(updated_authentication.authentication_status
== api_models::enums::AuthenticationStatus::Success)
{
if !(authentication_status == api_models::enums::AuthenticationStatus::Success) {
*should_continue_confirm_transaction = false;
}
}
@@ -198,7 +192,7 @@ pub async fn perform_pre_authentication<F: Clone + Send>(
.parse_value("AcquirerDetails")
.change_context(ApiErrorResponse::PreconditionFailed { message: "acquirer_bin and acquirer_merchant_id not found in Payment Connector's Metadata".to_string()})?;
let (authentication, authentication_data) = utils::update_trackers(
let authentication = utils::update_trackers(
state,
router_data,
authentication,
@@ -206,15 +200,12 @@ pub async fn perform_pre_authentication<F: Clone + Send>(
Some(acquirer_details),
)
.await?;
if authentication_data
.as_ref()
.is_some_and(|authentication_data| authentication_data.is_separate_authn_required())
if authentication.is_separate_authn_required()
|| authentication.authentication_status.is_failed()
{
*should_continue_confirm_transaction = false;
}
payment_data.authentication =
Some((authentication, authentication_data.unwrap_or_default()));
payment_data.authentication = Some(authentication);
}
types::PreAuthenthenticationFlowInput::PaymentMethodAuthNFlow {
card_number: _,

View File

@@ -10,7 +10,10 @@ use crate::{
errors::{self, RouterResult},
payments::helpers as payments_helpers,
},
types::{self, storage, transformers::ForeignFrom},
types::{
self, storage,
transformers::{ForeignFrom, ForeignTryFrom},
},
utils::ext_traits::OptionExt,
};
@@ -35,7 +38,7 @@ pub fn construct_authentication_router_data(
device_channel: payments::DeviceChannel,
business_profile: storage::BusinessProfile,
merchant_connector_account: payments_helpers::MerchantConnectorAccountType,
authentication_data: (super::types::AuthenticationData, storage::Authentication),
authentication_data: storage::Authentication,
return_url: Option<String>,
sdk_information: Option<api_models::payments::SdkInformation>,
threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
@@ -61,7 +64,9 @@ pub fn construct_authentication_router_data(
currency,
message_category,
device_channel,
authentication_data,
pre_authentication_data: super::types::PreAuthenticationData::foreign_try_from(
&authentication_data,
)?,
return_url,
sdk_information,
email,
@@ -82,10 +87,15 @@ pub fn construct_post_authentication_router_data(
authentication_connector: String,
business_profile: storage::BusinessProfile,
merchant_connector_account: payments_helpers::MerchantConnectorAccountType,
authentication_data: super::types::AuthenticationData,
authentication_data: &storage::Authentication,
) -> RouterResult<types::authentication::ConnectorPostAuthenticationRouterData> {
let threeds_server_transaction_id = authentication_data
.threeds_server_transaction_id
.clone()
.get_required_value("threeds_server_transaction_id")
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let router_request = types::authentication::ConnectorPostAuthenticationRequestData {
authentication_data,
threeds_server_transaction_id,
};
construct_router_data(
authentication_connector,
@@ -174,17 +184,17 @@ pub fn construct_router_data<F: Clone, Req, Res>(
})
}
impl ForeignFrom<payments::TransactionStatus> for common_enums::AuthenticationStatus {
fn foreign_from(trans_status: payments::TransactionStatus) -> Self {
impl ForeignFrom<common_enums::TransactionStatus> for common_enums::AuthenticationStatus {
fn foreign_from(trans_status: common_enums::TransactionStatus) -> Self {
match trans_status {
api_models::payments::TransactionStatus::Success => Self::Success,
api_models::payments::TransactionStatus::Failure
| api_models::payments::TransactionStatus::Rejected
| api_models::payments::TransactionStatus::VerificationNotPerformed
| api_models::payments::TransactionStatus::NotVerified => Self::Failed,
api_models::payments::TransactionStatus::ChallengeRequired
| api_models::payments::TransactionStatus::ChallengeRequiredDecoupledAuthentication
| api_models::payments::TransactionStatus::InformationOnly => Self::Pending,
common_enums::TransactionStatus::Success => Self::Success,
common_enums::TransactionStatus::Failure
| common_enums::TransactionStatus::Rejected
| common_enums::TransactionStatus::VerificationNotPerformed
| common_enums::TransactionStatus::NotVerified => Self::Failed,
common_enums::TransactionStatus::ChallengeRequired
| common_enums::TransactionStatus::ChallengeRequiredDecoupledAuthentication
| common_enums::TransactionStatus::InformationOnly => Self::Pending,
}
}
}

View File

@@ -1,9 +1,11 @@
use cards::CardNumber;
use error_stack::{Report, ResultExt};
use serde::{Deserialize, Serialize};
use crate::{
core::payments,
types::{authentication::AuthNFlowType, storage},
core::{errors, payments},
types::{storage, transformers::ForeignTryFrom},
utils::OptionExt,
};
pub enum PreAuthenthenticationFlowInput<'a, F: Clone> {
PaymentAuthNFlow {
@@ -20,7 +22,7 @@ pub enum PreAuthenthenticationFlowInput<'a, F: Clone> {
pub enum PostAuthenthenticationFlowInput<'a, F: Clone> {
PaymentAuthNFlow {
payment_data: &'a mut payments::PaymentData<F>,
authentication_data: (storage::Authentication, AuthenticationData),
authentication: storage::Authentication,
should_continue_confirm_transaction: &'a mut bool,
},
PaymentMethodAuthNFlow {
@@ -28,22 +30,36 @@ pub enum PostAuthenthenticationFlowInput<'a, F: Clone> {
},
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct AuthenticationData {
pub maximum_supported_version: (i64, i64, i64),
#[derive(Clone, Debug)]
pub struct PreAuthenticationData {
pub threeds_server_transaction_id: String,
pub cavv: Option<String>,
pub authn_flow_type: Option<AuthNFlowType>,
pub three_ds_method_data: ThreeDsMethodData,
pub message_version: String,
pub eci: Option<String>,
pub trans_status: api_models::payments::TransactionStatus,
pub acquirer_details: Option<AcquirerDetails>,
pub acquirer_bin: Option<String>,
pub acquirer_merchant_id: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
}
impl AuthenticationData {
pub fn is_separate_authn_required(&self) -> bool {
self.maximum_supported_version.0 == 2
impl ForeignTryFrom<&storage::Authentication> for PreAuthenticationData {
type Error = Report<errors::ApiErrorResponse>;
fn foreign_try_from(authentication: &storage::Authentication) -> Result<Self, Self::Error> {
let error_message = errors::ApiErrorResponse::UnprocessableEntity { message: "Pre Authentication must be completed successfully before Authentication can be performed".to_string() };
let threeds_server_transaction_id = authentication
.threeds_server_transaction_id
.clone()
.get_required_value("threeds_server_transaction_id")
.change_context(error_message)?;
let message_version = authentication
.message_version
.as_ref()
.get_required_value("message_version")?;
Ok(Self {
threeds_server_transaction_id,
message_version: message_version.to_string(),
acquirer_bin: authentication.acquirer_bin.clone(),
acquirer_merchant_id: authentication.acquirer_merchant_id.clone(),
connector_metadata: authentication.connector_metadata.clone(),
})
}
}

View File

@@ -1,12 +1,9 @@
use common_enums::DecoupledAuthenticationType;
use common_utils::ext_traits::{Encode, ValueExt};
use error_stack::ResultExt;
use super::types::{AuthenticationData, ThreeDsMethodData};
use crate::{
consts,
core::{
errors::{ApiErrorResponse, ConnectorErrorExt, StorageErrorExt},
errors::{self, ConnectorErrorExt, StorageErrorExt},
payments,
},
errors::RouterResult,
@@ -14,12 +11,11 @@ use crate::{
services::{self, execute_connector_processing_step},
types::{
api::{self, ConnectorCallType},
authentication::{AuthNFlowType, AuthenticationResponseData},
authentication::AuthenticationResponseData,
storage,
transformers::ForeignFrom,
RouterData,
},
utils::OptionExt,
};
pub fn get_connector_name_if_separate_authn_supported(
@@ -56,19 +52,8 @@ pub async fn update_trackers<F: Clone, Req>(
authentication: storage::Authentication,
token: Option<String>,
acquirer_details: Option<super::types::AcquirerDetails>,
) -> RouterResult<(storage::Authentication, Option<AuthenticationData>)> {
let authentication_data_option = authentication
.authentication_data
.as_ref()
.map(|authentication_data| {
authentication_data
.to_owned()
.parse_value::<AuthenticationData>("AuthenticationData")
.change_context(ApiErrorResponse::InternalServerError)
})
.transpose()?;
let (authentication_update, updated_authentication_data) = match router_data.response {
) -> RouterResult<storage::Authentication> {
let authentication_update = match router_data.response {
Ok(response) => match response {
AuthenticationResponseData::PreAuthNResponse {
threeds_server_transaction_id,
@@ -78,132 +63,70 @@ pub async fn update_trackers<F: Clone, Req>(
three_ds_method_url,
message_version,
connector_metadata,
} => {
let three_ds_method_data = ThreeDsMethodData {
three_ds_method_data,
three_ds_method_data_submission: three_ds_method_url.is_some(),
three_ds_method_url,
};
let authentication_data = AuthenticationData {
maximum_supported_version: maximum_supported_3ds_version,
threeds_server_transaction_id,
three_ds_method_data,
message_version,
acquirer_details,
..Default::default()
};
(
storage::AuthenticationUpdate::AuthenticationDataUpdate {
authentication_data: Some(
Encode::encode_to_value(&authentication_data)
.change_context(ApiErrorResponse::InternalServerError)?,
),
connector_authentication_id: Some(connector_authentication_id),
payment_method_id: token.map(|token| format!("eph_{}", token)),
authentication_type: None,
authentication_status: Some(common_enums::AuthenticationStatus::Started),
authentication_lifecycle_status: None,
connector_metadata,
},
Some(authentication_data),
)
}
} => storage::AuthenticationUpdate::PreAuthenticationUpdate {
threeds_server_transaction_id,
maximum_supported_3ds_version,
connector_authentication_id,
three_ds_method_data,
three_ds_method_url,
message_version,
connector_metadata,
authentication_status: common_enums::AuthenticationStatus::Pending,
payment_method_id: token.map(|token| format!("eph_{}", token)),
acquirer_bin: acquirer_details
.as_ref()
.map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
acquirer_merchant_id: acquirer_details
.map(|acquirer_details| acquirer_details.acquirer_merchant_id),
},
AuthenticationResponseData::AuthNResponse {
authn_flow_type,
authentication_value: cavv,
authentication_value,
trans_status,
} => {
let authentication_data = authentication_data_option
.get_required_value("authentication_data")
.attach_printable(
"AuthenticationData is required to make Authentication call",
)?;
let authentication_data = AuthenticationData {
authn_flow_type: Some(authn_flow_type.clone()),
cavv,
trans_status: trans_status.clone(),
..authentication_data
};
(
storage::AuthenticationUpdate::AuthenticationDataUpdate {
authentication_data: Some(
Encode::encode_to_value(&authentication_data)
.change_context(ApiErrorResponse::InternalServerError)?,
),
connector_authentication_id: None,
payment_method_id: None,
authentication_type: Some(match authn_flow_type {
AuthNFlowType::Challenge { .. } => {
DecoupledAuthenticationType::Challenge
}
AuthNFlowType::Frictionless => {
DecoupledAuthenticationType::Frictionless
}
}),
authentication_status: Some(
common_enums::AuthenticationStatus::foreign_from(trans_status),
),
authentication_lifecycle_status: None,
connector_metadata: None,
},
Some(authentication_data),
)
let authentication_status =
common_enums::AuthenticationStatus::foreign_from(trans_status.clone());
storage::AuthenticationUpdate::AuthenticationUpdate {
authentication_value,
trans_status,
acs_url: authn_flow_type.get_acs_url(),
challenge_request: authn_flow_type.get_challenge_request(),
acs_reference_number: authn_flow_type.get_acs_reference_number(),
acs_trans_id: authn_flow_type.get_acs_trans_id(),
acs_signed_content: authn_flow_type.get_acs_signed_content(),
authentication_type: authn_flow_type.get_decoupled_authentication_type(),
authentication_status,
}
}
AuthenticationResponseData::PostAuthNResponse {
trans_status,
authentication_value,
eci,
} => {
let authentication_data = authentication_data_option
.get_required_value("authentication_data")
.attach_printable(
"AuthenticationData is required to make Post Authentication call",
)?;
let authentication_data = AuthenticationData {
cavv: authentication_value,
eci,
trans_status: trans_status.clone(),
..authentication_data
};
(
storage::AuthenticationUpdate::AuthenticationDataUpdate {
authentication_data: Some(
Encode::encode_to_value(&authentication_data)
.change_context(ApiErrorResponse::InternalServerError)?,
),
connector_authentication_id: None,
payment_method_id: None,
authentication_type: None,
authentication_status: Some(
common_enums::AuthenticationStatus::foreign_from(trans_status),
),
authentication_lifecycle_status: None,
connector_metadata: None,
},
Some(authentication_data),
)
}
},
Err(error) => (
storage::AuthenticationUpdate::ErrorUpdate {
connector_authentication_id: error.connector_transaction_id,
authentication_status: common_enums::AuthenticationStatus::Failed,
error_message: Some(error.message),
error_code: Some(error.code),
} => storage::AuthenticationUpdate::PostAuthenticationUpdate {
authentication_status: common_enums::AuthenticationStatus::foreign_from(
trans_status.clone(),
),
trans_status,
authentication_value,
eci,
},
authentication_data_option,
),
},
Err(error) => storage::AuthenticationUpdate::ErrorUpdate {
connector_authentication_id: error.connector_transaction_id,
authentication_status: common_enums::AuthenticationStatus::Failed,
error_message: Some(error.message),
error_code: Some(error.code),
},
};
let authentication_result = state
state
.store
.update_authentication_by_merchant_id_authentication_id(
authentication,
authentication_update,
)
.await
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Error while updating authentication");
authentication_result.map(|authentication| (authentication, updated_authentication_data))
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while updating authentication")
}
impl ForeignFrom<common_enums::AuthenticationStatus> for common_enums::AttemptStatus {
@@ -229,7 +152,6 @@ pub async fn create_new_authentication(
merchant_id,
authentication_connector,
connector_authentication_id: None,
authentication_data: None,
payment_method_id: "".into(),
authentication_type: None,
authentication_status: common_enums::AuthenticationStatus::Started,
@@ -237,12 +159,29 @@ pub async fn create_new_authentication(
error_message: None,
error_code: None,
connector_metadata: None,
maximum_supported_version: None,
threeds_server_transaction_id: None,
cavv: None,
authentication_flow_type: None,
message_version: None,
eci: None,
trans_status: None,
acquirer_bin: None,
acquirer_merchant_id: None,
three_ds_method_data: None,
three_ds_method_url: None,
acs_url: None,
challenge_request: None,
acs_reference_number: None,
acs_trans_id: None,
three_dsserver_trans_id: None,
acs_signed_content: None,
};
state
.store
.insert_authentication(new_authorization)
.await
.to_duplicate_response(ApiErrorResponse::GenericDuplicateError {
.to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
message: format!(
"Authentication with authentication_id {} already exists",
authentication_id

View File

@@ -45,8 +45,7 @@ use self::{
routing::{self as self_routing, SessionFlowRoutingInput},
};
use super::{
authentication::types::AuthenticationData, errors::StorageErrorExt,
payment_methods::surcharge_decision_configs, routing::TransactionData,
errors::StorageErrorExt, payment_methods::surcharge_decision_configs, routing::TransactionData,
};
#[cfg(feature = "frm")]
use crate::core::fraud_check as frm_core;
@@ -2220,7 +2219,7 @@ where
pub payment_link_data: Option<api_models::payments::PaymentLinkResponse>,
pub incremental_authorization_details: Option<IncrementalAuthorizationDetails>,
pub authorizations: Vec<diesel_models::authorization::Authorization>,
pub authentication: Option<(storage::Authentication, AuthenticationData)>,
pub authentication: Option<storage::Authentication>,
pub frm_metadata: Option<serde_json::Value>,
}
@@ -3383,12 +3382,6 @@ pub async fn payment_external_authentication(
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while fetching authentication record")?;
let authentication_data: AuthenticationData = authentication
.authentication_data
.clone()
.parse_value("authentication data")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while parsing authentication_data")?;
let payment_method_details = helpers::get_payment_method_details_from_payment_token(
&state,
&payment_attempt,
@@ -3446,7 +3439,7 @@ pub async fn payment_external_authentication(
Some(currency),
authentication::MessageCategory::Payment,
req.device_channel,
(authentication_data, authentication),
authentication,
return_url,
req.sdk_information,
req.threeds_method_comp_ind,

View File

@@ -12,7 +12,7 @@ use tracing_futures::Instrument;
use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
core::{
authentication::{self, types},
authentication,
blocklist::utils as blocklist_utils,
errors::{self, CustomResult, RouterResult, StorageErrorExt},
payment_methods::PaymentMethodRetrieve,
@@ -554,27 +554,18 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
.payment_method_data
.apply_additional_payment_data(additional_payment_data)
});
let authentication = match payment_attempt.authentication_id.clone() {
Some(authentication_id) => {
let authentication = state
.store
.find_authentication_by_merchant_id_authentication_id(
merchant_id.to_string(),
authentication_id.clone(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?;
let authentication_data: authentication::types::AuthenticationData = authentication
.authentication_data
.clone()
.parse_value("authentication data")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while parsing authentication_data")?;
Some((authentication, authentication_data))
}
None => None,
};
let authentication = payment_attempt.authentication_id.as_ref().async_map(|authentication_id| async move {
state
.store
.find_authentication_by_merchant_id_authentication_id(
merchant_id.to_string(),
authentication_id.clone(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))
}).await
.transpose()?;
payment_attempt.payment_method_billing_address_id = payment_method_billing
.as_ref()
@@ -806,7 +797,7 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
None,
)
.await?;
if let Some(authentication_data) = authentication {
if let Some(authentication) = authentication {
// call post authn service
authentication::perform_post_authentication(
state,
@@ -815,7 +806,7 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
authentication_connector_mca,
authentication::types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
authentication_data,
authentication,
should_continue_confirm_transaction,
},
)
@@ -924,10 +915,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
};
let status_handler_for_authentication_results =
|(authentication, authentication_data): &(
storage::Authentication,
types::AuthenticationData,
)| {
|authentication: &storage::Authentication| {
if authentication.authentication_status.is_failed() {
(
storage_enums::IntentStatus::Failed,
@@ -937,7 +925,7 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
Some(Some("external authentication failure".to_string())),
),
)
} else if authentication_data.is_separate_authn_required() {
} else if authentication.is_separate_authn_required() {
(
storage_enums::IntentStatus::RequiresCustomerAction,
storage_enums::AttemptStatus::AuthenticationPending,
@@ -1038,8 +1026,8 @@ impl<F: Clone, Ctx: PaymentMethodRetrieve>
authentication_connector,
authentication_id,
) = match payment_data.authentication.as_ref() {
Some((authentication, authentication_data)) => (
Some(authentication_data.is_separate_authn_required()),
Some(authentication) => (
Some(authentication.is_separate_authn_required()),
Some(authentication.authentication_connector.clone()),
Some(authentication.authentication_id.clone()),
),

View File

@@ -793,7 +793,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
payment_data.payment_attempt = payment_attempt;
payment_data.authentication = match payment_data.authentication {
Some((authentication, authentication_data)) => {
Some(authentication) => {
let authentication_update = storage::AuthenticationUpdate::PostAuthorizationUpdate {
authentication_lifecycle_status:
storage::enums::AuthenticationLifecycleStatus::Used,
@@ -806,7 +806,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
Some((updated_authentication, authentication_data))
Some(updated_authentication)
}
None => None,
};

View File

@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use api_models::enums::FrmSuggestion;
use async_trait::async_trait;
use common_utils::ext_traits::{AsyncExt, ValueExt};
use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@@ -10,7 +10,6 @@ use router_env::{instrument, tracing};
use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
core::{
authentication,
errors::{self, CustomResult, RouterResult, StorageErrorExt},
payment_methods::PaymentMethodRetrieve,
payments::{
@@ -400,33 +399,17 @@ async fn get_tracker_for_sync<
.to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
id: profile_id.to_string(),
})?;
let authentication = match payment_attempt.authentication_id.clone() {
Some(authentication_id) => {
let authentication = db
.find_authentication_by_merchant_id_authentication_id(
merchant_account.merchant_id.to_string(),
let merchant_id = payment_intent.merchant_id.clone();
let authentication = payment_attempt.authentication_id.clone().async_map(|authentication_id| async move {
db.find_authentication_by_merchant_id_authentication_id(
merchant_id,
authentication_id.clone(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?;
let authentication_data: authentication::types::AuthenticationData = authentication
.authentication_data
.clone()
.map(|authentication_data_value| {
authentication_data_value
.parse_value("AuthenticationData")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while parsing authentication_data")
})
.transpose()?
.unwrap_or_default();
Some((authentication, authentication_data))
}
None => None,
};
.attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))
}).await
.transpose()?;
let payment_data = PaymentData {
flow: PhantomData,

View File

@@ -8,7 +8,7 @@ use error_stack::{report, IntoReport, ResultExt};
use masking::Maskable;
use router_env::{instrument, tracing};
use super::{flows::Feature, PaymentData};
use super::{flows::Feature, types::AuthenticationData, PaymentData};
use crate::{
configs::settings::{ConnectorRequestReferenceIdConfig, Server},
connector::{Helcim, Nexinets},
@@ -567,35 +567,29 @@ where
}
}))
.or(match payment_data.authentication.as_ref(){
Some((_authentication, authentication_data)) => {
if payment_intent.status == common_enums::IntentStatus::RequiresCustomerAction && authentication_data.cavv.is_none() && authentication_data.is_separate_authn_required(){
Some(authentication) => {
if payment_intent.status == common_enums::IntentStatus::RequiresCustomerAction && authentication.cavv.is_none() && authentication.is_separate_authn_required(){
// if preAuthn and separate authentication needed.
let payment_connector_name = payment_attempt.connector
.as_ref()
.get_required_value("connector")?;
Some(api_models::payments::NextActionData::ThreeDsInvoke {
three_ds_data: api_models::payments::ThreeDsData {
three_ds_authentication_url: helpers::create_authentication_url(
&server.base_url,
&payment_attempt,
),
three_ds_authentication_url: helpers::create_authentication_url(&server.base_url, &payment_attempt),
three_ds_authorize_url: helpers::create_authorize_url(
&server.base_url,
&payment_attempt,
payment_connector_name,
),
three_ds_method_details: authentication_data.three_ds_method_data.three_ds_method_url.as_ref().map(|three_ds_method_url|{
three_ds_method_details: authentication.three_ds_method_url.as_ref().zip(authentication.three_ds_method_data.as_ref()).map(|(three_ds_method_url,three_ds_method_data )|{
api_models::payments::ThreeDsMethodData::AcsThreeDsMethodData {
three_ds_method_data_submission: true,
three_ds_method_data: authentication_data
.three_ds_method_data
.three_ds_method_data
.clone(),
three_ds_method_data: Some(three_ds_method_data.clone()),
three_ds_method_url: Some(three_ds_method_url.to_owned()),
}
}).unwrap_or(api_models::payments::ThreeDsMethodData::AcsThreeDsMethodData {
three_ds_method_data_submission: false,
three_ds_method_data: "".into(),
three_ds_method_data: None,
three_ds_method_url: None,
}),
},
@@ -1212,7 +1206,11 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthoriz
| Some(RequestIncrementalAuthorization::Default)
),
metadata: additional_data.payment_data.payment_intent.metadata,
authentication_data: payment_data.authentication.map(|auth| auth.1),
authentication_data: payment_data
.authentication
.as_ref()
.map(AuthenticationData::foreign_try_from)
.transpose()?,
customer_acceptance: payment_data.customer_acceptance,
})
}

View File

@@ -1,7 +1,12 @@
use std::{collections::HashMap, num::TryFromIntError};
use api_models::{payment_methods::SurchargeDetailsResponse, payments::RequestSurchargeDetails};
use common_utils::{consts, errors::CustomResult, ext_traits::Encode, types as common_types};
use common_utils::{
consts,
errors::CustomResult,
ext_traits::{Encode, OptionExt},
types as common_types,
};
use data_models::payments::payment_attempt::PaymentAttempt;
use diesel_models::business_profile::BusinessProfile;
use error_stack::{IntoReport, ResultExt};
@@ -371,3 +376,53 @@ impl SurchargeMetadata {
.await
}
}
#[derive(Debug, Clone)]
pub struct AuthenticationData {
pub eci: String,
pub cavv: String,
pub threeds_server_transaction_id: String,
pub message_version: String,
}
impl ForeignTryFrom<&storage::Authentication> for AuthenticationData {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn foreign_try_from(authentication: &storage::Authentication) -> Result<Self, Self::Error> {
if authentication.authentication_status == common_enums::AuthenticationStatus::Success {
let threeds_server_transaction_id = authentication
.threeds_server_transaction_id
.clone()
.get_required_value("threeds_server_transaction_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("threeds_server_transaction_id must not be null when authentication_status is success")?;
let message_version = authentication
.message_version
.clone()
.get_required_value("message_version")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"message_version must not be null when authentication_status is success",
)?;
let cavv = authentication
.cavv
.clone()
.get_required_value("cavv")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("cavv must not be null when authentication_status is success")?;
let eci = authentication
.eci
.clone()
.get_required_value("eci")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("eci must not be null when authentication_status is success")?;
Ok(Self {
eci,
cavv,
threeds_server_transaction_id,
message_version: message_version.to_string(),
})
} else {
Err(errors::ApiErrorResponse::PaymentAuthenticationFailed { data: None }.into())
}
}
}

View File

@@ -103,13 +103,30 @@ impl AuthenticationInterface for MockDb {
authentication_status: authentication.authentication_status,
authentication_connector: authentication.authentication_connector,
connector_authentication_id: authentication.connector_authentication_id,
authentication_data: authentication.authentication_data,
authentication_data: None,
payment_method_id: authentication.payment_method_id,
authentication_type: authentication.authentication_type,
authentication_lifecycle_status: authentication.authentication_lifecycle_status,
error_code: authentication.error_code,
error_message: authentication.error_message,
connector_metadata: authentication.connector_metadata,
maximum_supported_version: authentication.maximum_supported_version,
threeds_server_transaction_id: authentication.threeds_server_transaction_id,
cavv: authentication.cavv,
authentication_flow_type: authentication.authentication_flow_type,
message_version: authentication.message_version,
eci: authentication.eci,
trans_status: authentication.trans_status,
acquirer_bin: authentication.acquirer_bin,
acquirer_merchant_id: authentication.acquirer_merchant_id,
three_ds_method_data: authentication.three_ds_method_data,
three_ds_method_url: authentication.three_ds_method_url,
acs_url: authentication.acs_url,
challenge_request: authentication.challenge_request,
acs_reference_number: authentication.acs_reference_number,
acs_trans_id: authentication.acs_trans_id,
three_ds_server_trans_id: authentication.three_dsserver_trans_id,
acs_signed_content: authentication.acs_signed_content,
};
authentications.push(authentication.clone());
Ok(authentication)

View File

@@ -36,15 +36,13 @@ pub use crate::core::payments::{payment_address::PaymentAddress, CustomerDetails
use crate::core::utils::IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLOW;
use crate::{
core::{
authentication as authentication_core,
errors::{self, RouterResult},
payments::{types, PaymentData, RecurringMandatePaymentData},
},
services,
types::transformers::ForeignFrom,
types::{transformers::ForeignFrom, types::AuthenticationData},
utils::OptionExt,
};
pub type PaymentsAuthorizeRouterData =
RouterData<api::Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
pub type PaymentsPreProcessingRouterData =
@@ -425,7 +423,7 @@ pub struct PaymentsAuthorizeData {
pub customer_id: Option<String>,
pub request_incremental_authorization: bool,
pub metadata: Option<pii::SecretSerdeValue>,
pub authentication_data: Option<authentication_core::types::AuthenticationData>,
pub authentication_data: Option<AuthenticationData>,
}
#[derive(Debug, Clone, Default)]

View File

@@ -25,7 +25,7 @@ pub struct AcquirerDetails {
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
pub struct AuthenticationResponse {
pub trans_status: api_models::payments::TransactionStatus,
pub trans_status: common_enums::TransactionStatus,
pub acs_url: Option<url::Url>,
pub challenge_request: Option<String>,
pub acs_reference_number: Option<String>,

View File

@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use super::{
api::{self, authentication},
storage, BrowserInformation, RouterData,
BrowserInformation, RouterData,
};
use crate::services;
@@ -13,20 +13,20 @@ use crate::services;
pub enum AuthenticationResponseData {
PreAuthNResponse {
threeds_server_transaction_id: String,
maximum_supported_3ds_version: (i64, i64, i64),
maximum_supported_3ds_version: common_utils::types::SemanticVersion,
connector_authentication_id: String,
three_ds_method_data: String,
three_ds_method_url: Option<String>,
message_version: String,
message_version: common_utils::types::SemanticVersion,
connector_metadata: Option<serde_json::Value>,
},
AuthNResponse {
authn_flow_type: AuthNFlowType,
authentication_value: Option<String>,
trans_status: api_models::payments::TransactionStatus,
trans_status: common_enums::TransactionStatus,
},
PostAuthNResponse {
trans_status: api_models::payments::TransactionStatus,
trans_status: common_enums::TransactionStatus,
authentication_value: Option<String>,
eci: Option<String>,
},
@@ -48,6 +48,50 @@ pub enum AuthNFlowType {
Frictionless,
}
impl AuthNFlowType {
pub fn get_acs_url(&self) -> Option<String> {
if let Self::Challenge(challenge_params) = self {
challenge_params.acs_url.as_ref().map(ToString::to_string)
} else {
None
}
}
pub fn get_challenge_request(&self) -> Option<String> {
if let Self::Challenge(challenge_params) = self {
challenge_params.challenge_request.clone()
} else {
None
}
}
pub fn get_acs_reference_number(&self) -> Option<String> {
if let Self::Challenge(challenge_params) = self {
challenge_params.acs_reference_number.clone()
} else {
None
}
}
pub fn get_acs_trans_id(&self) -> Option<String> {
if let Self::Challenge(challenge_params) = self {
challenge_params.acs_trans_id.clone()
} else {
None
}
}
pub fn get_acs_signed_content(&self) -> Option<String> {
if let Self::Challenge(challenge_params) = self {
challenge_params.acs_signed_content.clone()
} else {
None
}
}
pub fn get_decoupled_authentication_type(&self) -> common_enums::DecoupledAuthenticationType {
match self {
Self::Challenge(_) => common_enums::DecoupledAuthenticationType::Challenge,
Self::Frictionless => common_enums::DecoupledAuthenticationType::Frictionless,
}
}
}
#[derive(Clone, Default, Debug)]
pub struct PreAuthNRequestData {
// card number
@@ -65,10 +109,7 @@ pub struct ConnectorAuthenticationRequestData {
pub currency: Option<common_enums::Currency>,
pub message_category: authentication::MessageCategory,
pub device_channel: api_models::payments::DeviceChannel,
pub authentication_data: (
crate::core::authentication::types::AuthenticationData,
storage::Authentication,
),
pub pre_authentication_data: crate::core::authentication::types::PreAuthenticationData,
pub return_url: Option<String>,
pub sdk_information: Option<api_models::payments::SdkInformation>,
pub email: Option<Email>,
@@ -78,7 +119,7 @@ pub struct ConnectorAuthenticationRequestData {
#[derive(Clone, Debug)]
pub struct ConnectorPostAuthenticationRequestData {
pub authentication_data: crate::core::authentication::types::AuthenticationData,
pub threeds_server_transaction_id: String,
}
pub type PreAuthNRouterData =

View File

@@ -14,7 +14,7 @@ use masking::{ExposeInterface, PeekInterface};
use super::domain;
use crate::{
core::{authentication::types::AuthenticationData, errors},
core::errors,
services::authentication::get_header_value_by_key,
types::{
api::{self as api_types, routing as routing_types},
@@ -745,31 +745,20 @@ impl ForeignFrom<storage::Authorization> for payments::IncrementalAuthorizationR
}
}
impl ForeignFrom<&(storage::Authentication, AuthenticationData)>
for payments::ExternalAuthenticationDetailsResponse
{
fn foreign_from(authn_data: &(storage::Authentication, AuthenticationData)) -> Self {
let (ds_transaction_id, version) = if authn_data.0.authentication_data.is_some() {
(
Some(authn_data.1.threeds_server_transaction_id.clone()),
Some(format!(
"{}.{}.{}",
authn_data.1.maximum_supported_version.0,
authn_data.1.maximum_supported_version.1,
authn_data.1.maximum_supported_version.2
)),
)
} else {
(None, None)
};
impl ForeignFrom<&storage::Authentication> for payments::ExternalAuthenticationDetailsResponse {
fn foreign_from(authn_data: &storage::Authentication) -> Self {
let version = authn_data
.maximum_supported_version
.as_ref()
.map(|version| version.to_string());
Self {
authentication_flow: authn_data.0.authentication_type,
electronic_commerce_indicator: authn_data.1.eci.clone(),
status: authn_data.0.authentication_status,
ds_transaction_id,
authentication_flow: authn_data.authentication_type,
electronic_commerce_indicator: authn_data.eci.clone(),
status: authn_data.authentication_status,
ds_transaction_id: authn_data.threeds_server_transaction_id.clone(),
version,
error_code: authn_data.0.error_code.clone(),
error_message: authn_data.0.error_message.clone(),
error_code: authn_data.error_code.clone(),
error_message: authn_data.error_message.clone(),
}
}
}

View File

@@ -0,0 +1,19 @@
-- This file should undo anything in `up.sql`
ALTER TABLE authentication
DROP COLUMN IF EXISTS maximum_supported_version ,
DROP COLUMN IF EXISTS threeds_server_transaction_id ,
DROP COLUMN IF EXISTS cavv ,
DROP COLUMN IF EXISTS authentication_flow_type ,
DROP COLUMN IF EXISTS message_version ,
DROP COLUMN IF EXISTS eci ,
DROP COLUMN IF EXISTS trans_status ,
DROP COLUMN IF EXISTS acquirer_bin ,
DROP COLUMN IF EXISTS acquirer_merchant_id ,
DROP COLUMN IF EXISTS three_ds_method_data ,
DROP COLUMN IF EXISTS three_ds_method_url ,
DROP COLUMN IF EXISTS acs_url ,
DROP COLUMN IF EXISTS challenge_request ,
DROP COLUMN IF EXISTS acs_reference_number ,
DROP COLUMN IF EXISTS acs_trans_id ,
DROP COLUMN IF EXISTS three_dsserver_trans_id ,
DROP COLUMN IF EXISTS acs_signed_content;

View File

@@ -0,0 +1,20 @@
-- Your SQL goes here
ALTER TABLE authentication
ADD COLUMN IF NOT EXISTS maximum_supported_version JSONB,
ADD COLUMN IF NOT EXISTS threeds_server_transaction_id VARCHAR(64),
ADD COLUMN IF NOT EXISTS cavv VARCHAR(64),
ADD COLUMN IF NOT EXISTS authentication_flow_type VARCHAR(64),
ADD COLUMN IF NOT EXISTS message_version JSONB,
ADD COLUMN IF NOT EXISTS eci VARCHAR(64),
ADD COLUMN IF NOT EXISTS trans_status VARCHAR(64),
ADD COLUMN IF NOT EXISTS acquirer_bin VARCHAR(64),
ADD COLUMN IF NOT EXISTS acquirer_merchant_id VARCHAR(64),
ADD COLUMN IF NOT EXISTS three_ds_method_data VARCHAR,
ADD COLUMN IF NOT EXISTS three_ds_method_url VARCHAR,
ADD COLUMN IF NOT EXISTS acs_url VARCHAR,
ADD COLUMN IF NOT EXISTS challenge_request VARCHAR,
ADD COLUMN IF NOT EXISTS acs_reference_number VARCHAR,
ADD COLUMN IF NOT EXISTS acs_trans_id VARCHAR,
ADD COLUMN IF NOT EXISTS three_dsserver_trans_id VARCHAR,
ADD COLUMN IF NOT EXISTS acs_signed_content VARCHAR,
ADD COLUMN IF NOT EXISTS connector_metadata JSONB;

View File

@@ -16872,7 +16872,6 @@
"type": "object",
"required": [
"three_ds_method_data_submission",
"three_ds_method_data",
"three_ds_method_key"
],
"properties": {
@@ -16882,7 +16881,8 @@
},
"three_ds_method_data": {
"type": "string",
"description": "ThreeDS method data"
"description": "ThreeDS method data",
"nullable": true
},
"three_ds_method_url": {
"type": "string",