Files

313 lines
12 KiB
Rust

use error_stack::ResultExt;
use hyperswitch_domain_models::router_data_v2::ExternalAuthenticationFlowData;
use crate::{
consts,
core::{
errors::{self, ConnectorErrorExt, StorageErrorExt},
payments,
},
errors::RouterResult,
routes::SessionState,
services::{self, execute_connector_processing_step},
types::{
api, authentication::AuthenticationResponseData, domain, storage,
transformers::ForeignFrom, RouterData,
},
utils::OptionExt,
};
#[cfg(feature = "v1")]
pub fn get_connector_data_if_separate_authn_supported(
connector_call_type: &api::ConnectorCallType,
) -> Option<api::ConnectorData> {
match connector_call_type {
api::ConnectorCallType::PreDetermined(connector_data) => {
if connector_data
.connector_name
.is_separate_authentication_supported()
{
Some(connector_data.clone())
} else {
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,
}
}
pub async fn update_trackers<F: Clone, Req>(
state: &SessionState,
router_data: RouterData<F, Req, AuthenticationResponseData>,
authentication: storage::Authentication,
acquirer_details: Option<super::types::AcquirerDetails>,
) -> RouterResult<storage::Authentication> {
let authentication_update = 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,
directory_server_id,
} => storage::AuthenticationUpdate::PreAuthenticationUpdate {
threeds_server_transaction_id,
maximum_supported_3ds_version,
connector_authentication_id,
three_ds_method_data,
three_ds_method_url,
message_version,
connector_metadata,
authentication_status: common_enums::AuthenticationStatus::Pending,
acquirer_bin: acquirer_details
.as_ref()
.map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
acquirer_merchant_id: acquirer_details
.as_ref()
.map(|acquirer_details| acquirer_details.acquirer_merchant_id.clone()),
acquirer_country_code: acquirer_details
.and_then(|acquirer_details| acquirer_details.acquirer_country_code),
directory_server_id,
},
AuthenticationResponseData::AuthNResponse {
authn_flow_type,
authentication_value,
trans_status,
connector_metadata,
ds_trans_id,
} => {
let authentication_status =
common_enums::AuthenticationStatus::foreign_from(trans_status.clone());
storage::AuthenticationUpdate::AuthenticationUpdate {
authentication_value,
trans_status,
acs_url: authn_flow_type.get_acs_url(),
challenge_request: authn_flow_type.get_challenge_request(),
acs_reference_number: authn_flow_type.get_acs_reference_number(),
acs_trans_id: authn_flow_type.get_acs_trans_id(),
acs_signed_content: authn_flow_type.get_acs_signed_content(),
authentication_type: authn_flow_type.get_decoupled_authentication_type(),
authentication_status,
connector_metadata,
ds_trans_id,
}
}
AuthenticationResponseData::PostAuthNResponse {
trans_status,
authentication_value,
eci,
} => storage::AuthenticationUpdate::PostAuthenticationUpdate {
authentication_status: common_enums::AuthenticationStatus::foreign_from(
trans_status.clone(),
),
trans_status,
authentication_value,
eci,
},
AuthenticationResponseData::PreAuthVersionCallResponse {
maximum_supported_3ds_version,
} => storage::AuthenticationUpdate::PreAuthenticationVersionCallUpdate {
message_version: maximum_supported_3ds_version.clone(),
maximum_supported_3ds_version,
},
AuthenticationResponseData::PreAuthThreeDsMethodCallResponse {
threeds_server_transaction_id,
three_ds_method_data,
three_ds_method_url,
connector_metadata,
} => storage::AuthenticationUpdate::PreAuthenticationThreeDsMethodCall {
threeds_server_transaction_id,
three_ds_method_data,
three_ds_method_url,
connector_metadata,
acquirer_bin: acquirer_details
.as_ref()
.map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
acquirer_merchant_id: acquirer_details
.map(|acquirer_details| acquirer_details.acquirer_merchant_id),
},
},
Err(error) => storage::AuthenticationUpdate::ErrorUpdate {
connector_authentication_id: error.connector_transaction_id,
authentication_status: common_enums::AuthenticationStatus::Failed,
error_message: error
.reason
.map(|reason| format!("message: {}, reason: {}", error.message, reason))
.or(Some(error.message)),
error_code: Some(error.code),
},
};
state
.store
.update_authentication_by_merchant_id_authentication_id(
authentication,
authentication_update,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while updating authentication")
}
impl ForeignFrom<common_enums::AuthenticationStatus> 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,
}
}
}
#[allow(clippy::too_many_arguments)]
pub async fn create_new_authentication(
state: &SessionState,
merchant_id: common_utils::id_type::MerchantId,
authentication_connector: String,
token: String,
profile_id: common_utils::id_type::ProfileId,
payment_id: Option<common_utils::id_type::PaymentId>,
merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
organization_id: common_utils::id_type::OrganizationId,
) -> RouterResult<storage::Authentication> {
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,
payment_method_id: format!("eph_{}", token),
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,
maximum_supported_version: None,
threeds_server_transaction_id: None,
cavv: None,
authentication_flow_type: None,
message_version: None,
eci: None,
trans_status: None,
acquirer_bin: None,
acquirer_merchant_id: None,
three_ds_method_data: None,
three_ds_method_url: None,
acs_url: None,
challenge_request: None,
acs_reference_number: None,
acs_trans_id: None,
acs_signed_content: None,
profile_id,
payment_id,
merchant_connector_id,
ds_trans_id: None,
directory_server_id: None,
acquirer_country_code: None,
service_details: None,
organization_id,
};
state
.store
.insert_authentication(new_authorization)
.await
.to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
message: format!(
"Authentication with authentication_id {} already exists",
authentication_id
),
})
}
pub async fn do_auth_connector_call<F, Req, Res>(
state: &SessionState,
authentication_connector_name: String,
router_data: RouterData<F, Req, Res>,
) -> RouterResult<RouterData<F, Req, Res>>
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<F, Req, Res>,
dyn api::ConnectorV2 + Sync:
services::api::ConnectorIntegrationV2<F, ExternalAuthenticationFlowData, Req, Res>,
{
let connector_data =
api::AuthenticationConnectorData::get_connector_by_name(&authentication_connector_name)?;
let connector_integration: services::BoxedExternalAuthenticationConnectorIntegrationInterface<
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)
}
pub async fn get_authentication_connector_data(
state: &SessionState,
key_store: &domain::MerchantKeyStore,
business_profile: &domain::Profile,
) -> RouterResult<(
common_enums::AuthenticationConnectors,
payments::helpers::MerchantConnectorAccountType,
)> {
let authentication_details = 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")?;
let authentication_connector = authentication_details
.authentication_connectors
.first()
.ok_or(errors::ApiErrorResponse::UnprocessableEntity {
message: format!(
"No authentication_connector found for profile_id {:?}",
business_profile.get_id()
),
})
.attach_printable(
"No authentication_connector found from merchant_account.authentication_details",
)?
.to_owned();
let profile_id = business_profile.get_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))
}