diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index ffe96fc13d..14d4c6e4c1 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -52,7 +52,15 @@ impl FromStr for CardNumber { fn from_str(s: &str) -> Result { // Valid test cards for threedsecureio - let valid_test_cards = ["4000100511112003", "6000100611111203", "3000100811111072"]; + let valid_test_cards = match router_env::which() { + router_env::Env::Development | router_env::Env::Sandbox => vec![ + "4000100511112003", + "6000100611111203", + "3000100811111072", + "9000100111111111", + ], + router_env::Env::Production => vec![], + }; if luhn::valid(s) || valid_test_cards.contains(&s) { let cc_no_whitespace: String = s.split_whitespace().collect(); Ok(Self(StrongSecret::from_str(&cc_no_whitespace)?)) diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index c395ae3df9..b839fcc9b6 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -6,6 +6,7 @@ mod capture; pub mod cards_info; pub mod configs; +pub mod authentication; pub mod authorization; pub mod blocklist; pub mod blocklist_fingerprint; diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index cd408564ea..27c71ffb08 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -1 +1,226 @@ +pub(crate) mod utils; + +pub mod transformers; pub mod types; + +use api_models::payments; +use common_enums::Currency; +use common_utils::{errors::CustomResult, ext_traits::ValueExt}; +use error_stack::ResultExt; +use masking::PeekInterface; + +use super::errors; +use crate::{ + core::{errors::ApiErrorResponse, payments as payments_core}, + routes::AppState, + types::{self as core_types, api, authentication::AuthenticationResponseData, storage}, + utils::OptionExt, +}; + +#[allow(clippy::too_many_arguments)] +pub async fn perform_authentication( + state: &AppState, + authentication_connector: String, + payment_method_data: payments::PaymentMethodData, + payment_method: common_enums::PaymentMethod, + billing_address: api_models::payments::Address, + shipping_address: Option, + browser_details: Option, + business_profile: core_types::storage::BusinessProfile, + merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType, + amount: Option, + currency: Option, + message_category: api::authentication::MessageCategory, + device_channel: payments::DeviceChannel, + authentication_data: (types::AuthenticationData, storage::Authentication), + return_url: Option, + sdk_information: Option, + threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, + email: Option, +) -> CustomResult { + let router_data = transformers::construct_authentication_router_data( + authentication_connector.clone(), + payment_method_data, + payment_method, + billing_address, + shipping_address, + browser_details, + amount, + currency, + message_category, + device_channel, + business_profile, + merchant_connector_account, + authentication_data.clone(), + return_url, + sdk_information, + threeds_method_comp_ind, + email, + )?; + 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?; + 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(errors::ApiErrorResponse::InternalServerError.into()) + .attach_printable("unexpected response in authentication flow")?, + } +} + +pub async fn perform_post_authentication( + state: &AppState, + authentication_connector: String, + 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_data: (authentication, authentication_data), + should_continue_confirm_transaction, + } => { + // let (auth, authentication_data) = authentication; + let updated_authentication = + 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, + )?; + 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 + } else { + authentication + }; + //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) + { + *should_continue_confirm_transaction = false; + } + } + types::PostAuthenthenticationFlowInput::PaymentMethodAuthNFlow { other_fields: _ } => { + // todo!("Payment method post authN operation"); + } + } + Ok(()) +} + +pub async fn perform_pre_authentication( + state: &AppState, + authentication_connector_name: String, + authentication_flow_input: types::PreAuthenthenticationFlowInput<'_, F>, + business_profile: &core_types::storage::BusinessProfile, + three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType, + payment_connector_account: payments_core::helpers::MerchantConnectorAccountType, +) -> CustomResult<(), ApiErrorResponse> { + let authentication = utils::create_new_authentication( + state, + business_profile.merchant_id.clone(), + authentication_connector_name.clone(), + ) + .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, 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, authentication_data) = utils::update_trackers( + state, + router_data, + authentication, + payment_data.token.clone(), + Some(acquirer_details), + ) + .await?; + if authentication_data + .as_ref() + .is_some_and(|authentication_data| authentication_data.is_separate_authn_required()) + { + *should_continue_confirm_transaction = false; + } + payment_data.authentication = + Some((authentication, authentication_data.unwrap_or_default())); + } + types::PreAuthenthenticationFlowInput::PaymentMethodAuthNFlow { + card_number: _, + other_fields: _, + } => { + // todo!("Payment method authN operation"); + } + }; + Ok(()) +} diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs new file mode 100644 index 0000000000..99f468b467 --- /dev/null +++ b/crates/router/src/core/authentication/transformers.rs @@ -0,0 +1,189 @@ +use std::marker::PhantomData; + +use api_models::payments; +use common_enums::PaymentMethod; +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; + +use crate::{ + core::{ + errors::{self, RouterResult}, + payments::helpers as payments_helpers, + }, + types::{self, storage, transformers::ForeignFrom}, + utils::ext_traits::OptionExt, +}; + +const IRRELEVANT_PAYMENT_ID_IN_AUTHENTICATION_FLOW: &str = + "irrelevant_payment_id_in_AUTHENTICATION_flow"; +const IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW: &str = + "irrelevant_attempt_id_in_AUTHENTICATION_flow"; +const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW: &str = + "irrelevant_connector_request_reference_id_in_AUTHENTICATION_flow"; + +#[allow(clippy::too_many_arguments)] +pub fn construct_authentication_router_data( + authentication_connector: String, + payment_method_data: payments::PaymentMethodData, + payment_method: PaymentMethod, + billing_address: api_models::payments::Address, + shipping_address: Option, + browser_details: Option, + amount: Option, + currency: Option, + message_category: types::api::authentication::MessageCategory, + device_channel: payments::DeviceChannel, + business_profile: storage::BusinessProfile, + merchant_connector_account: payments_helpers::MerchantConnectorAccountType, + authentication_data: (super::types::AuthenticationData, storage::Authentication), + return_url: Option, + sdk_information: Option, + threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, + email: Option, +) -> RouterResult { + 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 router_request = types::authentication::ConnectorAuthenticationRequestData { + payment_method_data, + billing_address, + shipping_address, + browser_details, + amount, + currency, + message_category, + device_channel, + authentication_data, + return_url, + sdk_information, + email, + three_ds_requestor_url: authentication_details.three_ds_requestor_url, + threeds_method_comp_ind, + }; + construct_router_data( + authentication_connector, + payment_method, + business_profile.merchant_id.clone(), + types::PaymentAddress::default(), + router_request, + &merchant_connector_account, + ) +} + +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, +) -> RouterResult { + let router_request = types::authentication::ConnectorPostAuthenticationRequestData { + authentication_data, + }; + construct_router_data( + authentication_connector, + PaymentMethod::default(), + business_profile.merchant_id.clone(), + types::PaymentAddress::default(), + router_request, + &merchant_connector_account, + ) +} + +pub fn construct_pre_authentication_router_data( + authentication_connector: String, + card_holder_account_number: cards::CardNumber, + merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, + merchant_id: String, +) -> RouterResult { + let router_request = types::authentication::PreAuthNRequestData { + card_holder_account_number, + }; + construct_router_data( + authentication_connector, + PaymentMethod::default(), + merchant_id, + types::PaymentAddress::default(), + router_request, + merchant_connector_account, + ) +} + +pub fn construct_router_data( + authentication_connector_name: String, + payment_method: PaymentMethod, + merchant_id: String, + address: types::PaymentAddress, + request_data: Req, + merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, +) -> RouterResult> { + let test_mode: Option = merchant_connector_account.is_test_mode_on(); + let auth_type: types::ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError)?; + Ok(types::RouterData { + flow: PhantomData, + merchant_id, + customer_id: None, + connector_customer: None, + connector: authentication_connector_name, + payment_id: IRRELEVANT_PAYMENT_ID_IN_AUTHENTICATION_FLOW.to_owned(), + attempt_id: IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW.to_owned(), + status: common_enums::AttemptStatus::default(), + payment_method, + connector_auth_type: auth_type, + description: None, + return_url: None, + address, + auth_type: common_enums::AuthenticationType::NoThreeDs, + connector_meta_data: merchant_connector_account.get_metadata(), + amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + recurring_mandate_payment_data: None, + preprocessing_id: None, + payment_method_balance: None, + connector_api_version: None, + request: request_data, + response: Err(types::ErrorResponse::default()), + payment_method_id: None, + connector_request_reference_id: + IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW.to_owned(), + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + test_mode, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + dispute_id: None, + refund_id: None, + }) +} + +impl ForeignFrom for common_enums::AuthenticationStatus { + fn foreign_from(trans_status: payments::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, + } + } +} diff --git a/crates/router/src/core/authentication/types.rs b/crates/router/src/core/authentication/types.rs index c6816bacf8..b7c61c14d4 100644 --- a/crates/router/src/core/authentication/types.rs +++ b/crates/router/src/core/authentication/types.rs @@ -53,7 +53,6 @@ pub struct ThreeDsMethodData { pub three_ds_method_data: String, pub three_ds_method_url: Option, } - #[derive(Clone, Default, Debug, Serialize, Deserialize)] pub struct AcquirerDetails { pub acquirer_bin: String, diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs new file mode 100644 index 0000000000..059b0950ba --- /dev/null +++ b/crates/router/src/core/authentication/utils.rs @@ -0,0 +1,250 @@ +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}, + payments, + }, + errors::RouterResult, + routes::AppState, + services::{self, execute_connector_processing_step}, + types::{ + api, + authentication::{AuthNFlowType, AuthenticationResponseData}, + storage, + transformers::ForeignFrom, + RouterData, + }, + utils::OptionExt, +}; + +pub async fn update_trackers( + state: &AppState, + router_data: RouterData, + authentication: storage::Authentication, + token: Option, + acquirer_details: Option, +) -> RouterResult<(storage::Authentication, Option)> { + let authentication_data_option = authentication + .authentication_data + .as_ref() + .map(|authentication_data| { + authentication_data + .to_owned() + .parse_value::("AuthenticationData") + .change_context(ApiErrorResponse::InternalServerError) + }) + .transpose()?; + + let (authentication_update, updated_authentication_data) = match router_data.response { + Ok(response) => match response { + AuthenticationResponseData::PreAuthNResponse { + threeds_server_transaction_id, + maximum_supported_3ds_version, + connector_authentication_id, + three_ds_method_data, + 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), + ) + } + AuthenticationResponseData::AuthNResponse { + authn_flow_type, + authentication_value: cavv, + 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), + ) + } + 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), + }, + authentication_data_option, + ), + }; + let authentication_result = 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)) +} + +impl ForeignFrom for common_enums::AttemptStatus { + fn foreign_from(from: common_enums::AuthenticationStatus) -> Self { + match from { + common_enums::AuthenticationStatus::Started + | common_enums::AuthenticationStatus::Pending => Self::AuthenticationPending, + common_enums::AuthenticationStatus::Success => Self::AuthenticationSuccessful, + common_enums::AuthenticationStatus::Failed => Self::AuthenticationFailed, + } + } +} + +pub async fn create_new_authentication( + state: &AppState, + merchant_id: String, + authentication_connector: String, +) -> RouterResult { + let authentication_id = + common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); + let new_authorization = storage::AuthenticationNew { + authentication_id: authentication_id.clone(), + merchant_id, + authentication_connector, + connector_authentication_id: None, + authentication_data: None, + payment_method_id: "".into(), + authentication_type: None, + authentication_status: common_enums::AuthenticationStatus::Started, + authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused, + error_message: None, + error_code: None, + connector_metadata: None, + }; + state + .store + .insert_authentication(new_authorization) + .await + .to_duplicate_response(ApiErrorResponse::GenericDuplicateError { + message: format!( + "Authentication with authentication_id {} already exists", + authentication_id + ), + }) +} + +pub async fn do_auth_connector_call( + state: &AppState, + authentication_connector_name: String, + router_data: RouterData, +) -> RouterResult> +where + Req: std::fmt::Debug + Clone + 'static, + Res: std::fmt::Debug + Clone + 'static, + F: std::fmt::Debug + Clone + 'static, + dyn api::Connector + Sync: services::api::ConnectorIntegration, +{ + let connector_data = + api::AuthenticationConnectorData::get_connector_by_name(&authentication_connector_name)?; + let connector_integration: services::BoxedConnectorIntegration<'_, F, Req, Res> = + connector_data.connector.get_connector_integration(); + let router_data = execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .to_payment_failed_response()?; + Ok(router_data) +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 002502e9d9..9b06e657f2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -41,7 +41,8 @@ use self::{ routing::{self as self_routing, SessionFlowRoutingInput}, }; use super::{ - errors::StorageErrorExt, payment_methods::surcharge_decision_configs, routing::TransactionData, + authentication::types::AuthenticationData, errors::StorageErrorExt, + payment_methods::surcharge_decision_configs, routing::TransactionData, }; #[cfg(feature = "frm")] use crate::core::fraud_check as frm_core; @@ -2073,6 +2074,7 @@ where pub payment_link_data: Option, pub incremental_authorization_details: Option, pub authorizations: Vec, + pub authentication: Option<(storage::Authentication, AuthenticationData)>, pub frm_metadata: Option, } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index a38c7bbdd3..3068e7f238 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -177,6 +177,7 @@ impl incremental_authorization_details: None, authorizations: vec![], frm_metadata: None, + authentication: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 1b09e9abce..aa995d8f7a 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -185,6 +185,7 @@ impl incremental_authorization_details: None, authorizations: vec![], frm_metadata: None, + authentication: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 4a488a07eb..f5fd9eb60a 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -229,6 +229,7 @@ impl incremental_authorization_details: None, authorizations: vec![], frm_metadata: None, + authentication: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 395f8e59db..e26228011c 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -277,6 +277,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 529565c71e..72c8131b80 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -576,6 +576,7 @@ impl incremental_authorization_details: None, authorizations: vec![], frm_metadata: request.frm_metadata.clone(), + authentication: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 5ec8bfaf55..d75cecd7e6 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -417,6 +417,7 @@ impl payment_link_data, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: request.frm_metadata.clone(), }; diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index a1416ea190..2b28195a15 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -172,6 +172,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index ed3eda8832..64712881bf 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -197,6 +197,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 650fef7e1b..f90a57f5f6 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -175,6 +175,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 70f04ed364..4cfad8e55a 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -448,6 +448,7 @@ async fn get_tracker_for_sync< frm_message: frm_response.ok(), incremental_authorization_details: None, authorizations, + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 81efdbabc4..a669dc32cb 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -413,6 +413,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + authentication: None, frm_metadata: request.frm_metadata.clone(), }; diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 96cbf9fb9d..76efc7bcca 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -152,6 +152,7 @@ impl authorization_id: None, }), authorizations: vec![], + authentication: None, frm_metadata: None, }; diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index d385d94a5d..a6e15be0c0 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -1,5 +1,6 @@ pub mod address; pub mod api_keys; +pub mod authentication; pub mod authorization; pub mod blocklist; pub mod blocklist_fingerprint; @@ -113,6 +114,7 @@ pub trait StorageInterface: + user::sample_data::BatchSampleDataInterface + health_check::HealthCheckDbInterface + role::RoleInterface + + authentication::AuthenticationInterface + 'static { fn get_scheduler_db(&self) -> Box; diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 8ba977befc..4509da8f0f 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -33,6 +33,7 @@ use crate::{ db::{ address::AddressInterface, api_keys::ApiKeyInterface, + authentication::AuthenticationInterface, authorization::AuthorizationInterface, business_profile::BusinessProfileInterface, capture::CaptureInterface, @@ -2347,6 +2348,41 @@ impl AuthorizationInterface for KafkaStore { } } +#[async_trait::async_trait] +impl AuthenticationInterface for KafkaStore { + async fn insert_authentication( + &self, + authentication: storage::AuthenticationNew, + ) -> CustomResult { + self.diesel_store + .insert_authentication(authentication) + .await + } + + async fn find_authentication_by_merchant_id_authentication_id( + &self, + merchant_id: String, + authentication_id: String, + ) -> CustomResult { + self.diesel_store + .find_authentication_by_merchant_id_authentication_id(merchant_id, authentication_id) + .await + } + + async fn update_authentication_by_merchant_id_authentication_id( + &self, + previous_state: storage::Authentication, + authentication_update: storage::AuthenticationUpdate, + ) -> CustomResult { + self.diesel_store + .update_authentication_by_merchant_id_authentication_id( + previous_state, + authentication_update, + ) + .await + } +} + #[async_trait::async_trait] impl HealthCheckDbInterface for KafkaStore { async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError> { diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index 4c662c08fa..ab00872fe8 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -1,6 +1,11 @@ +use std::str::FromStr; + use api_models::enums; +use common_utils::errors::CustomResult; +use error_stack::{IntoReport, ResultExt}; use super::BoxedConnector; +use crate::core::errors; #[derive(Debug, Clone)] pub struct PreAuthentication; @@ -82,3 +87,32 @@ pub struct AuthenticationConnectorData { pub connector: BoxedConnector, pub connector_name: enums::AuthenticationConnectors, } + +impl AuthenticationConnectorData { + pub fn get_connector_by_name(name: &str) -> CustomResult { + let connector_name = enums::AuthenticationConnectors::from_str(name) + .into_report() + .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven) + .attach_printable_lazy(|| format!("unable to parse connector: {name}"))?; + let connector = Self::convert_connector(connector_name)?; + Ok(Self { + connector, + connector_name, + }) + } + + fn convert_connector( + connector_name: enums::AuthenticationConnectors, + ) -> CustomResult { + match connector_name { + enums::AuthenticationConnectors::Threedsecureio => { + Err(errors::ApiErrorResponse::NotImplemented { + message: errors::NotImplementedMessage::Reason( + "external 3ds authentication is not fully implemented".to_string(), + ), + } + .into()) + } + } + } +} diff --git a/crates/router/src/types/authentication.rs b/crates/router/src/types/authentication.rs index dfce745e79..93eb5f37ff 100644 --- a/crates/router/src/types/authentication.rs +++ b/crates/router/src/types/authentication.rs @@ -22,7 +22,7 @@ pub enum AuthenticationResponseData { }, AuthNResponse { authn_flow_type: AuthNFlowType, - cavv: Option, + authentication_value: Option, trans_status: api_models::payments::TransactionStatus, }, PostAuthNResponse { @@ -51,7 +51,8 @@ pub enum AuthNFlowType { #[derive(Clone, Default, Debug)] pub struct PreAuthNRequestData { // card number - pub card_holder_account_number: CardNumber, + #[allow(dead_code)] + pub(crate) card_holder_account_number: CardNumber, } #[derive(Clone, Debug)] diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index a1518cea43..023f30db32 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -45,6 +45,7 @@ pub struct MockDb { pub user_roles: Arc>>, pub authorizations: Arc>>, pub dashboard_metadata: Arc>>, + pub authentications: Arc>>, pub roles: Arc>>, } @@ -83,6 +84,7 @@ impl MockDb { user_roles: Default::default(), authorizations: Default::default(), dashboard_metadata: Default::default(), + authentications: Default::default(), roles: Default::default(), }) }