From 71a070e26989f080031d92a88aa0143836d1ea7b Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 7 May 2024 13:38:53 +0530 Subject: [PATCH] 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> --- Cargo.lock | 4 +- crates/api_models/src/enums.rs | 62 +--- crates/diesel_models/src/authentication.rs | 3 - crates/router/src/core/authentication.rs | 265 +++++------------- .../router/src/core/authentication/utils.rs | 98 +++++-- crates/router/src/core/payments/helpers.rs | 98 +++++++ .../payments/operations/payment_confirm.rs | 183 +++++------- crates/router/src/types/api/authentication.rs | 24 +- 8 files changed, 339 insertions(+), 398 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fce1e7f53..84fdee3802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index b48e6bce57..aed9915366 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -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, - } } } diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index 61f64dbaa3..8840d287e5 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -101,7 +101,6 @@ pub enum AuthenticationUpdate { message_version: common_utils::types::SemanticVersion, connector_metadata: Option, authentication_status: common_enums::AuthenticationStatus, - payment_method_id: Option, acquirer_bin: Option, acquirer_merchant_id: Option, }, @@ -309,7 +308,6 @@ impl From for AuthenticationUpdateInternal { message_version, connector_metadata, authentication_status, - payment_method_id, acquirer_bin, acquirer_merchant_id, } => Self { @@ -321,7 +319,6 @@ impl From for AuthenticationUpdateInternal { message_version: Some(message_version), connector_metadata, authentication_status: Some(authentication_status), - payment_method_id, acquirer_bin, acquirer_merchant_id, ..Default::default() diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index f9161f2ccc..36ee3c2041 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -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( +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( - pre_authentication_flow_input: &types::PreAuthenthenticationFlowInput<'_, F>, -) -> Option { - match pre_authentication_flow_input { - types::PreAuthenthenticationFlowInput::PaymentAuthNFlow { payment_data, .. } => { - Some(payment_data.payment_intent.payment_id.clone()) - } - _ => None, + authentication_id: String, +) -> CustomResult { + 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( +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, + payment_id: Option, +) -> CustomResult { + 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( .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 } diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index e9c0b3ffc6..bece545923 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -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 { +pub fn get_connector_data_if_separate_authn_supported( + connector_call_type: &api::ConnectorCallType, +) -> Option { 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( state: &AppState, router_data: RouterData, authentication: storage::Authentication, - token: Option, acquirer_details: Option, ) -> RouterResult { let authentication_update = match router_data.response { @@ -72,7 +72,6 @@ pub async fn update_trackers( 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, 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)) +} diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index d2a2896de8..f17a7075d4 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -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( + state: &AppState, + key_store: &domain::MerchantKeyStore, + business_profile: &storage::BusinessProfile, + payment_data: &mut PaymentData, + connector_call_type: &api::ConnectorCallType, +) -> RouterResult> { + 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") } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 89c996236a..3d4f2a3cad 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -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 .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 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 Domain 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)] diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index dddb9c7987..92bcd1ae73 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -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, } +impl TryFrom for AuthenticationResponse { + type Error = error_stack::Report; + fn try_from(authentication: storage::Authentication) -> Result { + 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,