refactor(core): refactor authentication core to fetch authentication only within it (#4138)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Hrithikesh
2024-05-07 13:38:53 +05:30
committed by GitHub
parent f63a97024c
commit 71a070e269
8 changed files with 339 additions and 398 deletions

4
Cargo.lock generated
View File

@ -7040,9 +7040,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.35"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",

View File

@ -177,8 +177,8 @@ impl Connector {
matches!(self, Self::Checkout)
}
pub fn is_separate_authentication_supported(&self) -> bool {
#[cfg(feature = "dummy_connector")]
match self {
#[cfg(feature = "dummy_connector")]
Self::DummyConnector1
| Self::DummyConnector2
| Self::DummyConnector3
@ -243,66 +243,6 @@ impl Connector {
| Self::Stripe => false,
Self::Checkout | Self::Nmi => true,
}
#[cfg(not(feature = "dummy_connector"))]
match self {
Self::Aci
| Self::Adyen
| Self::Airwallex
| Self::Authorizedotnet
| Self::Bambora
| Self::Bankofamerica
| Self::Billwerk
| Self::Bitpay
| Self::Bluesnap
| Self::Boku
| Self::Braintree
| Self::Cashtocode
| Self::Coinbase
| Self::Cryptopay
| Self::Dlocal
| Self::Ebanx
| Self::Fiserv
| Self::Forte
| Self::Globalpay
| Self::Globepay
| Self::Gocardless
| Self::Helcim
| Self::Iatapay
| Self::Klarna
| Self::Mollie
| Self::Multisafepay
| Self::Nexinets
| Self::Nmi
| Self::Nuvei
| Self::Opennode
| Self::Payme
| Self::Paypal
| Self::Payu
| Self::Placetopay
| Self::Powertranz
| Self::Prophetpay
| Self::Rapyd
| Self::Shift4
| Self::Square
| Self::Stax
| Self::Trustpay
| Self::Tsys
| Self::Volt
| Self::Wise
| Self::Worldline
| Self::Worldpay
| Self::Zen
| Self::Zsl
| Self::Signifyd
| Self::Plaid
| Self::Riskified
| Self::Threedsecureio
| Self::Cybersource
| Self::Noon
| Self::Netcetera
| Self::Stripe => false,
Self::Checkout => true,
}
}
}

View File

@ -101,7 +101,6 @@ pub enum AuthenticationUpdate {
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>,
},
@ -309,7 +308,6 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
message_version,
connector_metadata,
authentication_status,
payment_method_id,
acquirer_bin,
acquirer_merchant_id,
} => Self {
@ -321,7 +319,6 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
message_version: Some(message_version),
connector_metadata,
authentication_status: Some(authentication_status),
payment_method_id,
acquirer_bin,
acquirer_merchant_id,
..Default::default()

View File

@ -5,19 +5,16 @@ pub mod types;
use api_models::payments;
use common_enums::Currency;
use common_utils::{
errors::CustomResult,
ext_traits::{Encode, StringExt, ValueExt},
};
use error_stack::{report, ResultExt};
use masking::{ExposeInterface, PeekInterface};
use common_utils::errors::CustomResult;
use error_stack::ResultExt;
use masking::ExposeInterface;
use super::errors;
use super::errors::{self, StorageErrorExt};
use crate::{
core::{errors::ApiErrorResponse, payments as payments_core},
routes::AppState,
types::{self as core_types, api, authentication::AuthenticationResponseData, storage},
utils::{check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata, OptionExt},
types::{self as core_types, api, domain, storage},
utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata,
};
#[allow(clippy::too_many_arguments)]
@ -64,133 +61,76 @@ pub async fn perform_authentication(
)?;
let response =
utils::do_auth_connector_call(state, authentication_connector.clone(), router_data).await?;
utils::update_trackers(state, response.clone(), authentication_data, None, None).await?;
let authentication_response =
response
.response
.map_err(|err| ApiErrorResponse::ExternalConnectorError {
code: err.code,
message: err.message,
connector: authentication_connector,
status_code: err.status_code,
reason: err.reason,
})?;
match authentication_response {
AuthenticationResponseData::AuthNResponse {
authn_flow_type,
trans_status,
..
} => Ok(match authn_flow_type {
core_types::authentication::AuthNFlowType::Challenge(challenge_params) => {
core_types::api::AuthenticationResponse {
trans_status,
acs_url: challenge_params.acs_url,
challenge_request: challenge_params.challenge_request,
acs_reference_number: challenge_params.acs_reference_number,
acs_trans_id: challenge_params.acs_trans_id,
three_dsserver_trans_id: challenge_params.three_dsserver_trans_id,
acs_signed_content: challenge_params.acs_signed_content,
}
}
core_types::authentication::AuthNFlowType::Frictionless => {
core_types::api::AuthenticationResponse {
trans_status,
acs_url: None,
challenge_request: None,
acs_reference_number: None,
acs_trans_id: None,
three_dsserver_trans_id: None,
acs_signed_content: None,
}
}
}),
_ => Err(report!(errors::ApiErrorResponse::InternalServerError))
.attach_printable("unexpected response in authentication flow")?,
}
let authentication =
utils::update_trackers(state, response.clone(), authentication_data, None).await?;
response
.response
.map_err(|err| ApiErrorResponse::ExternalConnectorError {
code: err.code,
message: err.message,
connector: authentication_connector,
status_code: err.status_code,
reason: err.reason,
})?;
core_types::api::authentication::AuthenticationResponse::try_from(authentication)
}
pub async fn perform_post_authentication<F: Clone + Send>(
pub async fn perform_post_authentication(
state: &AppState,
authentication_connector: String,
key_store: &domain::MerchantKeyStore,
business_profile: core_types::storage::BusinessProfile,
merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType,
authentication_flow_input: types::PostAuthenthenticationFlowInput<'_, F>,
) -> CustomResult<(), ApiErrorResponse> {
match authentication_flow_input {
types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
authentication,
should_continue_confirm_transaction,
} => {
let is_pull_mechanism_enabled =
check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
merchant_connector_account
.get_metadata()
.map(|metadata| metadata.expose()),
);
let authentication_status =
if !authentication.authentication_status.is_terminal_status()
&& is_pull_mechanism_enabled
{
let router_data = transformers::construct_post_authentication_router_data(
authentication_connector.clone(),
business_profile.clone(),
merchant_connector_account,
&authentication,
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector, router_data)
.await?;
let updated_authentication = utils::update_trackers(
state,
router_data,
authentication.clone(),
payment_data.token.clone(),
None,
)
.await?;
let authentication_status = updated_authentication.authentication_status;
payment_data.authentication = Some(updated_authentication);
authentication_status
} else {
authentication.authentication_status
};
//If authentication is not successful, skip the payment connector flows and mark the payment as failure
if !(authentication_status == api_models::enums::AuthenticationStatus::Success) {
*should_continue_confirm_transaction = false;
}
}
types::PostAuthenthenticationFlowInput::PaymentMethodAuthNFlow { other_fields: _ } => {
// todo!("Payment method post authN operation");
}
}
Ok(())
}
fn get_payment_id_from_pre_authentication_flow_input<F: Clone + Send>(
pre_authentication_flow_input: &types::PreAuthenthenticationFlowInput<'_, F>,
) -> Option<String> {
match pre_authentication_flow_input {
types::PreAuthenthenticationFlowInput::PaymentAuthNFlow { payment_data, .. } => {
Some(payment_data.payment_intent.payment_id.clone())
}
_ => None,
authentication_id: String,
) -> CustomResult<storage::Authentication, ApiErrorResponse> {
let (authentication_connector, three_ds_connector_account) =
utils::get_authentication_connector_data(state, key_store, &business_profile).await?;
let is_pull_mechanism_enabled =
check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
three_ds_connector_account
.get_metadata()
.map(|metadata| metadata.expose()),
);
let authentication = state
.store
.find_authentication_by_merchant_id_authentication_id(
business_profile.merchant_id.clone(),
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}"))?;
if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled {
let router_data = transformers::construct_post_authentication_router_data(
authentication_connector.to_string(),
business_profile,
three_ds_connector_account,
&authentication,
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector.to_string(), router_data)
.await?;
utils::update_trackers(state, router_data, authentication, None).await
} else {
Ok(authentication)
}
}
pub async fn perform_pre_authentication<F: Clone + Send>(
pub async fn perform_pre_authentication(
state: &AppState,
authentication_connector_name: String,
authentication_flow_input: types::PreAuthenthenticationFlowInput<'_, F>,
key_store: &domain::MerchantKeyStore,
card_number: cards::CardNumber,
token: String,
business_profile: &core_types::storage::BusinessProfile,
three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType,
payment_connector_account: payments_core::helpers::MerchantConnectorAccountType,
) -> CustomResult<(), ApiErrorResponse> {
let payment_id = get_payment_id_from_pre_authentication_flow_input(&authentication_flow_input);
acquirer_details: Option<types::AcquirerDetails>,
payment_id: Option<String>,
) -> CustomResult<storage::Authentication, ApiErrorResponse> {
let (authentication_connector, three_ds_connector_account) =
utils::get_authentication_connector_data(state, key_store, business_profile).await?;
let authentication_connector_name = authentication_connector.to_string();
let authentication = utils::create_new_authentication(
state,
business_profile.merchant_id.clone(),
authentication_connector_name.clone(),
token,
business_profile.profile_id.clone(),
payment_id,
three_ds_connector_account
@ -199,74 +139,15 @@ pub async fn perform_pre_authentication<F: Clone + Send>(
.attach_printable("Error while finding mca_id from merchant_connector_account")?,
)
.await?;
match authentication_flow_input {
types::PreAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
should_continue_confirm_transaction,
card_number,
} => {
let router_data = transformers::construct_pre_authentication_router_data(
authentication_connector_name.clone(),
card_number,
&three_ds_connector_account,
business_profile.merchant_id.clone(),
)?;
let router_data = utils::do_auth_connector_call(
state,
authentication_connector_name.clone(),
router_data,
)
.await?;
let acquirer_details: types::AcquirerDetails = payment_connector_account
.get_metadata()
.get_required_value("merchant_connector_account.metadata")?
.peek()
.clone()
.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 = utils::update_trackers(
state,
router_data,
authentication,
payment_data.token.clone(),
Some(acquirer_details),
)
.await?;
if authentication.is_separate_authn_required()
|| authentication.authentication_status.is_failed()
{
*should_continue_confirm_transaction = false;
// If flow is going through external authentication, set the poll_config in payment_data which can be fetched while sending next_action block in confirm response
let default_poll_config = core_types::PollConfig::default();
let default_config_str = default_poll_config
.encode_to_string_of_json()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while stringifying default poll config")?;
let poll_config = state
.store
.find_config_by_key_unwrap_or(
&core_types::PollConfig::get_poll_config_key(authentication_connector_name),
Some(default_config_str),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("The poll config was not found in the DB")?;
let poll_config: core_types::PollConfig = poll_config
.config
.parse_struct("PollConfig")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while parsing PollConfig")?;
payment_data.poll_config = Some(poll_config)
}
payment_data.authentication = Some(authentication);
}
types::PreAuthenthenticationFlowInput::PaymentMethodAuthNFlow {
card_number: _,
other_fields: _,
} => {
// todo!("Payment method authN operation");
}
};
Ok(())
let router_data = transformers::construct_pre_authentication_router_data(
authentication_connector_name.clone(),
card_number,
&three_ds_connector_account,
business_profile.merchant_id.clone(),
)?;
let router_data =
utils::do_auth_connector_call(state, authentication_connector_name, router_data).await?;
utils::update_trackers(state, router_data, authentication, acquirer_details).await
}

View File

@ -1,3 +1,4 @@
use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;
use crate::{
@ -10,39 +11,39 @@ use crate::{
routes::AppState,
services::{self, execute_connector_processing_step},
types::{
api::{self, ConnectorCallType},
authentication::AuthenticationResponseData,
storage,
transformers::ForeignFrom,
RouterData,
api, authentication::AuthenticationResponseData, domain, storage,
transformers::ForeignFrom, RouterData,
},
utils::OptionExt,
};
pub fn get_connector_name_if_separate_authn_supported(
connector_call_type: &ConnectorCallType,
) -> Option<String> {
pub fn get_connector_data_if_separate_authn_supported(
connector_call_type: &api::ConnectorCallType,
) -> Option<api::ConnectorData> {
match connector_call_type {
ConnectorCallType::PreDetermined(connector_data) => {
api::ConnectorCallType::PreDetermined(connector_data) => {
if connector_data
.connector_name
.is_separate_authentication_supported()
{
Some(connector_data.connector_name.to_string())
Some(connector_data.clone())
} else {
None
}
}
ConnectorCallType::Retryable(connectors) => connectors.first().and_then(|connector_data| {
if connector_data
.connector_name
.is_separate_authentication_supported()
{
Some(connector_data.connector_name.to_string())
} else {
None
}
}),
ConnectorCallType::SessionMultiple(_) => None,
api::ConnectorCallType::Retryable(connectors) => {
connectors.first().and_then(|connector_data| {
if connector_data
.connector_name
.is_separate_authentication_supported()
{
Some(connector_data.clone())
} else {
None
}
})
}
api::ConnectorCallType::SessionMultiple(_) => None,
}
}
@ -50,7 +51,6 @@ pub async fn update_trackers<F: Clone, Req>(
state: &AppState,
router_data: RouterData<F, Req, AuthenticationResponseData>,
authentication: storage::Authentication,
token: Option<String>,
acquirer_details: Option<super::types::AcquirerDetails>,
) -> RouterResult<storage::Authentication> {
let authentication_update = match router_data.response {
@ -72,7 +72,6 @@ pub async fn update_trackers<F: Clone, Req>(
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()),
@ -144,6 +143,7 @@ pub async fn create_new_authentication(
state: &AppState,
merchant_id: String,
authentication_connector: String,
token: String,
profile_id: String,
payment_id: Option<String>,
merchant_connector_id: String,
@ -155,7 +155,7 @@ pub async fn create_new_authentication(
merchant_id,
authentication_connector,
connector_authentication_id: None,
payment_method_id: "".into(),
payment_method_id: format!("eph_{}", token),
authentication_type: None,
authentication_status: common_enums::AuthenticationStatus::Started,
authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused,
@ -221,3 +221,53 @@ where
.to_payment_failed_response()?;
Ok(router_data)
}
pub async fn get_authentication_connector_data(
state: &AppState,
key_store: &domain::MerchantKeyStore,
business_profile: &storage::BusinessProfile,
) -> RouterResult<(
api_models::enums::AuthenticationConnectors,
payments::helpers::MerchantConnectorAccountType,
)> {
let authentication_details: api_models::admin::AuthenticationConnectorDetails =
business_profile
.authentication_connector_details
.clone()
.get_required_value("authentication_details")
.change_context(errors::ApiErrorResponse::UnprocessableEntity {
message: "authentication_connector_details is not available in business profile"
.into(),
})
.attach_printable("authentication_connector_details not configured by the merchant")?
.parse_value("AuthenticationConnectorDetails")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Error while parsing authentication_connector_details from business_profile",
)?;
let authentication_connector = authentication_details
.authentication_connectors
.first()
.ok_or(errors::ApiErrorResponse::UnprocessableEntity {
message: format!(
"No authentication_connector found for profile_id {}",
business_profile.profile_id
),
})
.attach_printable(
"No authentication_connector found from merchant_account.authentication_details",
)?
.to_owned();
let profile_id = &business_profile.profile_id;
let authentication_connector_mca = payments::helpers::get_merchant_connector_account(
state,
&business_profile.merchant_id,
None,
key_store,
profile_id,
authentication_connector.to_string().as_str(),
None,
)
.await?;
Ok((authentication_connector, authentication_connector_mca))
}

View File

@ -36,6 +36,7 @@ use crate::{
connector,
consts::{self, BASE64_ENGINE},
core::{
authentication,
errors::{self, CustomResult, RouterResult, StorageErrorExt},
mandate::helpers::MandateGenericData,
payment_methods::{cards, vault, PaymentMethodRetrieve},
@ -4356,6 +4357,103 @@ pub fn validate_mandate_data_and_future_usage(
}
}
pub enum PaymentExternalAuthenticationFlow {
PreAuthenticationFlow {
acquirer_details: authentication::types::AcquirerDetails,
card_number: ::cards::CardNumber,
token: String,
},
PostAuthenticationFlow {
authentication_id: String,
},
}
pub async fn get_payment_external_authentication_flow_during_confirm<F: Clone>(
state: &AppState,
key_store: &domain::MerchantKeyStore,
business_profile: &storage::BusinessProfile,
payment_data: &mut PaymentData<F>,
connector_call_type: &api::ConnectorCallType,
) -> RouterResult<Option<PaymentExternalAuthenticationFlow>> {
let authentication_id = payment_data.payment_attempt.authentication_id.clone();
let is_authentication_type_3ds = payment_data.payment_attempt.authentication_type
== Some(common_enums::AuthenticationType::ThreeDs);
let separate_authentication_requested = payment_data
.payment_intent
.request_external_three_ds_authentication
.unwrap_or(false);
let separate_three_ds_authentication_attempted = payment_data
.payment_attempt
.external_three_ds_authentication_attempted
.unwrap_or(false);
let connector_supports_separate_authn =
authentication::utils::get_connector_data_if_separate_authn_supported(connector_call_type);
logger::info!("is_pre_authn_call {:?}", authentication_id.is_none());
logger::info!(
"separate_authentication_requested {:?}",
separate_authentication_requested
);
logger::info!(
"payment connector supports external authentication: {:?}",
connector_supports_separate_authn.is_some()
);
let card_number = payment_data.payment_method_data.as_ref().and_then(|pmd| {
if let api_models::payments::PaymentMethodData::Card(card) = pmd {
Some(card.card_number.clone())
} else {
None
}
});
Ok(if separate_three_ds_authentication_attempted {
authentication_id.map(|authentication_id| {
PaymentExternalAuthenticationFlow::PostAuthenticationFlow { authentication_id }
})
} else if separate_authentication_requested && is_authentication_type_3ds {
if let Some((connector_data, card_number)) =
connector_supports_separate_authn.zip(card_number)
{
let token = payment_data
.token
.clone()
.get_required_value("token")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"payment_data.token should not be None while making pre authentication call",
)?;
let payment_connector_mca = get_merchant_connector_account(
state,
&business_profile.merchant_id,
None,
key_store,
&business_profile.profile_id,
connector_data.connector_name.to_string().as_str(),
connector_data.merchant_connector_id.as_ref(),
)
.await?;
let acquirer_details: authentication::types::AcquirerDetails = payment_connector_mca
.get_metadata()
.get_required_value("merchant_connector_account.metadata")?
.peek()
.clone()
.parse_value("AcquirerDetails")
.change_context(errors::ApiErrorResponse::PreconditionFailed {
message:
"acquirer_bin and acquirer_merchant_id not found in Payment Connector's Metadata"
.to_string(),
})?;
Some(PaymentExternalAuthenticationFlow::PreAuthenticationFlow {
card_number,
token,
acquirer_details,
})
} else {
None
}
} else {
None
})
}
pub fn get_redis_key_for_extended_card_info(merchant_id: &str, payment_id: &str) -> String {
format!("{merchant_id}_{payment_id}_extended_card_info")
}

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use api_models::{admin::ExtendedCardInfoConfig, enums::FrmSuggestion, payments::ExtendedCardInfo};
use async_trait::async_trait;
use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
use common_utils::ext_traits::{AsyncExt, Encode, StringExt, ValueExt};
use error_stack::{report, ResultExt};
use futures::FutureExt;
use masking::{ExposeInterface, PeekInterface};
@ -28,6 +28,7 @@ use crate::{
routes::{app::ReqState, AppState},
services,
types::{
self,
api::{self, ConnectorCallType, PaymentIdTypeExt},
domain,
storage::{self, enums as storage_enums},
@ -587,18 +588,6 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
.map(|(payment_method_data, additional_payment_data)| {
payment_method_data.apply_additional_payment_data(additional_payment_data)
});
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()
@ -644,7 +633,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
incremental_authorization_details: None,
authorizations: vec![],
frm_metadata: request.frm_metadata.clone(),
authentication,
authentication: None,
recurring_details,
poll_config: None,
};
@ -785,117 +774,81 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
business_profile: &storage::BusinessProfile,
key_store: &domain::MerchantKeyStore,
) -> CustomResult<(), errors::ApiErrorResponse> {
// if authentication has already happened, then payment_data.authentication will be Some.
// We should do post authn call to fetch the authentication data from 3ds connector
let authentication = payment_data.authentication.clone();
let is_authentication_type_3ds = payment_data.payment_attempt.authentication_type
== Some(common_enums::AuthenticationType::ThreeDs);
let separate_authentication_requested = payment_data
.payment_intent
.request_external_three_ds_authentication
.unwrap_or(false);
let connector_supports_separate_authn =
authentication::utils::get_connector_name_if_separate_authn_supported(
connector_call_type,
);
logger::info!("is_pre_authn_call {:?}", authentication.is_none());
logger::info!(
"separate_authentication_requested {:?}",
separate_authentication_requested
);
if let Some(payment_connector) = match connector_supports_separate_authn {
Some(payment_connector)
if is_authentication_type_3ds && separate_authentication_requested =>
{
Some(payment_connector)
}
_ => None,
} {
let authentication_details: api_models::admin::AuthenticationConnectorDetails =
business_profile
.authentication_connector_details
.clone()
.get_required_value("authentication_details")
.attach_printable("authentication_details not configured by the merchant")?
.parse_value("AuthenticationDetails")
.change_context(errors::ApiErrorResponse::UnprocessableEntity {
message: "Invalid data format found for authentication_details".into(),
})
.attach_printable(
"Error while parsing authentication_details from merchant_account",
)?;
let authentication_connector_name = authentication_details
.authentication_connectors
.first()
.ok_or(errors::ApiErrorResponse::UnprocessableEntity { message: format!("No authentication_connector found for profile_id {}", business_profile.profile_id) })
.attach_printable("No authentication_connector found from merchant_account.authentication_details")?
.to_string();
let profile_id = &business_profile.profile_id;
let authentication_connector_mca = helpers::get_merchant_connector_account(
let external_authentication_flow =
helpers::get_payment_external_authentication_flow_during_confirm(
state,
&business_profile.merchant_id,
None,
key_store,
profile_id,
&authentication_connector_name,
None,
business_profile,
payment_data,
connector_call_type,
)
.await?;
if let Some(authentication) = authentication {
// call post authn service
authentication::perform_post_authentication(
payment_data.authentication = match external_authentication_flow {
Some(helpers::PaymentExternalAuthenticationFlow::PreAuthenticationFlow {
acquirer_details,
card_number,
token,
}) => {
let authentication = authentication::perform_pre_authentication(
state,
authentication_connector_name.clone(),
business_profile.clone(),
authentication_connector_mca,
authentication::types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
authentication,
should_continue_confirm_transaction,
},
)
.await?;
} else {
let payment_connector_mca = helpers::get_merchant_connector_account(
state,
&business_profile.merchant_id,
None,
key_store,
profile_id,
&payment_connector,
None,
card_number,
token,
business_profile,
Some(acquirer_details),
Some(payment_data.payment_attempt.payment_id.clone()),
)
.await?;
// call pre authn service
let card_number = payment_data.payment_method_data.as_ref().and_then(|pmd| {
if let api_models::payments::PaymentMethodData::Card(card) = pmd {
Some(card.card_number.clone())
} else {
None
}
});
// External 3DS authentication is applicable only for cards
if let Some(card_number) = card_number {
authentication::perform_pre_authentication(
state,
authentication_connector_name,
authentication::types::PreAuthenthenticationFlowInput::PaymentAuthNFlow {
payment_data,
should_continue_confirm_transaction,
card_number,
},
business_profile,
authentication_connector_mca,
payment_connector_mca,
)
.await?;
if authentication.is_separate_authn_required()
|| authentication.authentication_status.is_failed()
{
*should_continue_confirm_transaction = false;
let default_poll_config = types::PollConfig::default();
let default_config_str = default_poll_config
.encode_to_string_of_json()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while stringifying default poll config")?;
let poll_config = state
.store
.find_config_by_key_unwrap_or(
&types::PollConfig::get_poll_config_key(
authentication.authentication_connector.clone(),
),
Some(default_config_str),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("The poll config was not found in the DB")?;
let poll_config: types::PollConfig = poll_config
.config
.parse_struct("PollConfig")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while parsing PollConfig")?;
payment_data.poll_config = Some(poll_config)
}
Some(authentication)
}
Ok(())
} else {
Ok(())
}
Some(helpers::PaymentExternalAuthenticationFlow::PostAuthenticationFlow {
authentication_id,
}) => {
let authentication = authentication::perform_post_authentication(
state,
key_store,
business_profile.clone(),
authentication_id.clone(),
)
.await?;
//If authentication is not successful, skip the payment connector flows and mark the payment as failure
if authentication.authentication_status
!= api_models::enums::AuthenticationStatus::Success
{
*should_continue_confirm_transaction = false;
}
Some(authentication)
}
None => None,
};
Ok(())
}
#[instrument(skip_all)]

View File

@ -15,7 +15,7 @@ pub struct Authentication;
#[derive(Debug, Clone)]
pub struct PostAuthentication;
use crate::{connector, services, types};
use crate::{connector, services, types, types::storage};
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
pub struct AcquirerDetails {
@ -34,6 +34,28 @@ pub struct AuthenticationResponse {
pub acs_signed_content: Option<String>,
}
impl TryFrom<storage::Authentication> for AuthenticationResponse {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn try_from(authentication: storage::Authentication) -> Result<Self, Self::Error> {
let trans_status = authentication.trans_status.ok_or(errors::ApiErrorResponse::InternalServerError).attach_printable("trans_status must be populated in authentication table authentication call is successful")?;
let acs_url = authentication
.acs_url
.map(|url| url::Url::from_str(&url))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("not a valid URL")?;
Ok(Self {
trans_status,
acs_url,
challenge_request: authentication.challenge_request,
acs_reference_number: authentication.acs_reference_number,
acs_trans_id: authentication.acs_trans_id,
three_dsserver_trans_id: authentication.three_ds_server_trans_id,
acs_signed_content: authentication.acs_signed_content,
})
}
}
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
pub struct PostAuthenticationResponse {
pub trans_status: String,