mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
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:
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -7040,9 +7040,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.35"
|
version = "0.3.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582"
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
|||||||
@ -177,8 +177,8 @@ impl Connector {
|
|||||||
matches!(self, Self::Checkout)
|
matches!(self, Self::Checkout)
|
||||||
}
|
}
|
||||||
pub fn is_separate_authentication_supported(&self) -> bool {
|
pub fn is_separate_authentication_supported(&self) -> bool {
|
||||||
#[cfg(feature = "dummy_connector")]
|
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "dummy_connector")]
|
||||||
Self::DummyConnector1
|
Self::DummyConnector1
|
||||||
| Self::DummyConnector2
|
| Self::DummyConnector2
|
||||||
| Self::DummyConnector3
|
| Self::DummyConnector3
|
||||||
@ -243,66 +243,6 @@ impl Connector {
|
|||||||
| Self::Stripe => false,
|
| Self::Stripe => false,
|
||||||
Self::Checkout | Self::Nmi => true,
|
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -101,7 +101,6 @@ pub enum AuthenticationUpdate {
|
|||||||
message_version: common_utils::types::SemanticVersion,
|
message_version: common_utils::types::SemanticVersion,
|
||||||
connector_metadata: Option<serde_json::Value>,
|
connector_metadata: Option<serde_json::Value>,
|
||||||
authentication_status: common_enums::AuthenticationStatus,
|
authentication_status: common_enums::AuthenticationStatus,
|
||||||
payment_method_id: Option<String>,
|
|
||||||
acquirer_bin: Option<String>,
|
acquirer_bin: Option<String>,
|
||||||
acquirer_merchant_id: Option<String>,
|
acquirer_merchant_id: Option<String>,
|
||||||
},
|
},
|
||||||
@ -309,7 +308,6 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
|
|||||||
message_version,
|
message_version,
|
||||||
connector_metadata,
|
connector_metadata,
|
||||||
authentication_status,
|
authentication_status,
|
||||||
payment_method_id,
|
|
||||||
acquirer_bin,
|
acquirer_bin,
|
||||||
acquirer_merchant_id,
|
acquirer_merchant_id,
|
||||||
} => Self {
|
} => Self {
|
||||||
@ -321,7 +319,6 @@ impl From<AuthenticationUpdate> for AuthenticationUpdateInternal {
|
|||||||
message_version: Some(message_version),
|
message_version: Some(message_version),
|
||||||
connector_metadata,
|
connector_metadata,
|
||||||
authentication_status: Some(authentication_status),
|
authentication_status: Some(authentication_status),
|
||||||
payment_method_id,
|
|
||||||
acquirer_bin,
|
acquirer_bin,
|
||||||
acquirer_merchant_id,
|
acquirer_merchant_id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@ -5,19 +5,16 @@ pub mod types;
|
|||||||
|
|
||||||
use api_models::payments;
|
use api_models::payments;
|
||||||
use common_enums::Currency;
|
use common_enums::Currency;
|
||||||
use common_utils::{
|
use common_utils::errors::CustomResult;
|
||||||
errors::CustomResult,
|
use error_stack::ResultExt;
|
||||||
ext_traits::{Encode, StringExt, ValueExt},
|
use masking::ExposeInterface;
|
||||||
};
|
|
||||||
use error_stack::{report, ResultExt};
|
|
||||||
use masking::{ExposeInterface, PeekInterface};
|
|
||||||
|
|
||||||
use super::errors;
|
use super::errors::{self, StorageErrorExt};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{errors::ApiErrorResponse, payments as payments_core},
|
core::{errors::ApiErrorResponse, payments as payments_core},
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
types::{self as core_types, api, authentication::AuthenticationResponseData, storage},
|
types::{self as core_types, api, domain, storage},
|
||||||
utils::{check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata, OptionExt},
|
utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -64,133 +61,76 @@ pub async fn perform_authentication(
|
|||||||
)?;
|
)?;
|
||||||
let response =
|
let response =
|
||||||
utils::do_auth_connector_call(state, authentication_connector.clone(), router_data).await?;
|
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 =
|
||||||
let authentication_response =
|
utils::update_trackers(state, response.clone(), authentication_data, None).await?;
|
||||||
response
|
response
|
||||||
.response
|
.response
|
||||||
.map_err(|err| ApiErrorResponse::ExternalConnectorError {
|
.map_err(|err| ApiErrorResponse::ExternalConnectorError {
|
||||||
code: err.code,
|
code: err.code,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
connector: authentication_connector,
|
connector: authentication_connector,
|
||||||
status_code: err.status_code,
|
status_code: err.status_code,
|
||||||
reason: err.reason,
|
reason: err.reason,
|
||||||
})?;
|
})?;
|
||||||
match authentication_response {
|
core_types::api::authentication::AuthenticationResponse::try_from(authentication)
|
||||||
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")?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn perform_post_authentication<F: Clone + Send>(
|
pub async fn perform_post_authentication(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
authentication_connector: String,
|
key_store: &domain::MerchantKeyStore,
|
||||||
business_profile: core_types::storage::BusinessProfile,
|
business_profile: core_types::storage::BusinessProfile,
|
||||||
merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
authentication_id: String,
|
||||||
authentication_flow_input: types::PostAuthenthenticationFlowInput<'_, F>,
|
) -> CustomResult<storage::Authentication, ApiErrorResponse> {
|
||||||
) -> CustomResult<(), ApiErrorResponse> {
|
let (authentication_connector, three_ds_connector_account) =
|
||||||
match authentication_flow_input {
|
utils::get_authentication_connector_data(state, key_store, &business_profile).await?;
|
||||||
types::PostAuthenthenticationFlowInput::PaymentAuthNFlow {
|
let is_pull_mechanism_enabled =
|
||||||
payment_data,
|
check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
|
||||||
authentication,
|
three_ds_connector_account
|
||||||
should_continue_confirm_transaction,
|
.get_metadata()
|
||||||
} => {
|
.map(|metadata| metadata.expose()),
|
||||||
let is_pull_mechanism_enabled =
|
);
|
||||||
check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
|
let authentication = state
|
||||||
merchant_connector_account
|
.store
|
||||||
.get_metadata()
|
.find_authentication_by_merchant_id_authentication_id(
|
||||||
.map(|metadata| metadata.expose()),
|
business_profile.merchant_id.clone(),
|
||||||
);
|
authentication_id.clone(),
|
||||||
let authentication_status =
|
)
|
||||||
if !authentication.authentication_status.is_terminal_status()
|
.await
|
||||||
&& is_pull_mechanism_enabled
|
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)
|
||||||
{
|
.attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?;
|
||||||
let router_data = transformers::construct_post_authentication_router_data(
|
if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled {
|
||||||
authentication_connector.clone(),
|
let router_data = transformers::construct_post_authentication_router_data(
|
||||||
business_profile.clone(),
|
authentication_connector.to_string(),
|
||||||
merchant_connector_account,
|
business_profile,
|
||||||
&authentication,
|
three_ds_connector_account,
|
||||||
)?;
|
&authentication,
|
||||||
let router_data =
|
)?;
|
||||||
utils::do_auth_connector_call(state, authentication_connector, router_data)
|
let router_data =
|
||||||
.await?;
|
utils::do_auth_connector_call(state, authentication_connector.to_string(), router_data)
|
||||||
let updated_authentication = utils::update_trackers(
|
.await?;
|
||||||
state,
|
utils::update_trackers(state, router_data, authentication, None).await
|
||||||
router_data,
|
} else {
|
||||||
authentication.clone(),
|
Ok(authentication)
|
||||||
payment_data.token.clone(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let authentication_status = updated_authentication.authentication_status;
|
|
||||||
payment_data.authentication = Some(updated_authentication);
|
|
||||||
authentication_status
|
|
||||||
} else {
|
|
||||||
authentication.authentication_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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn perform_pre_authentication<F: Clone + Send>(
|
pub async fn perform_pre_authentication(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
authentication_connector_name: String,
|
key_store: &domain::MerchantKeyStore,
|
||||||
authentication_flow_input: types::PreAuthenthenticationFlowInput<'_, F>,
|
card_number: cards::CardNumber,
|
||||||
|
token: String,
|
||||||
business_profile: &core_types::storage::BusinessProfile,
|
business_profile: &core_types::storage::BusinessProfile,
|
||||||
three_ds_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
acquirer_details: Option<types::AcquirerDetails>,
|
||||||
payment_connector_account: payments_core::helpers::MerchantConnectorAccountType,
|
payment_id: Option<String>,
|
||||||
) -> CustomResult<(), ApiErrorResponse> {
|
) -> CustomResult<storage::Authentication, ApiErrorResponse> {
|
||||||
let payment_id = get_payment_id_from_pre_authentication_flow_input(&authentication_flow_input);
|
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(
|
let authentication = utils::create_new_authentication(
|
||||||
state,
|
state,
|
||||||
business_profile.merchant_id.clone(),
|
business_profile.merchant_id.clone(),
|
||||||
authentication_connector_name.clone(),
|
authentication_connector_name.clone(),
|
||||||
|
token,
|
||||||
business_profile.profile_id.clone(),
|
business_profile.profile_id.clone(),
|
||||||
payment_id,
|
payment_id,
|
||||||
three_ds_connector_account
|
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")?,
|
.attach_printable("Error while finding mca_id from merchant_connector_account")?,
|
||||||
)
|
)
|
||||||
.await?;
|
.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(
|
let router_data = transformers::construct_pre_authentication_router_data(
|
||||||
state,
|
authentication_connector_name.clone(),
|
||||||
router_data,
|
card_number,
|
||||||
authentication,
|
&three_ds_connector_account,
|
||||||
payment_data.token.clone(),
|
business_profile.merchant_id.clone(),
|
||||||
Some(acquirer_details),
|
)?;
|
||||||
)
|
let router_data =
|
||||||
.await?;
|
utils::do_auth_connector_call(state, authentication_connector_name, router_data).await?;
|
||||||
if authentication.is_separate_authn_required()
|
|
||||||
|| authentication.authentication_status.is_failed()
|
utils::update_trackers(state, router_data, authentication, acquirer_details).await
|
||||||
{
|
|
||||||
*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(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use common_utils::ext_traits::ValueExt;
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -10,39 +11,39 @@ use crate::{
|
|||||||
routes::AppState,
|
routes::AppState,
|
||||||
services::{self, execute_connector_processing_step},
|
services::{self, execute_connector_processing_step},
|
||||||
types::{
|
types::{
|
||||||
api::{self, ConnectorCallType},
|
api, authentication::AuthenticationResponseData, domain, storage,
|
||||||
authentication::AuthenticationResponseData,
|
transformers::ForeignFrom, RouterData,
|
||||||
storage,
|
|
||||||
transformers::ForeignFrom,
|
|
||||||
RouterData,
|
|
||||||
},
|
},
|
||||||
|
utils::OptionExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn get_connector_name_if_separate_authn_supported(
|
pub fn get_connector_data_if_separate_authn_supported(
|
||||||
connector_call_type: &ConnectorCallType,
|
connector_call_type: &api::ConnectorCallType,
|
||||||
) -> Option<String> {
|
) -> Option<api::ConnectorData> {
|
||||||
match connector_call_type {
|
match connector_call_type {
|
||||||
ConnectorCallType::PreDetermined(connector_data) => {
|
api::ConnectorCallType::PreDetermined(connector_data) => {
|
||||||
if connector_data
|
if connector_data
|
||||||
.connector_name
|
.connector_name
|
||||||
.is_separate_authentication_supported()
|
.is_separate_authentication_supported()
|
||||||
{
|
{
|
||||||
Some(connector_data.connector_name.to_string())
|
Some(connector_data.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConnectorCallType::Retryable(connectors) => connectors.first().and_then(|connector_data| {
|
api::ConnectorCallType::Retryable(connectors) => {
|
||||||
if connector_data
|
connectors.first().and_then(|connector_data| {
|
||||||
.connector_name
|
if connector_data
|
||||||
.is_separate_authentication_supported()
|
.connector_name
|
||||||
{
|
.is_separate_authentication_supported()
|
||||||
Some(connector_data.connector_name.to_string())
|
{
|
||||||
} else {
|
Some(connector_data.clone())
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
}),
|
}
|
||||||
ConnectorCallType::SessionMultiple(_) => None,
|
})
|
||||||
|
}
|
||||||
|
api::ConnectorCallType::SessionMultiple(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +51,6 @@ pub async fn update_trackers<F: Clone, Req>(
|
|||||||
state: &AppState,
|
state: &AppState,
|
||||||
router_data: RouterData<F, Req, AuthenticationResponseData>,
|
router_data: RouterData<F, Req, AuthenticationResponseData>,
|
||||||
authentication: storage::Authentication,
|
authentication: storage::Authentication,
|
||||||
token: Option<String>,
|
|
||||||
acquirer_details: Option<super::types::AcquirerDetails>,
|
acquirer_details: Option<super::types::AcquirerDetails>,
|
||||||
) -> RouterResult<storage::Authentication> {
|
) -> RouterResult<storage::Authentication> {
|
||||||
let authentication_update = match router_data.response {
|
let authentication_update = match router_data.response {
|
||||||
@ -72,7 +72,6 @@ pub async fn update_trackers<F: Clone, Req>(
|
|||||||
message_version,
|
message_version,
|
||||||
connector_metadata,
|
connector_metadata,
|
||||||
authentication_status: common_enums::AuthenticationStatus::Pending,
|
authentication_status: common_enums::AuthenticationStatus::Pending,
|
||||||
payment_method_id: token.map(|token| format!("eph_{}", token)),
|
|
||||||
acquirer_bin: acquirer_details
|
acquirer_bin: acquirer_details
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
|
.map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
|
||||||
@ -144,6 +143,7 @@ pub async fn create_new_authentication(
|
|||||||
state: &AppState,
|
state: &AppState,
|
||||||
merchant_id: String,
|
merchant_id: String,
|
||||||
authentication_connector: String,
|
authentication_connector: String,
|
||||||
|
token: String,
|
||||||
profile_id: String,
|
profile_id: String,
|
||||||
payment_id: Option<String>,
|
payment_id: Option<String>,
|
||||||
merchant_connector_id: String,
|
merchant_connector_id: String,
|
||||||
@ -155,7 +155,7 @@ pub async fn create_new_authentication(
|
|||||||
merchant_id,
|
merchant_id,
|
||||||
authentication_connector,
|
authentication_connector,
|
||||||
connector_authentication_id: None,
|
connector_authentication_id: None,
|
||||||
payment_method_id: "".into(),
|
payment_method_id: format!("eph_{}", token),
|
||||||
authentication_type: None,
|
authentication_type: None,
|
||||||
authentication_status: common_enums::AuthenticationStatus::Started,
|
authentication_status: common_enums::AuthenticationStatus::Started,
|
||||||
authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused,
|
authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused,
|
||||||
@ -221,3 +221,53 @@ where
|
|||||||
.to_payment_failed_response()?;
|
.to_payment_failed_response()?;
|
||||||
Ok(router_data)
|
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))
|
||||||
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ use crate::{
|
|||||||
connector,
|
connector,
|
||||||
consts::{self, BASE64_ENGINE},
|
consts::{self, BASE64_ENGINE},
|
||||||
core::{
|
core::{
|
||||||
|
authentication,
|
||||||
errors::{self, CustomResult, RouterResult, StorageErrorExt},
|
errors::{self, CustomResult, RouterResult, StorageErrorExt},
|
||||||
mandate::helpers::MandateGenericData,
|
mandate::helpers::MandateGenericData,
|
||||||
payment_methods::{cards, vault, PaymentMethodRetrieve},
|
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 {
|
pub fn get_redis_key_for_extended_card_info(merchant_id: &str, payment_id: &str) -> String {
|
||||||
format!("{merchant_id}_{payment_id}_extended_card_info")
|
format!("{merchant_id}_{payment_id}_extended_card_info")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use api_models::{admin::ExtendedCardInfoConfig, enums::FrmSuggestion, payments::ExtendedCardInfo};
|
use api_models::{admin::ExtendedCardInfoConfig, enums::FrmSuggestion, payments::ExtendedCardInfo};
|
||||||
use async_trait::async_trait;
|
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 error_stack::{report, ResultExt};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use masking::{ExposeInterface, PeekInterface};
|
use masking::{ExposeInterface, PeekInterface};
|
||||||
@ -28,6 +28,7 @@ use crate::{
|
|||||||
routes::{app::ReqState, AppState},
|
routes::{app::ReqState, AppState},
|
||||||
services,
|
services,
|
||||||
types::{
|
types::{
|
||||||
|
self,
|
||||||
api::{self, ConnectorCallType, PaymentIdTypeExt},
|
api::{self, ConnectorCallType, PaymentIdTypeExt},
|
||||||
domain,
|
domain,
|
||||||
storage::{self, enums as storage_enums},
|
storage::{self, enums as storage_enums},
|
||||||
@ -587,18 +588,6 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
|||||||
.map(|(payment_method_data, additional_payment_data)| {
|
.map(|(payment_method_data, additional_payment_data)| {
|
||||||
payment_method_data.apply_additional_payment_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
|
payment_attempt.payment_method_billing_address_id = payment_method_billing
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -644,7 +633,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
|||||||
incremental_authorization_details: None,
|
incremental_authorization_details: None,
|
||||||
authorizations: vec![],
|
authorizations: vec![],
|
||||||
frm_metadata: request.frm_metadata.clone(),
|
frm_metadata: request.frm_metadata.clone(),
|
||||||
authentication,
|
authentication: None,
|
||||||
recurring_details,
|
recurring_details,
|
||||||
poll_config: None,
|
poll_config: None,
|
||||||
};
|
};
|
||||||
@ -785,117 +774,81 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
|
|||||||
business_profile: &storage::BusinessProfile,
|
business_profile: &storage::BusinessProfile,
|
||||||
key_store: &domain::MerchantKeyStore,
|
key_store: &domain::MerchantKeyStore,
|
||||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||||
// if authentication has already happened, then payment_data.authentication will be Some.
|
let external_authentication_flow =
|
||||||
// We should do post authn call to fetch the authentication data from 3ds connector
|
helpers::get_payment_external_authentication_flow_during_confirm(
|
||||||
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(
|
|
||||||
state,
|
state,
|
||||||
&business_profile.merchant_id,
|
|
||||||
None,
|
|
||||||
key_store,
|
key_store,
|
||||||
profile_id,
|
business_profile,
|
||||||
&authentication_connector_name,
|
payment_data,
|
||||||
None,
|
connector_call_type,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(authentication) = authentication {
|
payment_data.authentication = match external_authentication_flow {
|
||||||
// call post authn service
|
Some(helpers::PaymentExternalAuthenticationFlow::PreAuthenticationFlow {
|
||||||
authentication::perform_post_authentication(
|
acquirer_details,
|
||||||
|
card_number,
|
||||||
|
token,
|
||||||
|
}) => {
|
||||||
|
let authentication = authentication::perform_pre_authentication(
|
||||||
state,
|
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,
|
key_store,
|
||||||
profile_id,
|
card_number,
|
||||||
&payment_connector,
|
token,
|
||||||
None,
|
business_profile,
|
||||||
|
Some(acquirer_details),
|
||||||
|
Some(payment_data.payment_attempt.payment_id.clone()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
// call pre authn service
|
if authentication.is_separate_authn_required()
|
||||||
let card_number = payment_data.payment_method_data.as_ref().and_then(|pmd| {
|
|| authentication.authentication_status.is_failed()
|
||||||
if let api_models::payments::PaymentMethodData::Card(card) = pmd {
|
{
|
||||||
Some(card.card_number.clone())
|
*should_continue_confirm_transaction = false;
|
||||||
} else {
|
let default_poll_config = types::PollConfig::default();
|
||||||
None
|
let default_config_str = default_poll_config
|
||||||
}
|
.encode_to_string_of_json()
|
||||||
});
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
// External 3DS authentication is applicable only for cards
|
.attach_printable("Error while stringifying default poll config")?;
|
||||||
if let Some(card_number) = card_number {
|
let poll_config = state
|
||||||
authentication::perform_pre_authentication(
|
.store
|
||||||
state,
|
.find_config_by_key_unwrap_or(
|
||||||
authentication_connector_name,
|
&types::PollConfig::get_poll_config_key(
|
||||||
authentication::types::PreAuthenthenticationFlowInput::PaymentAuthNFlow {
|
authentication.authentication_connector.clone(),
|
||||||
payment_data,
|
),
|
||||||
should_continue_confirm_transaction,
|
Some(default_config_str),
|
||||||
card_number,
|
)
|
||||||
},
|
.await
|
||||||
business_profile,
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
authentication_connector_mca,
|
.attach_printable("The poll config was not found in the DB")?;
|
||||||
payment_connector_mca,
|
let poll_config: types::PollConfig = poll_config
|
||||||
)
|
.config
|
||||||
.await?;
|
.parse_struct("PollConfig")
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error while parsing PollConfig")?;
|
||||||
|
payment_data.poll_config = Some(poll_config)
|
||||||
}
|
}
|
||||||
|
Some(authentication)
|
||||||
}
|
}
|
||||||
Ok(())
|
Some(helpers::PaymentExternalAuthenticationFlow::PostAuthenticationFlow {
|
||||||
} else {
|
authentication_id,
|
||||||
Ok(())
|
}) => {
|
||||||
}
|
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)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@ -15,7 +15,7 @@ pub struct Authentication;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PostAuthentication;
|
pub struct PostAuthentication;
|
||||||
use crate::{connector, services, types};
|
use crate::{connector, services, types, types::storage};
|
||||||
|
|
||||||
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
|
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
|
||||||
pub struct AcquirerDetails {
|
pub struct AcquirerDetails {
|
||||||
@ -34,6 +34,28 @@ pub struct AuthenticationResponse {
|
|||||||
pub acs_signed_content: Option<String>,
|
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)]
|
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)]
|
||||||
pub struct PostAuthenticationResponse {
|
pub struct PostAuthenticationResponse {
|
||||||
pub trans_status: String,
|
pub trans_status: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user