diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 6045147a05..e0d888135f 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -6347,6 +6347,46 @@ } ] } + }, + "/authentication": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Authentication - Create", + "description": "Create a new authentication for accessing our APIs from your servers.\n", + "operationId": "Create an Authentication", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Authentication created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResponse" + } + } + } + }, + "400": { + "description": "Invalid data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } } }, "components": { @@ -6734,6 +6774,29 @@ } } }, + "AcquirerDetails": { + "type": "object", + "properties": { + "bin": { + "type": "string", + "description": "The bin of the card.", + "example": "123456", + "nullable": true + }, + "merchant_id": { + "type": "string", + "description": "The merchant id of the card.", + "example": "merchant_abc", + "nullable": true + }, + "country_code": { + "type": "string", + "description": "The country code of the card.", + "example": "US/34456", + "nullable": true + } + } + }, "AdditionalMerchantData": { "oneOf": [ { @@ -7512,6 +7575,168 @@ "ctp_visa" ] }, + "AuthenticationCreateRequest": { + "type": "object", + "required": [ + "amount", + "currency" + ], + "properties": { + "authentication_id": { + "type": "string", + "description": "The unique identifier for this authentication.", + "example": "auth_mbabizu24mvu3mela5njyhpit4", + "nullable": true + }, + "profile_id": { + "type": "string", + "description": "The business profile that is associated with this authentication", + "nullable": true + }, + "authentication_connector": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationConnectors" + } + ], + "nullable": true + }, + "customer": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerDetails" + } + ], + "nullable": true + }, + "amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "return_url": { + "type": "string", + "description": "The URL to which the user should be redirected after authentication.", + "example": "https://example.com/redirect", + "nullable": true + }, + "acquirer_details": { + "allOf": [ + { + "$ref": "#/components/schemas/AcquirerDetails" + } + ], + "nullable": true + }, + "force_3ds_challenge": { + "type": "boolean", + "description": "Force 3DS challenge.", + "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + } + } + }, + "AuthenticationResponse": { + "type": "object", + "required": [ + "authentication_id", + "merchant_id", + "status", + "amount", + "currency" + ], + "properties": { + "authentication_id": { + "type": "string", + "description": "The unique identifier for this authentication.", + "example": "auth_mbabizu24mvu3mela5njyhpit4" + }, + "merchant_id": { + "type": "string", + "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", + "example": "merchant_abc" + }, + "status": { + "$ref": "#/components/schemas/AuthenticationStatus" + }, + "client_secret": { + "type": "string", + "description": "The client secret for this authentication, to be used for client-side operations.", + "example": "auth_mbabizu24mvu3mela5njyhpit4_secret_el9ksDkiB8hi6j9N78yo", + "nullable": true + }, + "amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "force_3ds_challenge": { + "type": "boolean", + "description": "Whether 3DS challenge was forced.", + "nullable": true + }, + "authentication_connector": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationConnectors" + } + ], + "nullable": true + }, + "return_url": { + "type": "string", + "description": "The URL to which the user should be redirected after authentication, if provided.", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2022-09-10T10:11:12Z", + "nullable": true + }, + "error_code": { + "type": "string", + "example": "E0001", + "nullable": true + }, + "error_message": { + "type": "string", + "description": "If there was an error while calling the connector the error message is received here", + "example": "Failed while verifying the card", + "nullable": true + }, + "profile_id": { + "type": "string", + "description": "The business profile that is associated with this payment", + "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + }, + "acquirer_details": { + "allOf": [ + { + "$ref": "#/components/schemas/AcquirerDetails" + } + ], + "nullable": true + } + } + }, "AuthenticationStatus": { "type": "string", "enum": [ @@ -30964,6 +31189,10 @@ { "name": "Event", "description": "Manage events" + }, + { + "name": "Authentication", + "description": "Create and manage authentication" } ] } \ No newline at end of file diff --git a/crates/api_models/src/authentication.rs b/crates/api_models/src/authentication.rs new file mode 100644 index 0000000000..d1c98b25ec --- /dev/null +++ b/crates/api_models/src/authentication.rs @@ -0,0 +1,145 @@ +use common_enums::{enums, AuthenticationConnectors}; +use common_utils::{ + events::{ApiEventMetric, ApiEventsType}, + id_type, +}; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; +use utoipa::ToSchema; + +use crate::payments::CustomerDetails; + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct AuthenticationCreateRequest { + /// The unique identifier for this authentication. + #[schema(value_type = Option, example = "auth_mbabizu24mvu3mela5njyhpit4")] + pub authentication_id: Option, + + /// The business profile that is associated with this authentication + #[schema(value_type = Option)] + pub profile_id: Option, + + /// The connector to be used for authentication, if known. + #[schema(value_type = Option, example = "netcetera")] + pub authentication_connector: Option, + + /// Customer details. + #[schema(value_type = Option)] + pub customer: Option, + + /// The amount for the transaction, required. + #[schema(value_type = MinorUnit, example = 1000)] + pub amount: common_utils::types::MinorUnit, + + /// The currency for the transaction, required. + #[schema(value_type = Currency)] + pub currency: common_enums::Currency, + + /// The URL to which the user should be redirected after authentication. + #[schema(value_type = Option, example = "https://example.com/redirect")] + pub return_url: Option, + + /// Acquirer details information + #[schema(value_type = Option)] + pub acquirer_details: Option, + + /// Force 3DS challenge. + #[serde(default)] + pub force_3ds_challenge: Option, + + /// Choose what kind of sca exemption is required for this payment + #[schema(value_type = Option)] + pub psd2_sca_exemption_type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct AcquirerDetails { + /// The bin of the card. + #[schema(value_type = Option, example = "123456")] + pub bin: Option, + /// The merchant id of the card. + #[schema(value_type = Option, example = "merchant_abc")] + pub merchant_id: Option, + /// The country code of the card. + #[schema(value_type = Option, example = "US/34456")] + pub country_code: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct AuthenticationResponse { + /// The unique identifier for this authentication. + #[schema(value_type = String, example = "auth_mbabizu24mvu3mela5njyhpit4")] + pub authentication_id: id_type::AuthenticationId, + + /// This is an identifier for the merchant account. This is inferred from the API key + /// provided during the request + #[schema(value_type = String, example = "merchant_abc")] + pub merchant_id: id_type::MerchantId, + + /// The current status of the authentication (e.g., Started). + #[schema(value_type = AuthenticationStatus)] + pub status: common_enums::AuthenticationStatus, + + /// The client secret for this authentication, to be used for client-side operations. + #[schema(value_type = Option, example = "auth_mbabizu24mvu3mela5njyhpit4_secret_el9ksDkiB8hi6j9N78yo")] + pub client_secret: Option>, + + /// The amount for the transaction. + #[schema(value_type = MinorUnit, example = 1000)] + pub amount: common_utils::types::MinorUnit, + + /// The currency for the transaction. + #[schema(value_type = Currency)] + pub currency: enums::Currency, + + /// Whether 3DS challenge was forced. + pub force_3ds_challenge: Option, + + /// The connector to be used for authentication, if specified in request. + #[schema(value_type = Option)] + pub authentication_connector: Option, + + /// The URL to which the user should be redirected after authentication, if provided. + pub return_url: Option, + + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub created_at: Option, + + #[schema(example = "E0001")] + pub error_code: Option, + + /// If there was an error while calling the connector the error message is received here + #[schema(example = "Failed while verifying the card")] + pub error_message: Option, + + /// The business profile that is associated with this payment + #[schema(value_type = Option)] + pub profile_id: Option, + + /// Choose what kind of sca exemption is required for this payment + #[schema(value_type = Option)] + pub psd2_sca_exemption_type: Option, + + /// Acquirer details information + #[schema(value_type = Option)] + pub acquirer_details: Option, +} + +impl ApiEventMetric for AuthenticationCreateRequest { + fn get_api_event_type(&self) -> Option { + self.authentication_id + .as_ref() + .map(|id| ApiEventsType::Authentication { + authentication_id: id.clone(), + }) + } +} + +impl ApiEventMetric for AuthenticationResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Authentication { + authentication_id: self.authentication_id.clone(), + }) + } +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 45533cd345..cbc50ed7ff 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -2,6 +2,7 @@ pub mod admin; pub mod analytics; pub mod api_keys; pub mod apple_pay_certificates_migration; +pub mod authentication; pub mod blocklist; pub mod cards_info; pub mod conditional_configs; diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index 1e2ea8573e..7895588e8e 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -232,7 +232,7 @@ pub enum MandateIdType { #[derive(Clone)] pub enum AuthenticationIdType { - AuthenticationId(String), + AuthenticationId(common_utils::id_type::AuthenticationId), ConnectorAuthenticationId(String), } diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 53e45ef9d0..506211c4d7 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -126,6 +126,9 @@ pub enum ApiEventsType { token_id: Option, }, ProcessTracker, + Authentication { + authentication_id: id_type::AuthenticationId, + }, ProfileAcquirer { profile_acquirer_id: id_type::ProfileAcquirerId, }, diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs index 45b65c9a44..d9daa5caf9 100644 --- a/crates/common_utils/src/id_type.rs +++ b/crates/common_utils/src/id_type.rs @@ -2,6 +2,7 @@ //! The id type can be used to create specific id types with custom behaviour mod api_key; +mod authentication; mod client_secret; mod customer; #[cfg(feature = "v2")] @@ -40,6 +41,7 @@ pub use self::global_id::{ }; pub use self::{ api_key::ApiKeyId, + authentication::AuthenticationId, client_secret::ClientSecretId, customer::CustomerId, merchant::MerchantId, diff --git a/crates/common_utils/src/id_type/authentication.rs b/crates/common_utils/src/id_type/authentication.rs new file mode 100644 index 0000000000..68a62a5681 --- /dev/null +++ b/crates/common_utils/src/id_type/authentication.rs @@ -0,0 +1,46 @@ +crate::id_type!( + AuthenticationId, + "A type for authentication_id that can be used for authentication IDs" +); +crate::impl_id_type_methods!(AuthenticationId, "authentication_id"); + +// This is to display the `AuthenticationId` as AuthenticationId(abcd) +crate::impl_debug_id_type!(AuthenticationId); +crate::impl_try_from_cow_str_id_type!(AuthenticationId, "authentication_id"); + +crate::impl_serializable_secret_id_type!(AuthenticationId); +crate::impl_queryable_id_type!(AuthenticationId); +crate::impl_to_sql_from_sql_id_type!(AuthenticationId); + +impl AuthenticationId { + /// Generate Authentication Id from prefix + pub fn generate_authentication_id(prefix: &'static str) -> Self { + Self(crate::generate_ref_id_with_default_length(prefix)) + } +} + +impl crate::events::ApiEventMetric for AuthenticationId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::Authentication { + authentication_id: self.clone(), + }) + } +} + +impl crate::events::ApiEventMetric for (super::MerchantId, AuthenticationId) { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::Authentication { + authentication_id: self.1.clone(), + }) + } +} + +impl crate::events::ApiEventMetric for (&super::MerchantId, &AuthenticationId) { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::Authentication { + authentication_id: self.1.clone(), + }) + } +} + +crate::impl_default_id_type!(AuthenticationId, "authentication"); diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index 0c7ac7338e..d8329e3765 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -9,9 +9,9 @@ use crate::schema::authentication; )] #[diesel(table_name = authentication, primary_key(authentication_id), check_for_backend(diesel::pg::Pg))] pub struct Authentication { - pub authentication_id: String, + pub authentication_id: common_utils::id_type::AuthenticationId, pub merchant_id: common_utils::id_type::MerchantId, - pub authentication_connector: String, + pub authentication_connector: Option, pub connector_authentication_id: Option, pub authentication_data: Option, pub payment_method_id: String, @@ -43,12 +43,18 @@ pub struct Authentication { pub acs_signed_content: Option, pub profile_id: common_utils::id_type::ProfileId, pub payment_id: Option, - pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option, pub ds_trans_id: Option, pub directory_server_id: Option, pub acquirer_country_code: Option, pub service_details: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub authentication_client_secret: Option, + pub force_3ds_challenge: Option, + pub psd2_sca_exemption_type: Option, + pub return_url: Option, + pub amount: Option, + pub currency: Option, } impl Authentication { @@ -62,9 +68,9 @@ impl Authentication { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Insertable)] #[diesel(table_name = authentication)] pub struct AuthenticationNew { - pub authentication_id: String, + pub authentication_id: common_utils::id_type::AuthenticationId, pub merchant_id: common_utils::id_type::MerchantId, - pub authentication_connector: String, + pub authentication_connector: Option, pub connector_authentication_id: Option, // pub authentication_data: Option, pub payment_method_id: String, @@ -92,12 +98,18 @@ pub struct AuthenticationNew { pub acs_signed_content: Option, pub profile_id: common_utils::id_type::ProfileId, pub payment_id: Option, - pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option, pub ds_trans_id: Option, pub directory_server_id: Option, pub acquirer_country_code: Option, pub service_details: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub authentication_client_secret: Option, + pub force_3ds_challenge: Option, + pub psd2_sca_exemption_type: Option, + pub return_url: Option, + pub amount: Option, + pub currency: Option, } #[derive(Debug)] @@ -193,6 +205,8 @@ pub struct AuthenticationUpdateInternal { pub directory_server_id: Option, pub acquirer_country_code: Option, pub service_details: Option, + pub force_3ds_challenge: Option, + pub psd2_sca_exemption_type: Option, } impl Default for AuthenticationUpdateInternal { @@ -226,6 +240,8 @@ impl Default for AuthenticationUpdateInternal { directory_server_id: Default::default(), acquirer_country_code: Default::default(), service_details: Default::default(), + force_3ds_challenge: Default::default(), + psd2_sca_exemption_type: Default::default(), } } } @@ -261,6 +277,8 @@ impl AuthenticationUpdateInternal { directory_server_id, acquirer_country_code, service_details, + force_3ds_challenge, + psd2_sca_exemption_type, } = self; Authentication { connector_authentication_id: connector_authentication_id @@ -295,6 +313,8 @@ impl AuthenticationUpdateInternal { directory_server_id: directory_server_id.or(source.directory_server_id), acquirer_country_code: acquirer_country_code.or(source.acquirer_country_code), service_details: service_details.or(source.service_details), + force_3ds_challenge: force_3ds_challenge.or(source.force_3ds_challenge), + psd2_sca_exemption_type: psd2_sca_exemption_type.or(source.psd2_sca_exemption_type), ..source } } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index d438b09351..9e20ca7407 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -74,7 +74,7 @@ pub struct PaymentAttempt { pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub fingerprint_id: Option, pub client_source: Option, pub client_version: Option, @@ -176,7 +176,7 @@ pub struct PaymentAttempt { pub net_amount: Option, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub mandate_data: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, @@ -314,7 +314,7 @@ pub struct PaymentAttemptNew { pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub fingerprint_id: Option, pub payment_method_billing_address: Option, pub client_source: Option, @@ -402,7 +402,7 @@ pub struct PaymentAttemptNew { pub net_amount: Option, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub mandate_data: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, @@ -488,7 +488,7 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, external_three_ds_authentication_attempted: Option, authentication_connector: Option, - authentication_id: Option, + authentication_id: Option, payment_method_billing_address_id: Option, client_source: Option, client_version: Option, @@ -616,7 +616,7 @@ pub enum PaymentAttemptUpdate { status: storage_enums::AttemptStatus, external_three_ds_authentication_attempted: Option, authentication_connector: Option, - authentication_id: Option, + authentication_id: Option, updated_by: String, }, ManualUpdate { @@ -922,7 +922,7 @@ pub struct PaymentAttemptUpdateInternal { pub unified_message: Option>, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, pub client_source: Option, diff --git a/crates/diesel_models/src/query/authentication.rs b/crates/diesel_models/src/query/authentication.rs index 41339fc1aa..4bd6bbdc32 100644 --- a/crates/diesel_models/src/query/authentication.rs +++ b/crates/diesel_models/src/query/authentication.rs @@ -20,7 +20,7 @@ impl Authentication { pub async fn update_by_merchant_id_authentication_id( conn: &PgPooledConn, merchant_id: common_utils::id_type::MerchantId, - authentication_id: String, + authentication_id: common_utils::id_type::AuthenticationId, authorization_update: AuthenticationUpdate, ) -> StorageResult { match generics::generic_update_with_unique_predicate_get_result::< @@ -59,7 +59,7 @@ impl Authentication { pub async fn find_by_merchant_id_authentication_id( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - authentication_id: &str, + authentication_id: &common_utils::id_type::AuthenticationId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index cb690e3f60..e303dfc553 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -67,7 +67,7 @@ diesel::table! { #[max_length = 64] merchant_id -> Varchar, #[max_length = 64] - authentication_connector -> Varchar, + authentication_connector -> Nullable, #[max_length = 64] connector_authentication_id -> Nullable, authentication_data -> Nullable, @@ -113,7 +113,7 @@ diesel::table! { #[max_length = 255] payment_id -> Nullable, #[max_length = 128] - merchant_connector_id -> Varchar, + merchant_connector_id -> Nullable, #[max_length = 64] ds_trans_id -> Nullable, #[max_length = 128] @@ -123,6 +123,14 @@ diesel::table! { service_details -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 128] + authentication_client_secret -> Nullable, + force_3ds_challenge -> Nullable, + psd2_sca_exemption_type -> Nullable, + #[max_length = 2048] + return_url -> Nullable, + amount -> Nullable, + currency -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 6ea3b49bb0..9ba5cdfaff 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -67,7 +67,7 @@ diesel::table! { #[max_length = 64] merchant_id -> Varchar, #[max_length = 64] - authentication_connector -> Varchar, + authentication_connector -> Nullable, #[max_length = 64] connector_authentication_id -> Nullable, authentication_data -> Nullable, @@ -113,7 +113,7 @@ diesel::table! { #[max_length = 255] payment_id -> Nullable, #[max_length = 128] - merchant_connector_id -> Varchar, + merchant_connector_id -> Nullable, #[max_length = 64] ds_trans_id -> Nullable, #[max_length = 128] @@ -123,6 +123,14 @@ diesel::table! { service_details -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 128] + authentication_client_secret -> Nullable, + force_3ds_challenge -> Nullable, + psd2_sca_exemption_type -> Nullable, + #[max_length = 2048] + return_url -> Nullable, + amount -> Nullable, + currency -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 2a21bb7c40..743d175e34 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -195,7 +195,7 @@ pub struct PaymentAttemptBatchNew { pub net_amount: Option, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs index 0b50559a4e..b283306c2d 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs @@ -36,8 +36,8 @@ impl From<(FloatMajorUnit, T)> for UnifiedAuthenticationServiceRouterData #[derive(Debug, Serialize)] pub struct UnifiedAuthenticationServicePreAuthenticateRequest { pub authenticate_by: String, - pub session_id: String, - pub source_authentication_id: String, + pub session_id: common_utils::id_type::AuthenticationId, + pub source_authentication_id: common_utils::id_type::AuthenticationId, pub authentication_info: Option, pub service_details: Option, pub customer_details: Option, @@ -49,7 +49,7 @@ pub struct UnifiedAuthenticationServicePreAuthenticateRequest { #[derive(Debug, Serialize)] pub struct UnifiedAuthenticationServiceAuthenticateConfirmationRequest { pub authenticate_by: String, - pub source_authentication_id: String, + pub source_authentication_id: common_utils::id_type::AuthenticationId, pub auth_creds: ConnectorAuthType, pub x_src_flow_id: Option, pub transaction_amount: Option, @@ -338,7 +338,7 @@ impl #[derive(Debug, Serialize)] pub struct UnifiedAuthenticationServicePostAuthenticateRequest { pub authenticate_by: String, - pub source_authentication_id: String, + pub source_authentication_id: common_utils::id_type::AuthenticationId, pub auth_creds: ConnectorAuthType, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 1de29f20d4..98b93d6b6f 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -436,7 +436,7 @@ pub struct PaymentAttempt { /// The connector that was used for external authentication pub authentication_connector: Option, /// The foreign key reference to the authentication details - pub authentication_id: Option, + pub authentication_id: Option, pub fingerprint_id: Option, pub client_source: Option, pub client_version: Option, @@ -885,7 +885,7 @@ pub struct PaymentAttempt { pub unified_message: Option, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, @@ -1144,7 +1144,7 @@ pub struct PaymentAttemptNew { pub unified_message: Option, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, - pub authentication_id: Option, + pub authentication_id: Option, pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, @@ -1222,7 +1222,7 @@ pub enum PaymentAttemptUpdate { merchant_connector_id: Option, external_three_ds_authentication_attempted: Option, authentication_connector: Option, - authentication_id: Option, + authentication_id: Option, payment_method_billing_address_id: Option, fingerprint_id: Option, payment_method_id: Option, @@ -1350,7 +1350,7 @@ pub enum PaymentAttemptUpdate { status: storage_enums::AttemptStatus, external_three_ds_authentication_attempted: Option, authentication_connector: Option, - authentication_id: Option, + authentication_id: Option, updated_by: String, }, ManualUpdate { diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 360359036f..dd1004c701 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -99,7 +99,7 @@ pub struct RouterData { pub connector_mandate_request_reference_id: Option, - pub authentication_id: Option, + pub authentication_id: Option, /// Contains the type of sca exemption required for the transaction pub psd2_sca_exemption_type: Option, diff --git a/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs b/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs index 661f04752a..5bc18dc8af 100644 --- a/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs +++ b/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs @@ -150,7 +150,7 @@ pub struct RevenueRecoveryRecordBackData; #[derive(Debug, Clone)] pub struct UasFlowData { pub authenticate_by: String, - pub source_authentication_id: String, + pub source_authentication_id: common_utils::id_type::AuthenticationId, } #[derive(Debug, Clone)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 8ce212dd09..0e420ab794 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -66,6 +66,7 @@ Never share your secret api keys. Keep them guarded and secure. (name = "payment link", description = "Create payment link"), (name = "Routing", description = "Create and manage routing configurations"), (name = "Event", description = "Manage events"), + (name = "Authentication", description = "Create and manage authentication") ), // The paths will be displayed in the same order as they are registered here paths( @@ -209,6 +210,9 @@ Never share your secret api keys. Keep them guarded and secure. // Routes for 3DS Decision Rule routes::three_ds_decision_rule::three_ds_decision_rule_execute, + + // Routes for authentication + routes::authentication::authentication_create, ), components(schemas( common_utils::types::MinorUnit, @@ -788,6 +792,9 @@ Never share your secret api keys. Keep them guarded and secure. euclid::frontend::dir::enums::CustomerDevicePlatform, euclid::frontend::dir::enums::CustomerDeviceType, euclid::frontend::dir::enums::CustomerDeviceDisplaySize, + api_models::authentication::AuthenticationCreateRequest, + api_models::authentication::AuthenticationResponse, + api_models::authentication::AcquirerDetails, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/routes.rs b/crates/openapi/src/routes.rs index c91dd74f0f..50c7db98ab 100644 --- a/crates/openapi/src/routes.rs +++ b/crates/openapi/src/routes.rs @@ -1,6 +1,7 @@ #![allow(unused)] pub mod api_keys; +pub mod authentication; pub mod blocklist; pub mod customers; pub mod disputes; diff --git a/crates/openapi/src/routes/authentication.rs b/crates/openapi/src/routes/authentication.rs new file mode 100644 index 0000000000..ebc4590bb6 --- /dev/null +++ b/crates/openapi/src/routes/authentication.rs @@ -0,0 +1,17 @@ +/// Authentication - Create +/// +/// Create a new authentication for accessing our APIs from your servers. +/// +#[utoipa::path( + post, + path = "/authentication", + request_body = AuthenticationCreateRequest, + responses( + (status = 200, description = "Authentication created", body = AuthenticationResponse), + (status = 400, description = "Invalid data") + ), + tag = "Authentication", + operation_id = "Create an Authentication", + security(("api_key" = [])) +)] +pub async fn authentication_create() {} diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index ab31c531e2..cc1db79444 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -13,7 +13,11 @@ use super::errors::StorageErrorExt; use crate::{ core::{errors::ApiErrorResponse, payments as payments_core}, routes::SessionState, - types::{self as core_types, api, domain, storage}, + types::{ + self as core_types, api, + domain::{self}, + storage, + }, utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata, }; @@ -99,7 +103,7 @@ pub async fn perform_post_authentication( state: &SessionState, key_store: &domain::MerchantKeyStore, business_profile: domain::Profile, - authentication_id: String, + authentication_id: common_utils::id_type::AuthenticationId, payment_id: &common_utils::id_type::PaymentId, ) -> CustomResult< hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore, @@ -117,11 +121,16 @@ pub async fn perform_post_authentication( .store .find_authentication_by_merchant_id_authentication_id( &business_profile.merchant_id, - authentication_id.clone(), + &authentication_id, ) .await .to_not_found_response(ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?; + .attach_printable_lazy(|| { + format!( + "Error while fetching authentication record with authentication_id {}", + authentication_id.get_string_repr() + ) + })?; let authentication_update = if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled @@ -147,7 +156,7 @@ pub async fn perform_post_authentication( // getting authentication value from temp locker before moving ahead with authrisation let tokenized_data = crate::core::payment_methods::vault::get_tokenized_data( state, - &authentication_id, + authentication_id.get_string_repr(), false, key_store.key.get_inner(), ) @@ -175,6 +184,8 @@ pub async fn perform_pre_authentication( acquirer_details: Option, payment_id: common_utils::id_type::PaymentId, organization_id: common_utils::id_type::OrganizationId, + force_3ds_challenge: Option, + psd2_sca_exemption_type: Option, ) -> CustomResult< hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore, ApiErrorResponse, @@ -194,6 +205,8 @@ pub async fn perform_pre_authentication( .ok_or(ApiErrorResponse::InternalServerError) .attach_printable("Error while finding mca_id from merchant_connector_account")?, organization_id, + force_3ds_challenge, + psd2_sca_exemption_type, ) .await?; diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index 0d9045ba38..57096aa83e 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -103,7 +103,10 @@ pub async fn update_trackers( state, auth_val.expose(), None, - authentication.authentication_id.clone(), + authentication + .authentication_id + .get_string_repr() + .to_string(), merchant_key_store.key.get_inner(), ) }) @@ -138,7 +141,10 @@ pub async fn update_trackers( state, auth_val.expose(), None, - authentication.authentication_id.clone(), + authentication + .authentication_id + .get_string_repr() + .to_string(), merchant_key_store.key.get_inner(), ) }) @@ -217,13 +223,20 @@ pub async fn create_new_authentication( payment_id: common_utils::id_type::PaymentId, merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, organization_id: common_utils::id_type::OrganizationId, + force_3ds_challenge: Option, + psd2_sca_exemption_type: Option, ) -> RouterResult { - let authentication_id = - common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); + let authentication_id = common_utils::id_type::AuthenticationId::generate_authentication_id( + consts::AUTHENTICATION_ID_PREFIX, + ); + let authentication_client_secret = Some(common_utils::generate_id_with_default_len(&format!( + "{}_secret", + authentication_id.get_string_repr() + ))); let new_authorization = storage::AuthenticationNew { authentication_id: authentication_id.clone(), merchant_id, - authentication_connector, + authentication_connector: Some(authentication_connector), connector_authentication_id: None, payment_method_id: format!("eph_{}", token), authentication_type: None, @@ -250,12 +263,18 @@ pub async fn create_new_authentication( acs_signed_content: None, profile_id, payment_id: Some(payment_id), - merchant_connector_id, + merchant_connector_id: Some(merchant_connector_id), ds_trans_id: None, directory_server_id: None, acquirer_country_code: None, service_details: None, organization_id, + authentication_client_secret, + force_3ds_challenge, + psd2_sca_exemption_type, + return_url: None, + amount: None, + currency: None, }; state .store @@ -264,7 +283,7 @@ pub async fn create_new_authentication( .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { message: format!( "Authentication with authentication_id {} already exists", - authentication_id + authentication_id.get_string_repr() ), }) } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 30e49f9def..1d4521661b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3155,13 +3155,10 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { .attach_printable("missing authentication_id in payment_attempt")?; let authentication = state .store - .find_authentication_by_merchant_id_authentication_id( - &merchant_id, - authentication_id.clone(), - ) + .find_authentication_by_merchant_id_authentication_id(&merchant_id, &authentication_id) .await .to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound { - id: authentication_id, + id: authentication_id.get_string_repr().to_string(), })?; // Fetching merchant_connector_account to check if pull_mechanism is enabled for 3ds connector let authentication_merchant_connector_account = helpers::get_merchant_connector_account( @@ -8425,7 +8422,7 @@ pub async fn payment_external_authentication( let authentication = db .find_authentication_by_merchant_id_authentication_id( merchant_id, - payment_attempt + &payment_attempt .authentication_id .clone() .ok_or(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 251b90a60d..1ba46ab7d0 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6620,7 +6620,7 @@ pub enum UnifiedAuthenticationServiceFlow { token: String, }, ExternalAuthenticationPostAuthenticate { - authentication_id: String, + authentication_id: id_type::AuthenticationId, }, ClickToPayConfirmation, } @@ -6702,7 +6702,7 @@ pub enum PaymentExternalAuthenticationFlow { token: String, }, PostAuthenticationFlow { - authentication_id: String, + authentication_id: id_type::AuthenticationId, }, } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 11f73b2300..6324c37bdf 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1031,6 +1031,8 @@ impl Domain> for acquirer_details, payment_data.payment_attempt.payment_id.clone(), payment_data.payment_attempt.organization_id.clone(), + payment_data.payment_intent.force_3ds_challenge, + payment_data.payment_intent.psd2_sca_exemption_type, )) .await?; if authentication_store @@ -1047,15 +1049,21 @@ impl Domain> for .encode_to_string_of_json() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error while stringifying default poll config")?; + + // raise error if authentication_connector is not present since it should we be present in the current flow + let authentication_connector = authentication_store + .authentication + .authentication_connector + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "authentication_connector not present in authentication record", + )?; + let poll_config = state .store .find_config_by_key_unwrap_or( - &types::PollConfig::get_poll_config_key( - authentication_store - .authentication - .authentication_connector - .clone(), - ), + &types::PollConfig::get_poll_config_key(authentication_connector), Some(default_config_str), ) .await @@ -1254,7 +1262,7 @@ impl Domain> for }, )?; let authentication_id = - common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); + common_utils::id_type::AuthenticationId::generate_authentication_id(consts::AUTHENTICATION_ID_PREFIX); let payment_method = payment_data.payment_attempt.payment_method.ok_or( errors::ApiErrorResponse::MissingRequiredField { field_name: "payment_method", @@ -1325,15 +1333,23 @@ impl Domain> for let authentication = uas_utils::create_new_authentication( state, payment_data.payment_attempt.merchant_id.clone(), - connector_mca.connector_name.to_string(), + Some(connector_mca.connector_name.to_string()), business_profile.get_id().clone(), Some(payment_data.payment_intent.get_id().clone()), - click_to_pay_mca_id.to_owned(), + Some(click_to_pay_mca_id.to_owned()), &authentication_id, payment_data.service_details.clone(), authentication_status, network_token.clone(), payment_data.payment_attempt.organization_id.clone(), + payment_data.payment_intent.force_3ds_challenge, + payment_data.payment_intent.psd2_sca_exemption_type, + None, + None, + None, + None, + None, + None ) .await?; let authentication_store = hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore { @@ -1410,6 +1426,8 @@ impl Domain> for .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error while finding mca_id from merchant_connector_account")?, payment_data.payment_attempt.organization_id.clone(), + payment_data.payment_intent.force_3ds_challenge, + payment_data.payment_intent.psd2_sca_exemption_type, ) .await?; @@ -1447,11 +1465,17 @@ impl Domain> for .encode_to_string_of_json() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error while stringifying default poll config")?; + + // raise error if authentication_connector is not present since it should we be present in the current flow + let authentication_connector = updated_authentication.authentication_connector + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("authentication_connector not found in updated_authentication")?; + let poll_config = state .store .find_config_by_key_unwrap_or( &types::PollConfig::get_poll_config_key( - updated_authentication.authentication_connector.clone(), + authentication_connector.clone(), ), Some(default_config_str), ) @@ -1479,11 +1503,11 @@ impl Domain> for .store .find_authentication_by_merchant_id_authentication_id( &business_profile.merchant_id, - authentication_id.clone(), + &authentication_id, ) .await .to_not_found_response(errors::ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?; + .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {}", authentication_id.get_string_repr()))?; let updated_authentication = if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled { let post_auth_response = uas_utils::types::ExternalAuthentication::post_authentication( state, @@ -1508,7 +1532,7 @@ impl Domain> for authentication }; - let tokenized_data = crate::core::payment_methods::vault::get_tokenized_data(state, &authentication_id, false, key_store.key.get_inner()).await?; + let tokenized_data = crate::core::payment_methods::vault::get_tokenized_data(state, authentication_id.get_string_repr(), false, key_store.key.get_inner()).await?; let authentication_store = hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore { cavv: Some(masking::Secret::new(tokenized_data.value1)), @@ -1833,12 +1857,10 @@ impl UpdateTracker, api::PaymentsRequest> for .authentication .is_separate_authn_required(), ), - Some( - authentication_store - .authentication - .authentication_connector - .clone(), - ), + authentication_store + .authentication + .authentication_connector + .clone(), Some( authentication_store .authentication diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 830f2754e5..ac2a7b923f 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -463,14 +463,16 @@ async fn get_tracker_for_sync< let authentication_store = if let Some(ref authentication_id) = payment_attempt.authentication_id { - let authentication = - db.find_authentication_by_merchant_id_authentication_id( - &merchant_id, - authentication_id.clone(), + let authentication = db + .find_authentication_by_merchant_id_authentication_id(&merchant_id, authentication_id) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Error while fetching authentication record with authentication_id {}", + authentication_id.get_string_repr() ) - .await - .to_not_found_response(errors::ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?; + })?; Some( hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore { diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index ff94c56f7b..becb1c173d 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -1,7 +1,10 @@ pub mod types; pub mod utils; -use api_models::payments; +use api_models::{ + authentication::{AcquirerDetails, AuthenticationCreateRequest, AuthenticationResponse}, + payments, +}; use diesel_models::authentication::{Authentication, AuthenticationNew}; use error_stack::ResultExt; use hyperswitch_domain_models::{ @@ -22,8 +25,12 @@ use hyperswitch_domain_models::{ }, }; -use super::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType}; +use super::{ + errors::{RouterResponse, RouterResult}, + payments::helpers::MerchantConnectorAccountType, +}; use crate::{ + consts, core::{ errors::utils::StorageErrorExt, payments::PaymentData, @@ -31,9 +38,11 @@ use crate::{ ClickToPay, ExternalAuthentication, UnifiedAuthenticationService, UNIFIED_AUTHENTICATION_SERVICE, }, + utils as core_utils, }, db::domain, routes::SessionState, + types::transformers::ForeignFrom, }; #[cfg(feature = "v1")] @@ -95,7 +104,7 @@ impl UnifiedAuthenticationService for ClickToPay { payment_data: &PaymentData, merchant_connector_account: &MerchantConnectorAccountType, connector_name: &str, - authentication_id: &str, + authentication_id: &common_utils::id_type::AuthenticationId, payment_method: common_enums::PaymentMethod, ) -> RouterResult { let pre_authentication_data = Self::get_pre_authentication_request_data(payment_data)?; @@ -281,7 +290,7 @@ impl UnifiedAuthenticationService for ExternalAuthentication payment_data: &PaymentData, merchant_connector_account: &MerchantConnectorAccountType, connector_name: &str, - authentication_id: &str, + authentication_id: &common_utils::id_type::AuthenticationId, payment_method: common_enums::PaymentMethod, ) -> RouterResult { let pre_authentication_data = Self::get_pre_authentication_request_data(payment_data)?; @@ -472,20 +481,27 @@ impl UnifiedAuthenticationService for ExternalAuthentication } } -#[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn create_new_authentication( state: &SessionState, merchant_id: common_utils::id_type::MerchantId, - authentication_connector: String, + authentication_connector: Option, profile_id: common_utils::id_type::ProfileId, payment_id: Option, - merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, - authentication_id: &str, + merchant_connector_id: Option, + authentication_id: &common_utils::id_type::AuthenticationId, service_details: Option, authentication_status: common_enums::AuthenticationStatus, network_token: Option, organization_id: common_utils::id_type::OrganizationId, + force_3ds_challenge: Option, + psd2_sca_exemption_type: Option, + acquirer_bin: Option, + acquirer_merchant_id: Option, + acquirer_country_code: Option, + amount: Option, + currency: Option, + return_url: Option, ) -> RouterResult { let service_details_value = service_details .map(serde_json::to_value) @@ -494,6 +510,10 @@ pub async fn create_new_authentication( .attach_printable( "unable to parse service details into json value while inserting to DB", )?; + let authentication_client_secret = Some(common_utils::generate_id_with_default_len(&format!( + "{}_secret", + authentication_id.get_string_repr() + ))); let new_authorization = AuthenticationNew { authentication_id: authentication_id.to_owned(), merchant_id, @@ -513,8 +533,8 @@ pub async fn create_new_authentication( message_version: None, eci: network_token.and_then(|data| data.eci), trans_status: None, - acquirer_bin: None, - acquirer_merchant_id: None, + acquirer_bin, + acquirer_merchant_id, three_ds_method_data: None, three_ds_method_url: None, acs_url: None, @@ -527,9 +547,15 @@ pub async fn create_new_authentication( merchant_connector_id, ds_trans_id: None, directory_server_id: None, - acquirer_country_code: None, + acquirer_country_code, service_details: service_details_value, organization_id, + authentication_client_secret, + force_3ds_challenge, + psd2_sca_exemption_type, + return_url, + amount, + currency, }; state .store @@ -538,7 +564,147 @@ pub async fn create_new_authentication( .to_duplicate_response(ApiErrorResponse::GenericDuplicateError { message: format!( "Authentication with authentication_id {} already exists", - authentication_id + authentication_id.get_string_repr() ), }) } + +// Modular authentication +#[cfg(feature = "v1")] +pub async fn authentication_create_core( + state: SessionState, + merchant_context: domain::MerchantContext, + req: AuthenticationCreateRequest, +) -> RouterResponse { + let db = &*state.store; + let merchant_account = merchant_context.get_merchant_account(); + let merchant_id = merchant_account.get_id(); + let key_manager_state = (&state).into(); + let profile_id = core_utils::get_profile_id_from_business_details( + &key_manager_state, + None, + None, + &merchant_context, + req.profile_id.as_ref(), + db, + true, + ) + .await?; + + let business_profile = db + .find_business_profile_by_profile_id( + &key_manager_state, + merchant_context.get_merchant_key_store(), + &profile_id, + ) + .await + .to_not_found_response(ApiErrorResponse::ProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + let organization_id = merchant_account.organization_id.clone(); + let authentication_id = common_utils::id_type::AuthenticationId::generate_authentication_id( + consts::AUTHENTICATION_ID_PREFIX, + ); + + let force_3ds_challenge = Some( + req.force_3ds_challenge + .unwrap_or(business_profile.force_3ds_challenge), + ); + + let new_authentication = create_new_authentication( + &state, + merchant_id.clone(), + req.authentication_connector + .map(|connector| connector.to_string()), + profile_id.clone(), + None, + None, + &authentication_id, + None, + common_enums::AuthenticationStatus::Started, + None, + organization_id, + force_3ds_challenge, + req.psd2_sca_exemption_type, + req.acquirer_details + .clone() + .and_then(|acquirer_details| acquirer_details.bin), + req.acquirer_details + .clone() + .and_then(|acquirer_details| acquirer_details.merchant_id), + req.acquirer_details + .clone() + .and_then(|acquirer_details| acquirer_details.country_code), + Some(req.amount), + Some(req.currency), + req.return_url, + ) + .await?; + + let acquirer_details = Some(AcquirerDetails { + bin: new_authentication.acquirer_bin.clone(), + merchant_id: new_authentication.acquirer_merchant_id.clone(), + country_code: new_authentication.acquirer_country_code.clone(), + }); + + let amount = new_authentication + .amount + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("amount failed to get amount from authentication table")?; + let currency = new_authentication + .currency + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("currency failed to get currency from authentication table")?; + + let response = AuthenticationResponse::foreign_from(( + new_authentication, + amount, + currency, + profile_id, + acquirer_details, + )); + + Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( + response, + )) +} + +impl + ForeignFrom<( + Authentication, + common_utils::types::MinorUnit, + common_enums::Currency, + common_utils::id_type::ProfileId, + Option, + )> for AuthenticationResponse +{ + fn foreign_from( + (authentication, amount, currency, profile_id, acquirer_details): ( + Authentication, + common_utils::types::MinorUnit, + common_enums::Currency, + common_utils::id_type::ProfileId, + Option, + ), + ) -> Self { + Self { + authentication_id: authentication.authentication_id, + client_secret: authentication + .authentication_client_secret + .map(masking::Secret::new), + amount, + currency, + force_3ds_challenge: authentication.force_3ds_challenge, + merchant_id: authentication.merchant_id, + status: authentication.authentication_status, + authentication_connector: authentication.authentication_connector, + return_url: authentication.return_url, + created_at: Some(authentication.created_at), + error_code: authentication.error_code, + error_message: authentication.error_message, + profile_id: Some(profile_id), + psd2_sca_exemption_type: authentication.psd2_sca_exemption_type, + acquirer_details, + } + } +} diff --git a/crates/router/src/core/unified_authentication_service/types.rs b/crates/router/src/core/unified_authentication_service/types.rs index 06c3bd1178..54e903edd6 100644 --- a/crates/router/src/core/unified_authentication_service/types.rs +++ b/crates/router/src/core/unified_authentication_service/types.rs @@ -55,7 +55,7 @@ pub trait UnifiedAuthenticationService { _payment_data: &PaymentData, _merchant_connector_account: &MerchantConnectorAccountType, _connector_name: &str, - _authentication_id: &str, + _authentication_id: &common_utils::id_type::AuthenticationId, _payment_method: common_enums::PaymentMethod, ) -> RouterResult { Err(errors::ApiErrorResponse::NotImplemented { diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index 371846a51f..5e199968fe 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -64,7 +64,7 @@ pub fn construct_uas_router_data( address: Option, request_data: Req, merchant_connector_account: &payments::helpers::MerchantConnectorAccountType, - authentication_id: Option, + authentication_id: Option, payment_id: common_utils::id_type::PaymentId, ) -> RouterResult> { let auth_type: ConnectorAuthType = merchant_connector_account @@ -191,7 +191,10 @@ pub async fn external_authentication_update_trackers( state, auth_val.expose(), None, - authentication.authentication_id.clone(), + authentication + .authentication_id + .get_string_repr() + .to_string(), merchant_key_store.key.get_inner(), ) }) @@ -240,7 +243,10 @@ pub async fn external_authentication_update_trackers( state, auth_val, None, - authentication.authentication_id.clone(), + authentication + .authentication_id + .get_string_repr() + .to_string(), merchant_key_store.key.get_inner(), ) }) diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 3f95d61bfb..9763e63f17 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -1277,11 +1277,11 @@ async fn external_authentication_incoming_webhook_flow( .store .find_authentication_by_merchant_id_authentication_id( merchant_context.get_merchant_account().get_id(), - authentication_id.clone(), + &authentication_id, ) .await .to_not_found_response(errors::ApiErrorResponse::AuthenticationNotFound { - id: authentication_id, + id: authentication_id.get_string_repr().to_string(), }) .attach_printable("Error while fetching authentication record"), webhooks::AuthenticationIdType::ConnectorAuthenticationId( @@ -1320,7 +1320,11 @@ async fn external_authentication_incoming_webhook_flow( &state, auth_val.expose(), None, - updated_authentication.authentication_id.clone(), + updated_authentication + .authentication_id + .clone() + .get_string_repr() + .to_string(), merchant_context.get_merchant_key_store().key.get_inner(), ) }) diff --git a/crates/router/src/db/authentication.rs b/crates/router/src/db/authentication.rs index 7c98b90134..0c31e70fe2 100644 --- a/crates/router/src/db/authentication.rs +++ b/crates/router/src/db/authentication.rs @@ -19,7 +19,7 @@ pub trait AuthenticationInterface { async fn find_authentication_by_merchant_id_authentication_id( &self, merchant_id: &common_utils::id_type::MerchantId, - authentication_id: String, + authentication_id: &common_utils::id_type::AuthenticationId, ) -> CustomResult; async fn find_authentication_by_merchant_id_connector_authentication_id( @@ -53,13 +53,13 @@ impl AuthenticationInterface for Store { async fn find_authentication_by_merchant_id_authentication_id( &self, merchant_id: &common_utils::id_type::MerchantId, - authentication_id: String, + authentication_id: &common_utils::id_type::AuthenticationId, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; storage::Authentication::find_by_merchant_id_authentication_id( &conn, merchant_id, - &authentication_id, + authentication_id, ) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -110,7 +110,12 @@ impl AuthenticationInterface for MockDb { }) { Err(errors::StorageError::DuplicateValue { entity: "authentication_id", - key: Some(authentication.authentication_id.clone()), + key: Some( + authentication + .authentication_id + .get_string_repr() + .to_string(), + ), })? } let authentication = storage::Authentication { @@ -152,6 +157,12 @@ impl AuthenticationInterface for MockDb { acquirer_country_code: authentication.acquirer_country_code, service_details: authentication.service_details, organization_id: authentication.organization_id, + authentication_client_secret: authentication.authentication_client_secret, + force_3ds_challenge: authentication.force_3ds_challenge, + psd2_sca_exemption_type: authentication.psd2_sca_exemption_type, + return_url: authentication.return_url, + amount: authentication.amount, + currency: authentication.currency, }; authentications.push(authentication.clone()); Ok(authentication) @@ -160,15 +171,15 @@ impl AuthenticationInterface for MockDb { async fn find_authentication_by_merchant_id_authentication_id( &self, merchant_id: &common_utils::id_type::MerchantId, - authentication_id: String, + authentication_id: &common_utils::id_type::AuthenticationId, ) -> CustomResult { let authentications = self.authentications.lock().await; authentications .iter() - .find(|a| a.merchant_id == *merchant_id && a.authentication_id == authentication_id) + .find(|a| a.merchant_id == *merchant_id && a.authentication_id == *authentication_id) .ok_or( errors::StorageError::ValueNotFound(format!( - "cannot find authentication for authentication_id = {authentication_id} and merchant_id = {merchant_id:?}" + "cannot find authentication for authentication_id = {authentication_id:?} and merchant_id = {merchant_id:?}" )).into(), ).cloned() } @@ -201,7 +212,7 @@ impl AuthenticationInterface for MockDb { }) .ok_or( errors::StorageError::ValueNotFound(format!( - "cannot find authentication for authentication_id = {authentication_id} and merchant_id = {merchant_id:?}" + "cannot find authentication for authentication_id = {authentication_id:?} and merchant_id = {merchant_id:?}" )) .into(), ) diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 4265f60c7d..d3d151c6ae 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3807,7 +3807,7 @@ impl AuthenticationInterface for KafkaStore { async fn find_authentication_by_merchant_id_authentication_id( &self, merchant_id: &id_type::MerchantId, - authentication_id: String, + authentication_id: &id_type::AuthenticationId, ) -> CustomResult { self.diesel_store .find_authentication_by_merchant_id_authentication_id(merchant_id, authentication_id) diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 94e98cda24..ed51b12ff0 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -168,7 +168,8 @@ pub fn mk_app( { server_app = server_app .service(routes::Refunds::server(state.clone())) - .service(routes::Mandates::server(state.clone())); + .service(routes::Mandates::server(state.clone())) + .service(routes::Authentication::server(state.clone())); } } diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index de32c7b01b..f639ee06ab 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -3,6 +3,7 @@ pub mod api_keys; pub mod app; #[cfg(feature = "v1")] pub mod apple_pay_certificates_migration; +pub mod authentication; #[cfg(all(feature = "olap", feature = "v1"))] pub mod blocklist; pub mod cache; @@ -85,11 +86,11 @@ pub use self::app::Recon; #[cfg(feature = "v2")] pub use self::app::Tokenization; pub use self::app::{ - ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, - Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Hypersense, - Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, - Poll, ProcessTracker, Profile, ProfileAcquirer, ProfileNew, Refunds, Relay, RelayWebhooks, - SessionState, ThreeDsDecisionRule, User, Webhooks, + ApiKeys, AppState, ApplePayCertificatesMigration, Authentication, Cache, Cards, Configs, + ConnectorOnboarding, Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, + Health, Hypersense, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, + PaymentMethods, Payments, Poll, ProcessTracker, Profile, ProfileAcquirer, ProfileNew, Refunds, + Relay, RelayWebhooks, SessionState, ThreeDsDecisionRule, User, Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index bbb1758916..f1c2def317 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -74,6 +74,8 @@ pub use crate::analytics::opensearch::OpenSearchClient; use crate::analytics::AnalyticsProvider; #[cfg(feature = "partial-auth")] use crate::errors::RouterResult; +#[cfg(feature = "oltp")] +use crate::routes::authentication; #[cfg(feature = "v1")] use crate::routes::cards_info::{ card_iin_info, create_cards_info, migrate_cards_info, update_cards_info, @@ -2637,6 +2639,17 @@ impl ProcessTracker { } } +pub struct Authentication; + +#[cfg(feature = "v1")] +impl Authentication { + pub fn server(state: AppState) -> Scope { + web::scope("/authentication") + .app_data(web::Data::new(state)) + .service(web::resource("").route(web::post().to(authentication::authentication_create))) + } +} + #[cfg(feature = "olap")] pub struct ProfileAcquirer; diff --git a/crates/router/src/routes/authentication.rs b/crates/router/src/routes/authentication.rs new file mode 100644 index 0000000000..776a591e18 --- /dev/null +++ b/crates/router/src/routes/authentication.rs @@ -0,0 +1,39 @@ +use actix_web::{web, HttpRequest, Responder}; +use api_models::authentication::AuthenticationCreateRequest; +use router_env::{instrument, tracing, Flow}; + +use crate::{ + core::{api_locking, unified_authentication_service}, + routes::app::{self}, + services::{api, authentication as auth}, + types::domain, +}; + +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::AuthenticationCreate))] +pub async fn authentication_create( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let flow = Flow::AuthenticationCreate; + + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationData, req, _| { + let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( + domain::Context(auth.merchant_account, auth.key_store), + )); + unified_authentication_service::authentication_create_core(state, merchant_context, req) + }, + &auth::HeaderAuth(auth::ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: false, + }), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 039986f1f4..c1f3533ef1 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -43,6 +43,7 @@ pub enum ApiIdentifier { Hypersense, PaymentMethodSession, ProcessTracker, + Authentication, Proxy, ProfileAcquirer, ThreeDsDecisionRule, @@ -346,6 +347,8 @@ impl From for ApiIdentifier { | Flow::PaymentMethodSessionUpdate => Self::PaymentMethodSession, Flow::RevenueRecoveryRetrieve => Self::ProcessTracker, + + Flow::AuthenticationCreate => Self::Authentication, Flow::Proxy => Self::Proxy, Flow::ProfileAcquirerCreate | Flow::ProfileAcquirerUpdate => Self::ProfileAcquirer, diff --git a/crates/router/src/services/kafka/authentication.rs b/crates/router/src/services/kafka/authentication.rs index 86652c8c76..7d59e7e7dc 100644 --- a/crates/router/src/services/kafka/authentication.rs +++ b/crates/router/src/services/kafka/authentication.rs @@ -3,9 +3,9 @@ use time::OffsetDateTime; #[derive(serde::Serialize, Debug)] pub struct KafkaAuthentication<'a> { - pub authentication_id: &'a String, + pub authentication_id: &'a common_utils::id_type::AuthenticationId, pub merchant_id: &'a common_utils::id_type::MerchantId, - pub authentication_connector: &'a String, + pub authentication_connector: Option<&'a String>, pub connector_authentication_id: Option<&'a String>, pub authentication_data: Option, pub payment_method_id: &'a String, @@ -37,7 +37,7 @@ pub struct KafkaAuthentication<'a> { pub acs_signed_content: Option<&'a String>, pub profile_id: &'a common_utils::id_type::ProfileId, pub payment_id: Option<&'a common_utils::id_type::PaymentId>, - pub merchant_connector_id: &'a common_utils::id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option<&'a common_utils::id_type::MerchantConnectorAccountId>, pub ds_trans_id: Option<&'a String>, pub directory_server_id: Option<&'a String>, pub acquirer_country_code: Option<&'a String>, @@ -52,7 +52,7 @@ impl<'a> KafkaAuthentication<'a> { authentication_id: &authentication.authentication_id, merchant_id: &authentication.merchant_id, authentication_status: authentication.authentication_status, - authentication_connector: &authentication.authentication_connector, + authentication_connector: authentication.authentication_connector.as_ref(), connector_authentication_id: authentication.connector_authentication_id.as_ref(), authentication_data: authentication.authentication_data.clone(), payment_method_id: &authentication.payment_method_id, @@ -79,7 +79,7 @@ impl<'a> KafkaAuthentication<'a> { acs_signed_content: authentication.acs_signed_content.as_ref(), profile_id: &authentication.profile_id, payment_id: authentication.payment_id.as_ref(), - merchant_connector_id: &authentication.merchant_connector_id, + merchant_connector_id: authentication.merchant_connector_id.as_ref(), ds_trans_id: authentication.ds_trans_id.as_ref(), directory_server_id: authentication.directory_server_id.as_ref(), acquirer_country_code: authentication.acquirer_country_code.as_ref(), @@ -93,7 +93,7 @@ impl super::KafkaMessage for KafkaAuthentication<'_> { format!( "{}_{}", self.merchant_id.get_string_repr(), - self.authentication_id + self.authentication_id.get_string_repr() ) } diff --git a/crates/router/src/services/kafka/authentication_event.rs b/crates/router/src/services/kafka/authentication_event.rs index a875b4bb49..796f1669d1 100644 --- a/crates/router/src/services/kafka/authentication_event.rs +++ b/crates/router/src/services/kafka/authentication_event.rs @@ -4,9 +4,9 @@ use time::OffsetDateTime; #[serde_with::skip_serializing_none] #[derive(serde::Serialize, Debug)] pub struct KafkaAuthenticationEvent<'a> { - pub authentication_id: &'a String, + pub authentication_id: &'a common_utils::id_type::AuthenticationId, pub merchant_id: &'a common_utils::id_type::MerchantId, - pub authentication_connector: &'a String, + pub authentication_connector: Option<&'a String>, pub connector_authentication_id: Option<&'a String>, pub authentication_data: Option, pub payment_method_id: &'a String, @@ -38,7 +38,7 @@ pub struct KafkaAuthenticationEvent<'a> { pub acs_signed_content: Option<&'a String>, pub profile_id: &'a common_utils::id_type::ProfileId, pub payment_id: Option<&'a common_utils::id_type::PaymentId>, - pub merchant_connector_id: &'a common_utils::id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option<&'a common_utils::id_type::MerchantConnectorAccountId>, pub ds_trans_id: Option<&'a String>, pub directory_server_id: Option<&'a String>, pub acquirer_country_code: Option<&'a String>, @@ -53,7 +53,7 @@ impl<'a> KafkaAuthenticationEvent<'a> { authentication_id: &authentication.authentication_id, merchant_id: &authentication.merchant_id, authentication_status: authentication.authentication_status, - authentication_connector: &authentication.authentication_connector, + authentication_connector: authentication.authentication_connector.as_ref(), connector_authentication_id: authentication.connector_authentication_id.as_ref(), authentication_data: authentication.authentication_data.clone(), payment_method_id: &authentication.payment_method_id, @@ -80,7 +80,7 @@ impl<'a> KafkaAuthenticationEvent<'a> { acs_signed_content: authentication.acs_signed_content.as_ref(), profile_id: &authentication.profile_id, payment_id: authentication.payment_id.as_ref(), - merchant_connector_id: &authentication.merchant_connector_id, + merchant_connector_id: authentication.merchant_connector_id.as_ref(), ds_trans_id: authentication.ds_trans_id.as_ref(), directory_server_id: authentication.directory_server_id.as_ref(), acquirer_country_code: authentication.acquirer_country_code.as_ref(), @@ -94,7 +94,7 @@ impl super::KafkaMessage for KafkaAuthenticationEvent<'_> { format!( "{}_{}", self.merchant_id.get_string_repr(), - self.authentication_id + self.authentication_id.get_string_repr() ) } diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index 68f1d01351..ba34b18b6a 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -334,7 +334,9 @@ impl<'a> KafkaPaymentAttempt<'a> { encoded_data: encoded_data.as_ref(), external_three_ds_authentication_attempted: *external_three_ds_authentication_attempted, authentication_connector: authentication_connector.clone(), - authentication_id: authentication_id.clone(), + authentication_id: authentication_id + .as_ref() + .map(|id| id.get_string_repr().to_string().clone()), fingerprint_id: fingerprint_id.clone(), customer_acceptance: customer_acceptance.as_ref(), shipping_cost: amount_details.get_shipping_cost(), diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index 9bfc6f8c31..7cd70f9a73 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -336,7 +336,9 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { encoded_data: encoded_data.as_ref(), external_three_ds_authentication_attempted: *external_three_ds_authentication_attempted, authentication_connector: authentication_connector.clone(), - authentication_id: authentication_id.clone(), + authentication_id: authentication_id + .as_ref() + .map(|id| id.get_string_repr().to_string()), fingerprint_id: fingerprint_id.clone(), customer_acceptance: customer_acceptance.as_ref(), shipping_cost: amount_details.get_shipping_cost(), diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 6861e4d1df..11818b0569 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -330,7 +330,7 @@ pub async fn find_mca_from_authentication_id_type( webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => db .find_authentication_by_merchant_id_authentication_id( merchant_context.get_merchant_account().get_id(), - authentication_id, + &authentication_id, ) .await .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?, @@ -345,19 +345,21 @@ pub async fn find_mca_from_authentication_id_type( }; #[cfg(feature = "v1")] { + // raise error if merchant_connector_id is not present since it should we be present in the current flow + let mca_id = authentication + .merchant_connector_id + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("merchant_connector_id not present in authentication record")?; db.find_by_merchant_connector_account_merchant_id_merchant_connector_id( &state.into(), merchant_context.get_merchant_account().get_id(), - &authentication.merchant_connector_id, + &mca_id, merchant_context.get_merchant_key_store(), ) .await .to_not_found_response( errors::ApiErrorResponse::MerchantConnectorAccountNotFound { - id: authentication - .merchant_connector_id - .get_string_repr() - .to_string(), + id: mca_id.get_string_repr().to_string(), }, ) } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index cf176e1562..4e0bdab3ce 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -596,9 +596,13 @@ pub enum Flow { TokenizationRetrieve, /// Clone Connector flow CloneConnector, + /// Authentication Create flow + AuthenticationCreate, ///Proxy Flow Proxy, + /// Profile Acquirer Create flow ProfileAcquirerCreate, + /// Profile Acquirer Update flow ProfileAcquirerUpdate, /// ThreeDs Decision Rule Execute flow ThreeDsDecisionRuleExecute, diff --git a/migrations/2025-05-19-130655_authentication_table_refactor/down.sql b/migrations/2025-05-19-130655_authentication_table_refactor/down.sql new file mode 100644 index 0000000000..e486cb79a5 --- /dev/null +++ b/migrations/2025-05-19-130655_authentication_table_refactor/down.sql @@ -0,0 +1,11 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE authentication + ALTER COLUMN authentication_connector SET NOT NULL, + ALTER COLUMN merchant_connector_id SET NOT NULL, + ADD COLUMN IF NOT EXISTS authentication_client_secret VARCHAR(128) NULL, + DROP COLUMN IF EXISTS force_3ds_challenge, + DROP COLUMN IF EXISTS psd2_sca_exemption_type, + DROP COLUMN IF EXISTS return_url, + DROP COLUMN IF EXISTS amount, + DROP COLUMN IF EXISTS currency; diff --git a/migrations/2025-05-19-130655_authentication_table_refactor/up.sql b/migrations/2025-05-19-130655_authentication_table_refactor/up.sql new file mode 100644 index 0000000000..86505ea124 --- /dev/null +++ b/migrations/2025-05-19-130655_authentication_table_refactor/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here + +ALTER TABLE authentication + ALTER COLUMN authentication_connector DROP NOT NULL, + ALTER COLUMN merchant_connector_id DROP NOT NULL, + ADD COLUMN IF NOT EXISTS authentication_client_secret VARCHAR(128) NULL, + ADD COLUMN IF NOT EXISTS force_3ds_challenge BOOLEAN NULL, + ADD COLUMN IF NOT EXISTS psd2_sca_exemption_type "ScaExemptionType" NULL, + ADD COLUMN IF NOT EXISTS return_url VARCHAR(2048) NULL, + ADD COLUMN IF NOT EXISTS amount BIGINT, + ADD COLUMN IF NOT EXISTS currency "Currency";