From c088d286c1a51286aad77ced11fb06e5e0013c60 Mon Sep 17 00:00:00 2001 From: Nishanth Challa Date: Mon, 3 Feb 2025 22:45:47 +0530 Subject: [PATCH 01/26] add revenue_rcovery_metadata to payment intent in diesel and api model --- crates/api_models/src/payments.rs | 35 +++++++++- crates/diesel_models/src/types.rs | 41 +++++++++++ crates/hyperswitch_domain_models/src/lib.rs | 76 ++++++++++++++++++++- 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b6152cc9787..7d6f7c06d58 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,7 +5,7 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; -use common_enums::ProductType; +use common_enums::{PaymentMethodType, ProductType,PaymentMethod}; #[cfg(feature = "v2")] use common_utils::id_type::GlobalPaymentId; use common_utils::{ @@ -6650,7 +6650,10 @@ pub struct FeatureMetadata { #[schema(value_type = Option>)] pub search_tags: Option>>, /// Recurring payment details required for apple pay Merchant Token - pub apple_pay_recurring_details: Option, + pub apple_pay_recurring_details: Option, + /// revenue recovery data for payment intent + #[cfg(feature= "v2" )] + pub revenue_recovery_metadata: Option, } #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] @@ -7506,3 +7509,31 @@ mod billing_from_payment_method_data { assert!(billing_details.is_none()); } } + +#[cfg(feature="v2")] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)] +pub struct RevenueRecoveryMetadata { + ///Total number of billing connector + recovery retries for a payment intent. + #[schema(value_type = i32,example = "1")] + pub retry_count : i32, + //if the payment_connector has been called or not + pub payment_connector_transmission : bool, + // Billing Connector Id to update the invoices + pub billing_connector_id : id_type::MerchantConnectorAccountId, + // Payment Connector Id to retry the payments + pub active_attempt_payment_connector_id : id_type::MerchantConnectorAccountId, + // Billing Connector Mit Token Details + pub billing_connector_mit_token_details : BillingConnectorMitTokenDetails, + //Payment Method Type + pub payment_method_type : PaymentMethod, + //PaymentMethod Subtype + pub payment_method_subtype : PaymentMethodType +} +#[derive(Debug, Clone, Eq, PartialEq,Serialize, Deserialize, ToSchema)] +#[cfg(feature="v2")] +pub struct BillingConnectorMitTokenDetails{ + //Payment Processor Token To process the retry payment + pub payment_processor_token : String, + //Connector Customer Id to process the retry payment + pub connector_customer_id : String, +} \ No newline at end of file diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 7806a7c6e1b..cdcb7fda448 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -5,6 +5,10 @@ use diesel::{ }; use masking::{Secret, WithType}; use serde::{Deserialize, Serialize}; +use common_enums::{ + PaymentMethod, + PaymentMethodType +}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Jsonb)] pub struct OrderDetailsWithAmount { @@ -50,6 +54,8 @@ pub struct FeatureMetadata { pub search_tags: Option>>, /// Recurring payment details required for apple pay Merchant Token pub apple_pay_recurring_details: Option, + #[cfg(feature = "v2")] + pub revenue_recovery_metadata: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] @@ -106,3 +112,38 @@ pub struct RedirectResponse { } impl masking::SerializableSecret for RedirectResponse {} common_utils::impl_to_sql_from_sql_json!(RedirectResponse); + +#[cfg(feature="v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct RevenueRecoveryMetadata { + ///Total number of billing connector + recovery retries for a payment intent. + pub retry_count : i32, + //if the payment_connector has been called or not + pub payment_connector_transmission : bool, + // Billing Connector Id to update the invoices + pub billing_connector_id : common_utils::id_type::MerchantConnectorAccountId, + // Payment Connector Id to retry the payments + pub active_attempt_payment_connector_id : common_utils::id_type::MerchantConnectorAccountId, + // Billing Connector Mit Token Details + pub billing_connector_mit_token_details : BillingConnectorMitTokenDetails, + //Payment Method Type + pub payment_method_type : PaymentMethod, + //PaymentMethod Subtype + pub payment_method_subtype : PaymentMethodType +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +#[cfg(feature="v2")] +pub struct BillingConnectorMitTokenDetails{ + //Payment Processor Token To process the retry payment + pub payment_processor_token : String, + //Connector Customer Id to process the retry payment + pub connector_customer_id : String, +} + +#[cfg(feature="v2")] +common_utils::impl_to_sql_from_sql_json!(RevenueRecoveryMetadata); +#[cfg(feature="v2")] +common_utils::impl_to_sql_from_sql_json!(BillingConnectorMitTokenDetails); diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index bd6dd2e5371..661fb27cb41 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -39,12 +39,27 @@ use api_models::payments::{ FeatureMetadata as ApiFeatureMetadata, OrderDetailsWithAmount as ApiOrderDetailsWithAmount, RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, RedirectResponse as ApiRedirectResponse, + +}; + +#[cfg(feature = "v2")] +use api_models::payments::{ + RevenueRecoveryMetadata as ApiRevenueRecoveryMetadata, + BillingConnectorMitTokenDetails as ApiBillingConnectorMitTokenDetails, }; + use diesel_models::types::{ ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, }; +#[cfg(feature = "v2")] +use diesel_models::types::{ + BillingConnectorMitTokenDetails, + RevenueRecoveryMetadata, +}; + + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { ForeignID(String), @@ -84,13 +99,19 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, + + #[cfg(feature = "v2")] + revenue_recovery_metadata, } = from; + Self { redirect_response: redirect_response.map(RedirectResponse::convert_from), search_tags, - apple_pay_recurring_details: apple_pay_recurring_details - .map(ApplePayRecurringDetails::convert_from), - } + apple_pay_recurring_details: apple_pay_recurring_details.map(ApplePayRecurringDetails::convert_from), + + #[cfg(feature = "v2")] + revenue_recovery_metadata: revenue_recovery_metadata.map(RevenueRecoveryMetadata::convert_from), + } } fn convert_back(self) -> ApiFeatureMetadata { @@ -98,6 +119,9 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, + + #[cfg(feature = "v2")] + revenue_recovery_metadata, } = self; ApiFeatureMetadata { redirect_response: redirect_response @@ -105,6 +129,8 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { search_tags, apple_pay_recurring_details: apple_pay_recurring_details .map(|value| value.convert_back()), + #[cfg(feature = "v2")] + revenue_recovery_metadata: revenue_recovery_metadata.map(|value| value.convert_back()), } } } @@ -204,6 +230,50 @@ impl ApiModelToDieselModelConvertor for ApplePayRec } } +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor for RevenueRecoveryMetadata { + fn convert_from(from: ApiRevenueRecoveryMetadata) -> Self { + Self { + retry_count: from.retry_count, + payment_connector_transmission: from.payment_connector_transmission, + billing_connector_id: from.billing_connector_id, + active_attempt_payment_connector_id: from.active_attempt_payment_connector_id, + billing_connector_mit_token_details: BillingConnectorMitTokenDetails::convert_from(from.billing_connector_mit_token_details), + payment_method_type: from.payment_method_type, + payment_method_subtype: from.payment_method_subtype, + } + } + + fn convert_back(self) -> ApiRevenueRecoveryMetadata { + ApiRevenueRecoveryMetadata { + retry_count: self.retry_count, + payment_connector_transmission: self.payment_connector_transmission, + billing_connector_id: self.billing_connector_id, + active_attempt_payment_connector_id: self.active_attempt_payment_connector_id, + billing_connector_mit_token_details: self.billing_connector_mit_token_details.convert_back(), + payment_method_type: self.payment_method_type, + payment_method_subtype: self.payment_method_subtype, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor for BillingConnectorMitTokenDetails { + fn convert_from(from: ApiBillingConnectorMitTokenDetails) -> Self { + Self { + payment_processor_token: from.payment_processor_token, + connector_customer_id: from.connector_customer_id, + } + } + + fn convert_back(self) -> ApiBillingConnectorMitTokenDetails { + ApiBillingConnectorMitTokenDetails { + payment_processor_token: self.payment_processor_token, + connector_customer_id: self.connector_customer_id, + } + } +} + impl ApiModelToDieselModelConvertor for OrderDetailsWithAmount { fn convert_from(from: ApiOrderDetailsWithAmount) -> Self { let ApiOrderDetailsWithAmount { From 9ddd2d7244af7c21aff0d02fe64becb2d6971631 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:10:49 +0000 Subject: [PATCH 02/26] chore: run formatter --- crates/api_models/src/payments.rs | 34 +++++++++--------- crates/diesel_models/src/types.rs | 33 ++++++++---------- crates/hyperswitch_domain_models/src/lib.rs | 38 ++++++++++----------- 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 7d6f7c06d58..d9e15f6a268 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,7 +5,7 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; -use common_enums::{PaymentMethodType, ProductType,PaymentMethod}; +use common_enums::{PaymentMethod, PaymentMethodType, ProductType}; #[cfg(feature = "v2")] use common_utils::id_type::GlobalPaymentId; use common_utils::{ @@ -6650,9 +6650,9 @@ pub struct FeatureMetadata { #[schema(value_type = Option>)] pub search_tags: Option>>, /// Recurring payment details required for apple pay Merchant Token - pub apple_pay_recurring_details: Option, + pub apple_pay_recurring_details: Option, /// revenue recovery data for payment intent - #[cfg(feature= "v2" )] + #[cfg(feature = "v2")] pub revenue_recovery_metadata: Option, } @@ -7510,30 +7510,30 @@ mod billing_from_payment_method_data { } } -#[cfg(feature="v2")] +#[cfg(feature = "v2")] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)] pub struct RevenueRecoveryMetadata { ///Total number of billing connector + recovery retries for a payment intent. #[schema(value_type = i32,example = "1")] - pub retry_count : i32, + pub retry_count: i32, //if the payment_connector has been called or not - pub payment_connector_transmission : bool, + pub payment_connector_transmission: bool, // Billing Connector Id to update the invoices - pub billing_connector_id : id_type::MerchantConnectorAccountId, + pub billing_connector_id: id_type::MerchantConnectorAccountId, // Payment Connector Id to retry the payments - pub active_attempt_payment_connector_id : id_type::MerchantConnectorAccountId, + pub active_attempt_payment_connector_id: id_type::MerchantConnectorAccountId, // Billing Connector Mit Token Details - pub billing_connector_mit_token_details : BillingConnectorMitTokenDetails, + pub billing_connector_mit_token_details: BillingConnectorMitTokenDetails, //Payment Method Type - pub payment_method_type : PaymentMethod, + pub payment_method_type: PaymentMethod, //PaymentMethod Subtype - pub payment_method_subtype : PaymentMethodType + pub payment_method_subtype: PaymentMethodType, } -#[derive(Debug, Clone, Eq, PartialEq,Serialize, Deserialize, ToSchema)] -#[cfg(feature="v2")] -pub struct BillingConnectorMitTokenDetails{ +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)] +#[cfg(feature = "v2")] +pub struct BillingConnectorMitTokenDetails { //Payment Processor Token To process the retry payment - pub payment_processor_token : String, + pub payment_processor_token: String, //Connector Customer Id to process the retry payment - pub connector_customer_id : String, -} \ No newline at end of file + pub connector_customer_id: String, +} diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index cdcb7fda448..27efbac02c4 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -1,3 +1,4 @@ +use common_enums::{PaymentMethod, PaymentMethodType}; use common_utils::{hashing::HashedString, pii, types::MinorUnit}; use diesel::{ sql_types::{Json, Jsonb}, @@ -5,10 +6,6 @@ use diesel::{ }; use masking::{Secret, WithType}; use serde::{Deserialize, Serialize}; -use common_enums::{ - PaymentMethod, - PaymentMethodType -}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Jsonb)] pub struct OrderDetailsWithAmount { @@ -113,37 +110,37 @@ pub struct RedirectResponse { impl masking::SerializableSecret for RedirectResponse {} common_utils::impl_to_sql_from_sql_json!(RedirectResponse); -#[cfg(feature="v2")] +#[cfg(feature = "v2")] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] pub struct RevenueRecoveryMetadata { ///Total number of billing connector + recovery retries for a payment intent. - pub retry_count : i32, + pub retry_count: i32, //if the payment_connector has been called or not - pub payment_connector_transmission : bool, + pub payment_connector_transmission: bool, // Billing Connector Id to update the invoices - pub billing_connector_id : common_utils::id_type::MerchantConnectorAccountId, + pub billing_connector_id: common_utils::id_type::MerchantConnectorAccountId, // Payment Connector Id to retry the payments - pub active_attempt_payment_connector_id : common_utils::id_type::MerchantConnectorAccountId, + pub active_attempt_payment_connector_id: common_utils::id_type::MerchantConnectorAccountId, // Billing Connector Mit Token Details - pub billing_connector_mit_token_details : BillingConnectorMitTokenDetails, + pub billing_connector_mit_token_details: BillingConnectorMitTokenDetails, //Payment Method Type - pub payment_method_type : PaymentMethod, + pub payment_method_type: PaymentMethod, //PaymentMethod Subtype - pub payment_method_subtype : PaymentMethodType + pub payment_method_subtype: PaymentMethodType, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] -#[cfg(feature="v2")] -pub struct BillingConnectorMitTokenDetails{ +#[cfg(feature = "v2")] +pub struct BillingConnectorMitTokenDetails { //Payment Processor Token To process the retry payment - pub payment_processor_token : String, + pub payment_processor_token: String, //Connector Customer Id to process the retry payment - pub connector_customer_id : String, + pub connector_customer_id: String, } -#[cfg(feature="v2")] +#[cfg(feature = "v2")] common_utils::impl_to_sql_from_sql_json!(RevenueRecoveryMetadata); -#[cfg(feature="v2")] +#[cfg(feature = "v2")] common_utils::impl_to_sql_from_sql_json!(BillingConnectorMitTokenDetails); diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 661fb27cb41..f5e2b8dfa5f 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -39,26 +39,18 @@ use api_models::payments::{ FeatureMetadata as ApiFeatureMetadata, OrderDetailsWithAmount as ApiOrderDetailsWithAmount, RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, RedirectResponse as ApiRedirectResponse, - }; - #[cfg(feature = "v2")] use api_models::payments::{ - RevenueRecoveryMetadata as ApiRevenueRecoveryMetadata, BillingConnectorMitTokenDetails as ApiBillingConnectorMitTokenDetails, + RevenueRecoveryMetadata as ApiRevenueRecoveryMetadata, }; - use diesel_models::types::{ ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, }; - #[cfg(feature = "v2")] -use diesel_models::types::{ - BillingConnectorMitTokenDetails, - RevenueRecoveryMetadata, -}; - +use diesel_models::types::{BillingConnectorMitTokenDetails, RevenueRecoveryMetadata}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { @@ -103,15 +95,17 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { #[cfg(feature = "v2")] revenue_recovery_metadata, } = from; - + Self { redirect_response: redirect_response.map(RedirectResponse::convert_from), search_tags, - apple_pay_recurring_details: apple_pay_recurring_details.map(ApplePayRecurringDetails::convert_from), - + apple_pay_recurring_details: apple_pay_recurring_details + .map(ApplePayRecurringDetails::convert_from), + #[cfg(feature = "v2")] - revenue_recovery_metadata: revenue_recovery_metadata.map(RevenueRecoveryMetadata::convert_from), - } + revenue_recovery_metadata: revenue_recovery_metadata + .map(RevenueRecoveryMetadata::convert_from), + } } fn convert_back(self) -> ApiFeatureMetadata { @@ -238,7 +232,9 @@ impl ApiModelToDieselModelConvertor for RevenueRecov payment_connector_transmission: from.payment_connector_transmission, billing_connector_id: from.billing_connector_id, active_attempt_payment_connector_id: from.active_attempt_payment_connector_id, - billing_connector_mit_token_details: BillingConnectorMitTokenDetails::convert_from(from.billing_connector_mit_token_details), + billing_connector_mit_token_details: BillingConnectorMitTokenDetails::convert_from( + from.billing_connector_mit_token_details, + ), payment_method_type: from.payment_method_type, payment_method_subtype: from.payment_method_subtype, } @@ -250,15 +246,19 @@ impl ApiModelToDieselModelConvertor for RevenueRecov payment_connector_transmission: self.payment_connector_transmission, billing_connector_id: self.billing_connector_id, active_attempt_payment_connector_id: self.active_attempt_payment_connector_id, - billing_connector_mit_token_details: self.billing_connector_mit_token_details.convert_back(), + billing_connector_mit_token_details: self + .billing_connector_mit_token_details + .convert_back(), payment_method_type: self.payment_method_type, payment_method_subtype: self.payment_method_subtype, } } -} +} #[cfg(feature = "v2")] -impl ApiModelToDieselModelConvertor for BillingConnectorMitTokenDetails { +impl ApiModelToDieselModelConvertor + for BillingConnectorMitTokenDetails +{ fn convert_from(from: ApiBillingConnectorMitTokenDetails) -> Self { Self { payment_processor_token: from.payment_processor_token, From 76ccac45a3f3989affd9fe179c9f87e0ba075358 Mon Sep 17 00:00:00 2001 From: Nishanth Challa Date: Tue, 4 Feb 2025 11:33:13 +0530 Subject: [PATCH 03/26] remove unncessary imports --- crates/api_models/src/payments.rs | 6 +++--- crates/diesel_models/src/types.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index d9e15f6a268..b334c595aaa 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,7 +5,7 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; -use common_enums::{PaymentMethod, PaymentMethodType, ProductType}; +use common_enums::ProductType; #[cfg(feature = "v2")] use common_utils::id_type::GlobalPaymentId; use common_utils::{ @@ -7525,9 +7525,9 @@ pub struct RevenueRecoveryMetadata { // Billing Connector Mit Token Details pub billing_connector_mit_token_details: BillingConnectorMitTokenDetails, //Payment Method Type - pub payment_method_type: PaymentMethod, + pub payment_method_type: common_enums::PaymentMethod, //PaymentMethod Subtype - pub payment_method_subtype: PaymentMethodType, + pub payment_method_subtype: common_enums::PaymentMethodType, } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)] #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 27efbac02c4..1fac92270b4 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "v2")] use common_enums::{PaymentMethod, PaymentMethodType}; use common_utils::{hashing::HashedString, pii, types::MinorUnit}; use diesel::{ From f3338d870197dc23a56676fb06b2ac899dff921d Mon Sep 17 00:00:00 2001 From: Nishanth Challa Date: Tue, 4 Feb 2025 15:29:44 +0530 Subject: [PATCH 04/26] refactor structs of Feature Metadata --- crates/api_models/src/payments.rs | 19 ++++++++++++++++++- crates/diesel_models/src/types.rs | 16 +++++++++++++++- crates/hyperswitch_domain_models/src/lib.rs | 9 ++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b334c595aaa..1c2d448d154 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6640,6 +6640,7 @@ pub struct PaymentsStartRequest { } /// additional data that might be required by hyperswitch +#[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub struct FeatureMetadata { /// Redirection response coming in request as metadata field only for redirection scenarios @@ -6652,10 +6653,26 @@ pub struct FeatureMetadata { /// Recurring payment details required for apple pay Merchant Token pub apple_pay_recurring_details: Option, /// revenue recovery data for payment intent - #[cfg(feature = "v2")] pub revenue_recovery_metadata: Option, } +/// additional data that might be required by hyperswitch +#[cfg(feature = "v1")] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct FeatureMetadata { + /// Redirection response coming in request as metadata field only for redirection scenarios + #[schema(value_type = Option)] + pub redirect_response: Option, + // TODO: Convert this to hashedstrings to avoid PII sensitive data + /// Additional tags to be used for global search + #[schema(value_type = Option>)] + pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + + + #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ApplePayRecurringDetails { /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 1fac92270b4..715dd4b2170 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -42,6 +42,7 @@ impl masking::SerializableSecret for OrderDetailsWithAmount {} common_utils::impl_to_sql_from_sql_json!(OrderDetailsWithAmount); +#[cfg(feature = "v2")] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] pub struct FeatureMetadata { @@ -52,10 +53,23 @@ pub struct FeatureMetadata { pub search_tags: Option>>, /// Recurring payment details required for apple pay Merchant Token pub apple_pay_recurring_details: Option, - #[cfg(feature = "v2")] + /// revenue recovery data for payment intent pub revenue_recovery_metadata: Option, } +#[cfg(feature = "v1")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct FeatureMetadata { + /// Redirection response coming in request as metadata field only for redirection scenarios + pub redirect_response: Option, + // TODO: Convert this to hashedstrings to avoid PII sensitive data + /// Additional tags to be used for global search + pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] pub struct ApplePayRecurringDetails { diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index f5e2b8dfa5f..3791e2cea5d 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -109,14 +109,21 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { } fn convert_back(self) -> ApiFeatureMetadata { + #[cfg(feature = "v1")] let Self { redirect_response, search_tags, apple_pay_recurring_details, + } = self; - #[cfg(feature = "v2")] + #[cfg(feature = "v2")] + let Self { + redirect_response, + search_tags, + apple_pay_recurring_details, revenue_recovery_metadata, } = self; + ApiFeatureMetadata { redirect_response: redirect_response .map(|redirect_response| redirect_response.convert_back()), From c846fabe47b01db541c237971b724727ebb2ef82 Mon Sep 17 00:00:00 2001 From: Nishanth Challa Date: Tue, 4 Feb 2025 16:00:29 +0530 Subject: [PATCH 05/26] refactor(router) : add revenue_recovery_metadata to feature_metadata in payment intent for v2 flow --- crates/hyperswitch_domain_models/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 3791e2cea5d..41fbbb3aeb3 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -87,12 +87,18 @@ pub trait ApiModelToDieselModelConvertor { impl ApiModelToDieselModelConvertor for FeatureMetadata { fn convert_from(from: ApiFeatureMetadata) -> Self { + #[cfg(feature = "v1")] let ApiFeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, + } = from; - #[cfg(feature = "v2")] + #[cfg(feature = "v2")] + let ApiFeatureMetadata { + redirect_response, + search_tags, + apple_pay_recurring_details, revenue_recovery_metadata, } = from; From a0759ebec2b99f615c52b254afe78b0ed0ad1b0a Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:17:13 +0000 Subject: [PATCH 06/26] chore: run formatter --- crates/api_models/src/payments.rs | 2 -- crates/hyperswitch_domain_models/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 1c2d448d154..1d73dce708f 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6671,8 +6671,6 @@ pub struct FeatureMetadata { pub apple_pay_recurring_details: Option, } - - #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ApplePayRecurringDetails { /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 41fbbb3aeb3..3a25884cbd0 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -129,7 +129,7 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { apple_pay_recurring_details, revenue_recovery_metadata, } = self; - + ApiFeatureMetadata { redirect_response: redirect_response .map(|redirect_response| redirect_response.convert_back()), From db0ebafc6aca7228744d464c3d295d77ed91b772 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 24 Jan 2025 14:41:16 +0530 Subject: [PATCH 07/26] add v2 version of proxy_payments function --- crates/router/src/core/payments.rs | 307 +++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e6cd8409343..93862039e22 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1025,6 +1025,103 @@ where )) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[instrument(skip_all, fields(payment_id, merchant_id))] +pub async fn proxy_for_payments_operation_core( + state: &SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, + operation: Op, + req: Req, + get_tracker_response: operations::GetTrackerResponse, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResult<(D, Req, Option, Option, Option)> +where + F: Send + Clone + Sync, + Req: Send + Sync, + Op: Operation + Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + + // To create connector flow specific interface data + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, + + RouterData: + hyperswitch_domain_models::router_data::TrackerPostUpdateObjects, + + // To perform router related operation for PaymentResponse + PaymentResponse: Operation, + FData: Send + Sync + Clone, +{ + let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation); + + // Get the trackers related to track the state of the payment + let operations::GetTrackerResponse { mut payment_data } = get_tracker_response; + + let connector = operation + .to_domain()? + .get_connector( + &merchant_account, + state, + &profile, + payment_data.get_payment_intent(), + &key_store, + ) + .await?; + + let payment_data = match connector { + ConnectorCallType::PreDetermined(connector_data) => { + let router_data = proxy_for_call_connector_service( + state, + req_state.clone(), + &merchant_account, + &key_store, + connector_data.clone(), + &operation, + &mut payment_data, + &None, + call_connector_action.clone(), + None, + header_payload.clone(), + &profile, + ) + .await?; + + let payments_response_operation = Box::new(PaymentResponse); + + payments_response_operation + .to_post_update_tracker()? + .update_tracker( + state, + payment_data, + router_data, + &key_store, + merchant_account.storage_scheme, + ) + .await? + } + ConnectorCallType::Retryable(vec) => todo!(), + ConnectorCallType::SessionMultiple(vec) => todo!(), + ConnectorCallType::Skip => payment_data, + }; + + Ok(( + payment_data, + req, + None, + router_data.connector_http_status_code, + router_data.external_latency, + )) +} + #[cfg(feature = "v2")] #[allow(clippy::too_many_arguments, clippy::type_complexity)] #[instrument(skip_all, fields(payment_id, merchant_id))] @@ -1476,6 +1573,85 @@ where ) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn proxy_for_payments_core( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + operation: Op, + req: Req, + payment_id: id_type::GlobalPaymentId, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResponse +where + F: Send + Clone + Sync, + Req: Send + Sync, + FData: Send + Sync + Clone, + Op: Operation + ValidateStatusForOperation + Send + Sync + Clone, + Req: Debug, + D: OperationSessionGetters + + OperationSessionSetters + + transformers::GenerateResponse + + Send + + Sync + + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature, + + dyn api::Connector: + services::api::ConnectorIntegration, + + PaymentResponse: Operation, + + RouterData: + hyperswitch_domain_models::router_data::TrackerPostUpdateObjects, +{ + operation + .to_validate_request()? + .validate_request(&req, &merchant_account)?; + + let get_tracker_response = operation + .to_get_tracker()? + .get_trackers( + &state, + &payment_id, + &req, + &merchant_account, + &profile, + &key_store, + &header_payload, + None, + ) + .await?; + + let (payment_data, _req, customer, connector_http_status_code, external_latency) = + proxy_for_payments_operation_core::<_, _, _, _, _>( + &state, + req_state, + merchant_account.clone(), + key_store, + profile, + operation.clone(), + req, + get_tracker_response, + call_connector_action, + header_payload.clone(), + ) + .await?; + + payment_data.generate_response( + &state, + connector_http_status_code, + external_latency, + header_payload.x_hs_latency, + &merchant_account, + ) +} + #[cfg(feature = "v2")] #[allow(clippy::too_many_arguments)] pub async fn payments_intent_core( @@ -3006,6 +3182,137 @@ where Ok((router_data, merchant_connector_account)) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub async fn proxy_for_call_connector_service( + state: &SessionState, + req_state: ReqState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + customer: &Option, + call_connector_action: CallConnectorAction, + schedule_time: Option, + header_payload: HeaderPayload, + business_profile: &domain::Profile, +) -> RouterResult> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let stime_connector = Instant::now(); + + let merchant_connector_id = connector + .merchant_connector_id + .as_ref() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector id is not set")?; + + let merchant_connector_account = state + .store + .find_merchant_connector_account_by_id(&state.into(), merchant_connector_id, key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_owned(), + })?; + + let mut router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_account, + key_store, + customer, + &merchant_connector_account, + None, + None, + ) + .await?; + + let add_access_token_result = router_data + .add_access_token( + state, + &connector, + merchant_account, + payment_data.get_creds_identifier(), + ) + .await?; + + router_data = router_data.add_session_token(state, &connector).await?; + + let mut should_continue_further = access_token::update_router_data_with_access_token_result( + &add_access_token_result, + &mut router_data, + &call_connector_action, + ); + + (router_data, should_continue_further) = complete_preprocessing_steps_if_required( + state, + &connector, + payment_data, + router_data, + operation, + should_continue_further, + ) + .await?; + + let (connector_request, should_continue_further) = if should_continue_further { + router_data + .build_flow_specific_connector_request(state, &connector, call_connector_action.clone()) + .await? + } else { + (None, false) + }; + + (_, *payment_data) = operation + .to_update_tracker()? + .update_trackers( + state, + req_state, + payment_data.clone(), + customer.clone(), + merchant_account.storage_scheme, + None, + key_store, + None, + header_payload.clone(), + ) + .await?; + + let router_data = if should_continue_further { + router_data + .decide_flows( + state, + &connector, + call_connector_action, + connector_request, + business_profile, + header_payload.clone(), + ) + .await + } else { + Ok(router_data) + }?; + + let etime_connector = Instant::now(); + let duration_connector = etime_connector.saturating_duration_since(stime_connector); + tracing::info!(duration = format!("Duration taken: {}", duration_connector.as_millis())); + + Ok(router_data) +} + pub async fn add_decrypted_payment_method_token( tokenization_action: TokenizationAction, payment_data: &D, From d34f32f82f27231d7c9776586d473d4c08db442e Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 27 Jan 2025 12:39:29 +0530 Subject: [PATCH 08/26] correct the construct_router_data and update_trackers --- crates/router/src/core/payments.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 93862039e22..f15b918ad69 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3234,7 +3234,7 @@ where connector.connector.id(), merchant_account, key_store, - customer, + &None, &merchant_connector_account, None, None, @@ -3282,7 +3282,7 @@ where state, req_state, payment_data.clone(), - customer.clone(), + None, merchant_account.storage_scheme, None, key_store, From c9ff63e6316b78067e7541f2059dec575466ba88 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 4 Feb 2025 19:44:23 +0530 Subject: [PATCH 09/26] add proxy-confirm-intent api --- crates/api_models/src/events/payment.rs | 11 +- crates/api_models/src/payments.rs | 104 ++++ .../hyperswitch_domain_models/src/payments.rs | 37 ++ .../src/payments/payment_attempt.rs | 74 +++ .../src/router_data.rs | 1 + .../src/router_request_types.rs | 53 ++ crates/router/src/core/payments.rs | 53 +- crates/router/src/core/payments/operations.rs | 3 + .../payments/operations/payment_response.rs | 4 +- .../operations/proxy_payments_intent.rs | 451 ++++++++++++++++++ .../router/src/core/payments/transformers.rs | 88 ++++ crates/router/src/routes/app.rs | 4 + crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/payments.rs | 74 ++- crates/router_env/src/logger/types.rs | 1 + 15 files changed, 935 insertions(+), 26 deletions(-) create mode 100644 crates/router/src/core/payments/operations/proxy_payments_intent.rs diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index e6de6190b83..a0bad7bb6c0 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -3,7 +3,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; #[cfg(feature = "v2")] use super::{ PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, - PaymentsGetIntentRequest, PaymentsIntentResponse, + PaymentsGetIntentRequest, PaymentsIntentResponse,ProxyPaymentsIntentResponse, }; #[cfg(all( any(feature = "v2", feature = "v1"), @@ -177,6 +177,15 @@ impl ApiEventMetric for PaymentsConfirmIntentResponse { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for ProxyPaymentsIntentResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + #[cfg(feature = "v2")] impl ApiEventMetric for super::PaymentsRetrieveResponse { fn get_api_event_type(&self) -> Option { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 1d73dce708f..81c2cf63339 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4782,6 +4782,43 @@ pub struct PaymentsConfirmIntentRequest { /// Additional details required by 3DS 2.0 #[schema(value_type = Option)] pub browser_info: Option, + +} + +#[cfg(feature = "v2")] +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct ProxyPaymentsIntentRequest { + /// The URL to which you want the user to be redirected after the completion of the payment operation + /// If this url is not passed, the url configured in the business profile will be used + #[schema(value_type = Option, example = "https://hyperswitch.io")] + pub return_url: Option, + + pub amount: AmountDetails, + + pub recurring_details:RecurringDetails, + + pub connector_customer_id:String, + + /// The payment metho:d type to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method_type: api_enums::PaymentMethod, + + /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + pub shipping: Option
, + + /// Additional details required by 3DS 2.0 + #[schema(value_type = Option)] + pub browser_info: Option, + + #[schema(example = "stripe")] + pub connector: String, + + #[schema(value_type = String)] + pub merchant_connector_id: id_type::MerchantConnectorAccountId, } // Serialize is implemented because, this will be serialized in the api events. @@ -4894,6 +4931,73 @@ pub struct PaymentsConfirmIntentResponse { pub error: Option, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct ProxyPaymentsIntentResponse { + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + #[schema(value_type = IntentStatus, example = "success")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// The connector used for the payment + #[schema(example = "stripe")] + pub connector: String, + + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: common_utils::types::ClientSecret, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method information provided for making a payment + #[schema(value_type = Option)] + #[serde(serialize_with = "serialize_payment_method_data_response")] + pub payment_method_data: Option, + + /// The payment method type for this payment attempt + #[schema(value_type = PaymentMethod, example = "wallet")] + pub payment_method_type: api_enums::PaymentMethod, + + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// Additional information required for redirection + pub next_action: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_transaction_id: Option, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = String)] + pub merchant_connector_id: id_type::MerchantConnectorAccountId, + + /// The browser information used for this payment + #[schema(value_type = Option)] + pub browser_info: Option, + + /// Error details for the payment if any + pub error: Option, +} + // TODO: have a separate response for detailed, summarized /// Response for Payment Intent Confirm #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 095b4c33a46..32c25977595 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -242,6 +242,43 @@ impl AmountDetails { }) } + pub fn proxy_create_attempt_amount_details( + &self, + confirm_intent_request: &api_models::payments::ProxyPaymentsIntentRequest, + ) -> payment_attempt::AttemptAmountDetails { + let net_amount = self.calculate_net_amount(); + + let surcharge_amount = match self.skip_surcharge_calculation { + common_enums::SurchargeCalculationOverride::Skip => self.surcharge_amount, + common_enums::SurchargeCalculationOverride::Calculate => None, + }; + + let tax_on_surcharge = match self.skip_surcharge_calculation { + common_enums::SurchargeCalculationOverride::Skip => self.tax_on_surcharge, + common_enums::SurchargeCalculationOverride::Calculate => None, + }; + + let order_tax_amount = match self.skip_external_tax_calculation { + common_enums::TaxCalculationOverride::Skip => { + self.tax_details.as_ref().and_then(|tax_details| { + tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype) + }) + } + common_enums::TaxCalculationOverride::Calculate => None, + }; + + payment_attempt::AttemptAmountDetails::from(payment_attempt::AttemptAmountDetailsSetter { + net_amount, + amount_to_capture: None, + surcharge_amount, + tax_on_surcharge, + // This will be updated when we receive response from the connector + amount_capturable: MinorUnit::zero(), + shipping_cost: self.shipping_cost, + order_tax_amount, + }) + } + pub fn update_from_request(self, req: &api_models::payments::AmountDetailsUpdate) -> Self { Self { order_amount: req diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 0a7ada39ecd..af1c2eef7a1 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -522,6 +522,80 @@ impl PaymentAttempt { id, }) } + + #[cfg(feature = "v2")] + pub async fn proxy_create_domain_model( + payment_intent: &super::PaymentIntent, + cell_id: id_type::CellId, + storage_scheme: storage_enums::MerchantStorageScheme, + request: &api_models::payments::ProxyPaymentsIntentRequest, + encrypted_data: DecryptedPaymentAttempt, + ) -> CustomResult { + let id = id_type::GlobalAttemptId::generate(&cell_id); + let intent_amount_details = payment_intent.amount_details.clone(); + + let attempt_amount_details = intent_amount_details.proxy_create_attempt_amount_details(request); + + let now = common_utils::date_time::now(); + let payment_method_billing_address = encrypted_data + .payment_method_billing_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode billing address")?; + + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: Some(request.connector.clone()), + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + redirection_data: None, + encoded_data: None, + merchant_connector_id: Some(request.merchant_connector_id.clone()), + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: request.payment_method_type, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: request.payment_method_subtype, + authentication_applied: None, + external_reference_id: None, + payment_method_billing_address, + error: None, + connector_mandate_detail: None, + id, + }) + } } #[cfg(feature = "v1")] diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 8e4c79a965e..448a7c9a600 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -637,6 +637,7 @@ impl } } + #[cfg(feature = "v2")] impl TrackerPostUpdateObjects< diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 4598fa5d1b5..66de5dcf6c9 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -72,7 +72,60 @@ pub struct PaymentsAuthorizeData { pub shipping_cost: Option, pub additional_payment_method_data: Option, } +pub struct ProxyPaymentsAuthorizeData { + // pub payment_method_data: PaymentMethodData, + /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount) + /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately + /// ```text + /// get_original_amount() + /// get_surcharge_amount() + /// get_tax_on_surcharge_amount() + /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount + /// ``` + pub amount: i64, + pub order_tax_amount: Option, + // pub email: Option, + // pub customer_name: Option>, + pub currency: storage_enums::Currency, + pub confirm: bool, + pub statement_descriptor_suffix: Option, + pub statement_descriptor: Option, + pub capture_method: Option, + pub router_return_url: Option, + pub webhook_url: Option, + pub complete_authorize_url: Option, + // Mandates + pub setup_future_usage: Option, + pub mandate_id: Option, + pub off_session: Option, + // pub customer_acceptance: Option, + pub setup_mandate_details: Option, + pub browser_info: Option, + pub order_details: Option>, + pub order_category: Option, + pub session_token: Option, + pub enrolled_for_3ds: bool, + pub related_transaction_id: Option, + pub payment_experience: Option, + // pub payment_method_type: Option, + pub surcharge_details: Option, + // pub customer_id: Option, + pub request_incremental_authorization: bool, + pub metadata: Option, + pub authentication_data: Option, + pub split_payments: Option, + + // New amount for amount frame work + pub minor_amount: MinorUnit, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector + /// if the connector provides support to accept multiple reference ids. + /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. + pub merchant_order_reference_id: Option, + pub integrity_object: Option, + pub shipping_cost: Option, + // pub additional_payment_method_data: Option, +} #[derive(Debug, Clone)] pub struct PaymentsPostSessionTokensData { // amount here would include amount, surcharge_amount and shipping_cost diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f15b918ad69..c3509c9407c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4,7 +4,7 @@ pub mod connector_integration_v2_impls; pub mod customers; pub mod flows; pub mod helpers; -pub mod operations; +pub mod operations; #[cfg(feature = "retry")] pub mod retry; @@ -73,7 +73,7 @@ use time; pub use self::operations::{ PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, - PaymentSessionUpdate, PaymentStatus, PaymentUpdate, + PaymentSessionUpdate, PaymentStatus, PaymentUpdate }; use self::{ conditional_configs::perform_decision_management, @@ -1039,7 +1039,7 @@ pub async fn proxy_for_payments_operation_core( get_tracker_response: operations::GetTrackerResponse, call_connector_action: CallConnectorAction, header_payload: HeaderPayload, -) -> RouterResult<(D, Req, Option, Option, Option)> +) -> RouterResult<(D, Req, Option, Option)> where F: Send + Clone + Sync, Req: Send + Sync, @@ -1065,14 +1065,14 @@ where // Get the trackers related to track the state of the payment let operations::GetTrackerResponse { mut payment_data } = get_tracker_response; - + // consume the req merchant_connector_id and set it in the payment_data let connector = operation .to_domain()? - .get_connector( + .perform_routing( &merchant_account, - state, &profile, - payment_data.get_payment_intent(), + state, + &mut payment_data, &key_store, ) .await?; @@ -1087,9 +1087,7 @@ where connector_data.clone(), &operation, &mut payment_data, - &None, call_connector_action.clone(), - None, header_payload.clone(), &profile, ) @@ -1113,13 +1111,7 @@ where ConnectorCallType::Skip => payment_data, }; - Ok(( - payment_data, - req, - None, - router_data.connector_http_status_code, - router_data.external_latency, - )) + Ok((payment_data,req,None,None)) } #[cfg(feature = "v2")] @@ -1628,7 +1620,7 @@ where ) .await?; - let (payment_data, _req, customer, connector_http_status_code, external_latency) = + let (payment_data, _req, connector_http_status_code, external_latency) = proxy_for_payments_operation_core::<_, _, _, _, _>( &state, req_state, @@ -3193,9 +3185,7 @@ pub async fn proxy_for_call_connector_service( connector: api::ConnectorData, operation: &BoxedOperation<'_, F, ApiRequest, D>, payment_data: &mut D, - customer: &Option, call_connector_action: CallConnectorAction, - schedule_time: Option, header_payload: HeaderPayload, business_profile: &domain::Profile, ) -> RouterResult> @@ -7219,7 +7209,7 @@ pub trait OperationSessionGetters { fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails>; fn get_force_sync(&self) -> Option; fn get_capture_method(&self) -> Option; - + fn get_merchant_connector_id_in_attempt(&self) -> Option; #[cfg(feature = "v2")] fn get_optional_payment_attempt(&self) -> Option<&storage::PaymentAttempt>; } @@ -7288,7 +7278,9 @@ impl OperationSessionGetters for PaymentData { fn get_address(&self) -> &PaymentAddress { &self.address } - + fn get_merchant_connector_id_in_attempt(&self) -> Option { + self.payment_attempt.merchant_connector_id.clone() + } fn get_creds_identifier(&self) -> Option<&str> { self.creds_identifier.as_deref() } @@ -7606,6 +7598,10 @@ impl OperationSessionGetters for PaymentIntentData { } fn get_payment_attempt_connector(&self) -> Option<&str> { + todo!() + } + + fn get_merchant_connector_id_in_attempt(&self) -> Option{ todo!() } @@ -7818,7 +7814,11 @@ impl OperationSessionGetters for PaymentConfirmData { } fn get_payment_attempt_connector(&self) -> Option<&str> { - todo!() + self.payment_attempt.connector.as_deref() + } + + fn get_merchant_connector_id_in_attempt(&self) -> Option { + self.payment_attempt.merchant_connector_id.clone() } fn get_billing_address(&self) -> Option { @@ -7940,6 +7940,7 @@ impl OperationSessionSetters for PaymentConfirmData { } } + #[cfg(feature = "v2")] impl OperationSessionGetters for PaymentStatusData { #[track_caller] @@ -8035,6 +8036,10 @@ impl OperationSessionGetters for PaymentStatusData { todo!() } + fn get_merchant_connector_id_in_attempt(&self) -> Option{ + todo!() + } + fn get_billing_address(&self) -> Option { todo!() } @@ -8249,6 +8254,10 @@ impl OperationSessionGetters for PaymentCaptureData { todo!() } + fn get_merchant_connector_id_in_attempt(&self) -> Option{ + todo!() + } + fn get_billing_address(&self) -> Option { todo!() } diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 0a264f24e93..3dbf543b323 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -32,6 +32,9 @@ pub mod tax_calculation; #[cfg(feature = "v2")] pub mod payment_confirm_intent; +#[cfg(feature = "v2")] +pub mod proxy_payments_intent; + #[cfg(feature = "v2")] pub mod payment_create_intent; #[cfg(feature = "v2")] diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9692b1ca8a5..45314455f81 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -14,7 +14,7 @@ use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; #[cfg(feature = "v2")] use hyperswitch_domain_models::payments::{ - PaymentConfirmData, PaymentIntentData, PaymentStatusData, + PaymentConfirmData, PaymentIntentData, PaymentStatusData }; use router_derive; use router_env::{instrument, logger, tracing}; @@ -2204,6 +2204,8 @@ impl Operation for PaymentResp } } + + #[cfg(feature = "v2")] impl Operation for PaymentResponse { type Data = hyperswitch_domain_models::payments::PaymentCaptureData; diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs new file mode 100644 index 00000000000..ef760ad5397 --- /dev/null +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -0,0 +1,451 @@ +use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest,PostUpdateTracker}; +use crate::{ + core::{ + errors::{self, CustomResult, RouterResult, StorageErrorExt}, + payments::operations::{self, ValidateStatusForOperation}, + }, + routes::{app::ReqState, SessionState}, + types::{ + self, + api::{self, ConnectorCallType}, + domain::{self, types as domain_types}, + storage::{self, enums as storage_enums}, + }, + utils::OptionExt, +}; +use api_models::payments::ProxyPaymentsIntentRequest; +use api_models::enums::FrmSuggestion; + +use async_trait::async_trait; +use common_utils::types::keymanager::ToEncryptable; +use error_stack::ResultExt; +use hyperswitch_domain_models::payments::PaymentConfirmData; +use masking::PeekInterface; +use router_env::{instrument, tracing}; +use common_enums::enums; + +#[derive(Debug, Clone, Copy)] +pub struct PaymentProxyIntent; + +impl ValidateStatusForOperation for PaymentProxyIntent { + /// Validate if the current operation can be performed on the current status of the payment intent + fn validate_status_for_operation( + &self, + intent_status: common_enums::IntentStatus, + ) -> Result<(), errors::ApiErrorResponse> { + match intent_status { + common_enums::IntentStatus::RequiresPaymentMethod => Ok(()), + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::Processing + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => { + Err(errors::ApiErrorResponse::PaymentUnexpectedState { + current_flow: format!("{self:?}"), + field_name: "status".to_string(), + current_value: intent_status.to_string(), + states: ["requires_payment_method".to_string()].join(", "), + }) + } + } + } +} +type BoxedConfirmOperation<'b, F> = + super::BoxedOperation<'b, F, ProxyPaymentsIntentRequest, PaymentConfirmData>; + +impl Operation for &PaymentProxyIntent { + type Data = PaymentConfirmData; + fn to_validate_request( + &self, + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + { + Ok(*self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(*self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(*self) + } +} + +#[automatically_derived] +impl Operation for PaymentProxyIntent { + type Data = PaymentConfirmData; + fn to_validate_request( + &self, + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + { + Ok(self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&dyn Domain> { + Ok(self) + } + fn to_update_tracker( + &self, + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + { + Ok(self) + } +} + +impl ValidateRequest> + for PaymentProxyIntent +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + _request: &ProxyPaymentsIntentRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult { + let validate_result = operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }; + + Ok(validate_result) + } +} + +#[async_trait] +impl GetTracker, ProxyPaymentsIntentRequest> + for PaymentProxyIntent +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &common_utils::id_type::GlobalPaymentId, + request: &ProxyPaymentsIntentRequest, + merchant_account: &domain::MerchantAccount, + _profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, + ) -> RouterResult>> { + let db = &*state.store; + let key_manager_state = &state.into(); + + let storage_scheme = merchant_account.storage_scheme; + + let payment_intent = db + .find_payment_intent_by_id(key_manager_state, payment_id, key_store, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + self.validate_status_for_operation(payment_intent.status)?; + let client_secret = header_payload + .client_secret + .as_ref() + .get_required_value("client_secret header")?; + payment_intent.validate_client_secret(client_secret)?; + + let cell_id = state.conf.cell_information.id.clone(); + + let batch_encrypted_data = domain_types::crypto_operation( + key_manager_state, + common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt), + domain_types::CryptoOperation::BatchEncrypt( + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable( + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt { + payment_method_billing_address: None, + // .map(|address| address.clone().encode_to_value()).transpose() + // .change_context(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new), + }, + ), + ), + common_utils::types::keymanager::Identifier::Merchant(merchant_account.get_id().to_owned()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details".to_string())?; + + let encrypted_data = + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details")?; + + let payment_attempt_domain_model = + hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::proxy_create_domain_model( + &payment_intent, + cell_id, + storage_scheme, + request, + encrypted_data + ) + .await?; + + let payment_attempt = db + .insert_payment_attempt( + key_manager_state, + key_store, + payment_attempt_domain_model, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not insert payment attempt")?; + + let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new( + payment_intent + .shipping_address + .clone() + .map(|address| address.into_inner()), + payment_intent + .billing_address + .clone() + .map(|address| address.into_inner()), + payment_attempt + .payment_method_billing_address + .clone() + .map(|address| address.into_inner()), + Some(true), + ); + + let payment_data = PaymentConfirmData { + flow: std::marker::PhantomData, + payment_intent, + payment_attempt, + payment_method_data: None, + payment_address, + }; + + let get_trackers_response = operations::GetTrackerResponse { payment_data }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl Domain> + for PaymentProxyIntent { + async fn get_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut PaymentConfirmData, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult<(BoxedConfirmOperation<'a, F>, Option), errors::StorageError> + { + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut PaymentConfirmData, + _storage_scheme: storage_enums::MerchantStorageScheme, + _key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + ) -> RouterResult<( + BoxedConfirmOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn perform_routing<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + _business_profile: &domain::Profile, + state: &SessionState, + payment_data: &mut PaymentConfirmData, + _mechant_key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + use crate::core::payments::OperationSessionGetters; + + let connector_name = payment_data.get_payment_attempt_connector(); + let merchant_connector_id= payment_data.get_merchant_connector_id_in_attempt(); + + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + connector_name.get_required_value("connector_name")?, + api::GetToken::Connector, + merchant_connector_id, + )?; + + Ok(ConnectorCallType::PreDetermined(connector_data)) + } +} + + +#[async_trait] +impl UpdateTracker, ProxyPaymentsIntentRequest> + for PaymentProxyIntent +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + state: &'b SessionState, + _req_state: ReqState, + mut payment_data: PaymentConfirmData, + _customer: Option, + storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, + key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult<(BoxedConfirmOperation<'b, F>, PaymentConfirmData)> + where + F: 'b + Send, + { + let db = &*state.store; + let key_manager_state = &state.into(); + + let intent_status = common_enums::IntentStatus::Processing; + let attempt_status = common_enums::AttemptStatus::Pending; + + let connector = payment_data + .payment_attempt + .connector + .clone() + .get_required_value("connector") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Connector is none when constructing response")?; + + let merchant_connector_id = payment_data + .payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is none when constructing response")?; + + let payment_intent_update = + hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntent { + status: intent_status, + updated_by: storage_scheme.to_string(), + active_attempt_id: payment_data.payment_attempt.id.clone(), + }; + + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { + status: attempt_status, + updated_by: storage_scheme.to_string(), + connector, + merchant_connector_id, + }; + + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent.clone(), + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + + payment_data.payment_intent = updated_payment_intent; + + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt.clone(), + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + + payment_data.payment_attempt = updated_payment_attempt; + + Ok((Box::new(self), payment_data)) + } +} + + +#[cfg(feature = "v2")] +#[async_trait] +impl PostUpdateTracker, types::PaymentsAuthorizeData> + for PaymentProxyIntent +{ + async fn update_tracker<'b>( + &'b self, + state: &'b SessionState, + mut payment_data: PaymentConfirmData, + response: types::RouterData, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> RouterResult> + where + F: 'b + Send + Sync, + types::RouterData: + hyperswitch_domain_models::router_data::TrackerPostUpdateObjects< + F, + types::PaymentsAuthorizeData, + PaymentConfirmData, + >, + { + use hyperswitch_domain_models::router_data::TrackerPostUpdateObjects; + + let db = &*state.store; + let key_manager_state = &state.into(); + + let response_router_data = response; + + let payment_intent_update = + response_router_data.get_payment_intent_update(&payment_data, storage_scheme); + let payment_attempt_update = + response_router_data.get_payment_attempt_update(&payment_data, storage_scheme); + + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt, + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + + payment_data.payment_intent = updated_payment_intent; + payment_data.payment_attempt = updated_payment_attempt; + + Ok(payment_data) + } +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 80c10a294ec..bef09e4f354 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1459,6 +1459,94 @@ where } } +#[cfg(feature = "v2")] +impl GenerateResponse + for hyperswitch_domain_models::payments::PaymentConfirmData +where + F: Clone, +{ + fn generate_response( + self, + state: &SessionState, + connector_http_status_code: Option, + external_latency: Option, + is_latency_header_enabled: Option, + merchant_account: &domain::MerchantAccount, + ) -> RouterResponse { + let payment_intent = self.payment_intent; + let payment_attempt = self.payment_attempt; + + let amount = api_models::payments::PaymentAmountDetailsResponse::foreign_from(( + &payment_intent.amount_details, + &payment_attempt.amount_details, + )); + + let connector = payment_attempt + .connector + .clone() + .get_required_value("connector") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Connector is none when constructing response")?; + + let merchant_connector_id = payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is none when constructing response")?; + + let error = payment_attempt + .error + .clone() + .map(api_models::payments::ErrorDetails::foreign_from); + + let payment_address = self.payment_address; + + let payment_method_data = + Some(api_models::payments::PaymentMethodDataResponseWithBilling { + payment_method_data: None, + billing: payment_address + .get_request_payment_method_billing() + .cloned() + .map(From::from), + }); + + // TODO: Add support for other next actions, currently only supporting redirect to url + let redirect_to_url = payment_intent.create_start_redirection_url( + &state.base_url, + merchant_account.publishable_key.clone(), + )?; + + let next_action = payment_attempt + .redirection_data + .as_ref() + .map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url }); + + let response = api_models::payments::ProxyPaymentsIntentResponse { + id: payment_intent.id.clone(), + status: payment_intent.status, + amount, + connector, + client_secret: payment_intent.client_secret.clone(), + created: payment_intent.created_at, + payment_method_data, + payment_method_type: payment_attempt.payment_method_type, + payment_method_subtype: payment_attempt.payment_method_subtype, + next_action, + connector_transaction_id: payment_attempt.connector_payment_id.clone(), + connector_reference_id: None, + merchant_connector_id, + browser_info: None, + error, + }; + + Ok(services::ApplicationResponse::JsonWithHeaders(( + response, + vec![], + ))) + } +} + #[cfg(feature = "v2")] impl GenerateResponse for hyperswitch_domain_models::payments::PaymentStatusData diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index fffb3eab09e..462b03d1d6b 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -561,6 +561,10 @@ impl Payments { web::resource("/confirm-intent") .route(web::post().to(payments::payment_confirm_intent)), ) + .service( + web::resource("/proxy-confirm-intent") + .route(web::post().to(payments::proxy_confirm_intent)), + ) .service( web::resource("/get-intent") .route(web::get().to(payments::payments_get_intent)), diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 6550778619a..e6e6a37bd56 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -146,7 +146,8 @@ impl From for ApiIdentifier { | Flow::PaymentsGetIntent | Flow::PaymentsPostSessionTokens | Flow::PaymentsUpdateIntent - | Flow::PaymentStartRedirection => Self::Payments, + | Flow::PaymentStartRedirection + | Flow::ProxyConfirmIntent => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 16468aef74c..6d4978262f9 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -3,7 +3,6 @@ use crate::{ services::authorization::permissions::Permission, }; pub mod helpers; - use actix_web::{web, Responder}; use error_stack::report; use hyperswitch_domain_models::payments::HeaderPayload; @@ -2346,6 +2345,79 @@ pub async fn payment_confirm_intent( .await } + +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::ProxyConfirmIntent, payment_id))] +pub async fn proxy_confirm_intent( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + use hyperswitch_domain_models::payments::PaymentConfirmData; + + let flow = Flow::ProxyConfirmIntent; + + // Extract the payment ID from the path + let global_payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", global_payment_id.get_string_repr()); + + // Parse and validate headers + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + // Prepare the internal payload + let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId { + global_payment_id, + payload: json_payload.into_inner(), + }; + + // Determine the locking action, if required + let locking_action = internal_payload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow, + state, + &req, + internal_payload, + |state, auth: auth::AuthenticationData, req, req_state| { + let payment_id = req.global_payment_id; + let request = req.payload; + + // Define the operation for proxy payments intent + let operation = payments::operations::proxy_payments_intent::PaymentProxyIntent; + + // Call the core proxy logic + Box::pin(payments::proxy_for_payments_core::< + api_types::Authorize, + api_models::payments::ProxyPaymentsIntentResponse, + _, + _, + _, + PaymentConfirmData, + >( + state, + req_state, + auth.merchant_account, + auth.profile, + auth.key_store, + operation, + request, + payment_id, + payments::CallConnectorAction::Trigger, + header_payload.clone(), + )) + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + locking_action, + )) + .await +} + #[cfg(feature = "v2")] #[instrument(skip(state, req), fields(flow, payment_id))] pub async fn payment_status( diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 935efa3c78b..0ec0406b77b 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -527,6 +527,7 @@ pub enum Flow { SessionUpdateTaxCalculation, /// Payments confirm intent PaymentsConfirmIntent, + ProxyConfirmIntent, /// Payments post session tokens flow PaymentsPostSessionTokens, /// Payments start redirection flow From 684b5e7e2fbfc9734610fbf37022e73803e19736 Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 6 Feb 2025 21:31:44 +0530 Subject: [PATCH 10/26] change paymentAttemptNew and paymentConfirmData struct --- crates/api_models/src/payments.rs | 16 +++--- crates/diesel_models/src/payment_attempt.rs | 1 + crates/diesel_models/src/payment_intent.rs | 9 ++-- .../hyperswitch_domain_models/src/payments.rs | 7 +-- .../src/payments/payment_attempt.rs | 8 ++- .../src/router_request_types.rs | 54 ------------------- crates/router/src/core/payments.rs | 1 + .../operations/payment_confirm_intent.rs | 3 +- .../operations/proxy_payments_intent.rs | 45 +++++++++++----- .../router/src/core/payments/transformers.rs | 3 +- crates/router/src/routes/payments.rs | 2 +- 11 files changed, 61 insertions(+), 88 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 81c2cf63339..4a6a3f83151 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -33,7 +33,7 @@ use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, ephemeral_key::EphemeralKeyCreateResponse, - mandates::RecurringDetails, + mandates::{RecurringDetails,ProcessorPaymentToken}, refunds, }; @@ -4796,17 +4796,17 @@ pub struct ProxyPaymentsIntentRequest { pub amount: AmountDetails, - pub recurring_details:RecurringDetails, + pub recurring_details:ProcessorPaymentToken, - pub connector_customer_id:String, + // pub connector_customer_id:String, /// The payment metho:d type to be used for the payment. This should match with the `payment_method_data` provided - #[schema(value_type = PaymentMethod, example = "card")] - pub payment_method_type: api_enums::PaymentMethod, + // #[schema(value_type = PaymentMethod, example = "card")] + // pub payment_method_type: api_enums::PaymentMethod, - /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided - #[schema(value_type = PaymentMethodType, example = "apple_pay")] - pub payment_method_subtype: api_enums::PaymentMethodType, + // /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided + // #[schema(value_type = PaymentMethodType, example = "apple_pay")] + // pub payment_method_subtype: api_enums::PaymentMethodType, pub shipping: Option
, diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index a450a30fa76..e2ccc15108e 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -278,6 +278,7 @@ pub struct PaymentAttemptNew { pub payment_method_subtype: storage_enums::PaymentMethodType, pub id: id_type::GlobalAttemptId, pub connector_mandate_detail: Option, + pub connector: Option, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 4cb48f6c36e..48c63f9501f 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -215,11 +215,14 @@ impl TaxDetails { /// Get the tax amount /// If default tax is present, return the default tax amount /// If default tax is not present, return the tax amount based on the payment method if it matches the provided payment method type - pub fn get_tax_amount(&self, payment_method: PaymentMethodType) -> Option { + pub fn get_tax_amount(&self, payment_method: Option) -> Option { self.payment_method_type .as_ref() - .filter(|payment_method_type_tax| payment_method_type_tax.pmt == payment_method) - .map(|payment_method_type_tax| payment_method_type_tax.order_tax_amount) + .zip(payment_method) + .filter(|(payment_method_type_tax, payment_method)| { + payment_method_type_tax.pmt == *payment_method + }) + .map(|(payment_method_type_tax, _)| payment_method_type_tax.order_tax_amount) .or_else(|| self.get_default_tax_amount()) } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 32c25977595..6565dbd42bc 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -224,7 +224,7 @@ impl AmountDetails { let order_tax_amount = match self.skip_external_tax_calculation { common_enums::TaxCalculationOverride::Skip => { self.tax_details.as_ref().and_then(|tax_details| { - tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype) + tax_details.get_tax_amount(Some(confirm_intent_request.payment_method_subtype)) }) } common_enums::TaxCalculationOverride::Calculate => None, @@ -261,7 +261,7 @@ impl AmountDetails { let order_tax_amount = match self.skip_external_tax_calculation { common_enums::TaxCalculationOverride::Skip => { self.tax_details.as_ref().and_then(|tax_details| { - tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype) + tax_details.get_tax_amount(None) }) } common_enums::TaxCalculationOverride::Calculate => None, @@ -618,7 +618,7 @@ where // TODO: Check if this can be merged with existing payment data #[cfg(feature = "v2")] -#[derive(Clone)] +#[derive(Clone,Debug)] pub struct PaymentConfirmData where F: Clone, @@ -628,6 +628,7 @@ where pub payment_attempt: PaymentAttempt, pub payment_method_data: Option, pub payment_address: payment_address::PaymentAddress, + pub mandate_data: Option, } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index af1c2eef7a1..9df068d0a8c 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -19,6 +19,7 @@ use diesel_models::{ PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; +use diesel_models::types::RevenueRecoveryMetadata; use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::PeekInterface; @@ -548,6 +549,8 @@ impl PaymentAttempt { .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode billing address")?; + // let (payment_method_type, payment_method_subtype) = payment_intent.feature_metadata.and_then(|metatdata| metatdata.revenue_recovery_metadata.map(|metadata|( metadata.payment_method_type, metadata.payment_method_subtype))); + Ok(Self { payment_id: payment_intent.id.clone(), merchant_id: payment_intent.merchant_id.clone(), @@ -584,10 +587,10 @@ impl PaymentAttempt { customer_acceptance: None, profile_id: payment_intent.profile_id.clone(), organization_id: payment_intent.organization_id.clone(), - payment_method_type: request.payment_method_type, + payment_method_type: storage_enums::PaymentMethod::Card, payment_method_id: None, connector_payment_id: None, - payment_method_subtype: request.payment_method_subtype, + payment_method_subtype: storage_enums::PaymentMethodType::Credit, authentication_applied: None, external_reference_id: None, payment_method_billing_address, @@ -2127,6 +2130,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_type_v2: self.payment_method_type, id: self.id, connector_mandate_detail: self.connector_mandate_detail, + connector: self.connector, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 66de5dcf6c9..b612db10456 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -72,60 +72,6 @@ pub struct PaymentsAuthorizeData { pub shipping_cost: Option, pub additional_payment_method_data: Option, } -pub struct ProxyPaymentsAuthorizeData { - // pub payment_method_data: PaymentMethodData, - /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount) - /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately - /// ```text - /// get_original_amount() - /// get_surcharge_amount() - /// get_tax_on_surcharge_amount() - /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount - /// ``` - pub amount: i64, - pub order_tax_amount: Option, - // pub email: Option, - // pub customer_name: Option>, - pub currency: storage_enums::Currency, - pub confirm: bool, - pub statement_descriptor_suffix: Option, - pub statement_descriptor: Option, - pub capture_method: Option, - pub router_return_url: Option, - pub webhook_url: Option, - pub complete_authorize_url: Option, - // Mandates - pub setup_future_usage: Option, - pub mandate_id: Option, - pub off_session: Option, - // pub customer_acceptance: Option, - pub setup_mandate_details: Option, - pub browser_info: Option, - pub order_details: Option>, - pub order_category: Option, - pub session_token: Option, - pub enrolled_for_3ds: bool, - pub related_transaction_id: Option, - pub payment_experience: Option, - // pub payment_method_type: Option, - pub surcharge_details: Option, - // pub customer_id: Option, - pub request_incremental_authorization: bool, - pub metadata: Option, - pub authentication_data: Option, - pub split_payments: Option, - - // New amount for amount frame work - pub minor_amount: MinorUnit, - - /// Merchant's identifier for the payment/invoice. This will be sent to the connector - /// if the connector provides support to accept multiple reference ids. - /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. - pub merchant_order_reference_id: Option, - pub integrity_object: Option, - pub shipping_cost: Option, - // pub additional_payment_method_data: Option, -} #[derive(Debug, Clone)] pub struct PaymentsPostSessionTokensData { // amount here would include amount, surcharge_amount and shipping_cost diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c3509c9407c..14940dfc56b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -7814,6 +7814,7 @@ impl OperationSessionGetters for PaymentConfirmData { } fn get_payment_attempt_connector(&self) -> Option<&str> { + self.payment_attempt.connector.as_deref() } diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 71d99d03e41..b56a9eae64c 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -251,6 +251,7 @@ impl GetTracker, PaymentsConfir payment_attempt, payment_method_data, payment_address, + mandate_data:None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; @@ -403,7 +404,7 @@ impl UpdateTracker, PaymentsConfirmInt connector, merchant_connector_id, }; - + let updated_payment_intent = db .update_payment_intent( key_manager_state, diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index ef760ad5397..cb93ab62237 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -13,9 +13,10 @@ use crate::{ }, utils::OptionExt, }; -use api_models::payments::ProxyPaymentsIntentRequest; +use api_models::payments::{ProxyPaymentsIntentRequest,MandateIds,MandateReferenceId,ConnectorMandateReferenceId}; +use hyperswitch_domain_models::payment_method_data::PaymentMethodData; use api_models::enums::FrmSuggestion; - +// use diesel_models::payment_attempt::ConnectorMandateReferenceId; use async_trait::async_trait; use common_utils::types::keymanager::ToEncryptable; use error_stack::ResultExt; @@ -170,9 +171,6 @@ impl GetTracker, ProxyPaymentsI hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable( hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt { payment_method_billing_address: None, - // .map(|address| address.clone().encode_to_value()).transpose() - // .change_context(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new), }, ), ), @@ -189,7 +187,7 @@ impl GetTracker, ProxyPaymentsI .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while encrypting payment intent details")?; - let payment_attempt_domain_model = + let payment_attempt_domain_model: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt = hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::proxy_create_domain_model( &payment_intent, cell_id, @@ -198,7 +196,7 @@ impl GetTracker, ProxyPaymentsI encrypted_data ) .await?; - + let payment_attempt = db .insert_payment_attempt( key_manager_state, @@ -209,6 +207,7 @@ impl GetTracker, ProxyPaymentsI .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Could not insert payment attempt")?; + let processor_payment_token = request.recurring_details.processor_payment_token.clone(); let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new( payment_intent @@ -225,13 +224,25 @@ impl GetTracker, ProxyPaymentsI .map(|address| address.into_inner()), Some(true), ); - + let mandate_data_input= MandateIds { + mandate_id: None, + mandate_reference_id: Some(MandateReferenceId::ConnectorMandateId( + ConnectorMandateReferenceId::new( + Some(processor_payment_token), + None, + None, + None, + None, + ) + )) + }; let payment_data = PaymentConfirmData { flow: std::marker::PhantomData, payment_intent, payment_attempt, - payment_method_data: None, + payment_method_data: Some(PaymentMethodData::MandatePayment), payment_address, + mandate_data: Some(mandate_data_input), }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; @@ -265,7 +276,7 @@ impl Domain RouterResult<( BoxedConfirmOperation<'a, F>, - Option, + Option, Option, )> { Ok((Box::new(self), None, None)) @@ -282,20 +293,26 @@ impl Domain UpdateTracker, ProxyPaymentsIntentRequest> for PaymentProxyIntent diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index bef09e4f354..99df2ebdacc 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -251,14 +251,13 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .browser_info .clone() .map(types::BrowserInformation::from); - // TODO: few fields are repeated in both routerdata and request let request = types::PaymentsAuthorizeData { payment_method_data: payment_data .payment_method_data .get_required_value("payment_method_data")?, setup_future_usage: Some(payment_data.payment_intent.setup_future_usage), - mandate_id: None, + mandate_id: payment_data.mandate_data.clone(), off_session: None, setup_mandate_details: None, confirm: true, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 6d4978262f9..0ed1b98e132 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -2412,7 +2412,7 @@ pub async fn proxy_confirm_intent( header_payload.clone(), )) }, - &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::PublishableKeyAuth, locking_action, )) .await From f3e12bc4bb8f303c770f07b722d4f680b3e18a0b Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 7 Feb 2025 12:28:32 +0530 Subject: [PATCH 11/26] remove all the warnings --- crates/api_models/src/payments.rs | 12 +----------- .../src/payments/payment_attempt.rs | 1 + 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 4a6a3f83151..7e1c1ff2aff 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -33,7 +33,7 @@ use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, ephemeral_key::EphemeralKeyCreateResponse, - mandates::{RecurringDetails,ProcessorPaymentToken}, + mandates::{RecurringDetails}, refunds, }; @@ -4798,16 +4798,6 @@ pub struct ProxyPaymentsIntentRequest { pub recurring_details:ProcessorPaymentToken, - // pub connector_customer_id:String, - - /// The payment metho:d type to be used for the payment. This should match with the `payment_method_data` provided - // #[schema(value_type = PaymentMethod, example = "card")] - // pub payment_method_type: api_enums::PaymentMethod, - - // /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided - // #[schema(value_type = PaymentMethodType, example = "apple_pay")] - // pub payment_method_subtype: api_enums::PaymentMethodType, - pub shipping: Option
, /// Additional details required by 3DS 2.0 diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 9df068d0a8c..b855a070aef 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -19,6 +19,7 @@ use diesel_models::{ PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; +#[cfg(feature = "v2")] use diesel_models::types::RevenueRecoveryMetadata; use error_stack::ResultExt; #[cfg(feature = "v2")] From 1ae403f2650abea7d047974b03ddec4babc20a19 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:23:41 +0000 Subject: [PATCH 12/26] chore: run formatter --- crates/api_models/src/events/payment.rs | 2 +- crates/api_models/src/payments.rs | 7 +- .../hyperswitch_domain_models/src/payments.rs | 11 +- .../src/payments/payment_attempt.rs | 101 ++++++------- .../src/router_data.rs | 1 - crates/router/src/core/payments.rs | 24 ++-- .../operations/payment_confirm_intent.rs | 4 +- .../payments/operations/payment_response.rs | 4 +- .../operations/proxy_payments_intent.rs | 133 +++++++++--------- crates/router/src/routes/payments.rs | 1 - 10 files changed, 142 insertions(+), 146 deletions(-) diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index e54f22c5041..58e644adc08 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -3,7 +3,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; #[cfg(feature = "v2")] use super::{ PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, - PaymentsGetIntentRequest, PaymentsIntentResponse,ProxyPaymentsIntentResponse, PaymentsRequest, + PaymentsGetIntentRequest, PaymentsIntentResponse, PaymentsRequest, ProxyPaymentsIntentResponse, }; #[cfg(all( any(feature = "v2", feature = "v1"), diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 65961097484..675ee0109ff 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -33,7 +33,7 @@ use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, ephemeral_key::EphemeralKeyCreateResponse, - mandates::{RecurringDetails,ProcessorPaymentToken}, + mandates::{ProcessorPaymentToken, RecurringDetails}, refunds, }; @@ -4790,7 +4790,6 @@ pub struct PaymentsConfirmIntentRequest { /// Additional details required by 3DS 2.0 #[schema(value_type = Option)] pub browser_info: Option, - } #[cfg(feature = "v2")] @@ -4804,7 +4803,7 @@ pub struct ProxyPaymentsIntentRequest { pub amount: AmountDetails, - pub recurring_details:ProcessorPaymentToken, + pub recurring_details: ProcessorPaymentToken, pub shipping: Option
, @@ -4814,7 +4813,7 @@ pub struct ProxyPaymentsIntentRequest { #[schema(example = "stripe")] pub connector: String, - + #[schema(value_type = String)] pub merchant_connector_id: id_type::MerchantConnectorAccountId, } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 6565dbd42bc..3a69e0582a0 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -259,11 +259,10 @@ impl AmountDetails { }; let order_tax_amount = match self.skip_external_tax_calculation { - common_enums::TaxCalculationOverride::Skip => { - self.tax_details.as_ref().and_then(|tax_details| { - tax_details.get_tax_amount(None) - }) - } + common_enums::TaxCalculationOverride::Skip => self + .tax_details + .as_ref() + .and_then(|tax_details| tax_details.get_tax_amount(None)), common_enums::TaxCalculationOverride::Calculate => None, }; @@ -618,7 +617,7 @@ where // TODO: Check if this can be merged with existing payment data #[cfg(feature = "v2")] -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct PaymentConfirmData where F: Clone, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 6fbf34575ef..ee322e2afca 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -14,13 +14,13 @@ use common_utils::{ ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, }, }; +#[cfg(feature = "v2")] +use diesel_models::types::RevenueRecoveryMetadata; use diesel_models::{ ConnectorMandateReferenceId, PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; -#[cfg(feature = "v2")] -use diesel_models::types::RevenueRecoveryMetadata; use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::PeekInterface; @@ -542,7 +542,8 @@ impl PaymentAttempt { let id = id_type::GlobalAttemptId::generate(&cell_id); let intent_amount_details = payment_intent.amount_details.clone(); - let attempt_amount_details = intent_amount_details.proxy_create_attempt_amount_details(request); + let attempt_amount_details = + intent_amount_details.proxy_create_attempt_amount_details(request); let now = common_utils::date_time::now(); let payment_method_billing_address = encrypted_data @@ -558,58 +559,58 @@ impl PaymentAttempt { // let (payment_method_type, payment_method_subtype) = payment_intent.feature_metadata.and_then(|metatdata| metatdata.revenue_recovery_metadata.map(|metadata|( metadata.payment_method_type, metadata.payment_method_subtype))); - Ok(Self { - payment_id: payment_intent.id.clone(), - merchant_id: payment_intent.merchant_id.clone(), - amount_details: attempt_amount_details, - status: common_enums::AttemptStatus::Started, - // This will be decided by the routing algorithm and updated in update trackers - // right before calling the connector - connector: Some(request.connector.clone()), - authentication_type: payment_intent.authentication_type, - created_at: now, - modified_at: now, - last_synced: None, - cancellation_reason: None, - browser_info: request.browser_info.clone(), - payment_token: None, - connector_metadata: None, - payment_experience: None, - payment_method_data: None, - routing_result: None, - preprocessing_step_id: None, - multiple_capture_count: None, - connector_response_reference_id: None, - updated_by: storage_scheme.to_string(), - redirection_data: None, - encoded_data: None, - merchant_connector_id: Some(request.merchant_connector_id.clone()), - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - profile_id: payment_intent.profile_id.clone(), - organization_id: payment_intent.organization_id.clone(), - payment_method_type: storage_enums::PaymentMethod::Card, - payment_method_id: None, - connector_payment_id: None, - payment_method_subtype: storage_enums::PaymentMethodType::Credit, - authentication_applied: None, - external_reference_id: None, - payment_method_billing_address, - error: None, - connector_token_details: Some(diesel_models::ConnectorTokenDetails { + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: Some(request.connector.clone()), + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + redirection_data: None, + encoded_data: None, + merchant_connector_id: Some(request.merchant_connector_id.clone()), + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: storage_enums::PaymentMethod::Card, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: storage_enums::PaymentMethodType::Credit, + authentication_applied: None, + external_reference_id: None, + payment_method_billing_address, + error: None, + connector_token_details: Some(diesel_models::ConnectorTokenDetails { connector_mandate_id: None, - connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( + connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), }), id, - card_discovery: None, + card_discovery: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 034a9ee84be..1ad7d2dcbdd 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -671,7 +671,6 @@ impl } } - #[cfg(feature = "v2")] impl TrackerPostUpdateObjects< diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8991a36181e..58a4f165442 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4,7 +4,7 @@ pub mod connector_integration_v2_impls; pub mod customers; pub mod flows; pub mod helpers; -pub mod operations; +pub mod operations; #[cfg(feature = "retry")] pub mod retry; @@ -73,7 +73,7 @@ use time; pub use self::operations::{ PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, - PaymentSessionUpdate, PaymentStatus, PaymentUpdate + PaymentSessionUpdate, PaymentStatus, PaymentUpdate, }; use self::{ conditional_configs::perform_decision_management, @@ -1066,7 +1066,7 @@ where // Get the trackers related to track the state of the payment let operations::GetTrackerResponse { mut payment_data } = get_tracker_response; - // consume the req merchant_connector_id and set it in the payment_data + // consume the req merchant_connector_id and set it in the payment_data let connector = operation .to_domain()? .perform_routing( @@ -1112,7 +1112,7 @@ where ConnectorCallType::Skip => payment_data, }; - Ok((payment_data,req,None,None)) + Ok((payment_data, req, None, None)) } #[cfg(feature = "v2")] @@ -7756,7 +7756,7 @@ impl OperationSessionGetters for PaymentData { fn get_address(&self) -> &PaymentAddress { &self.address } - fn get_merchant_connector_id_in_attempt(&self) -> Option { + fn get_merchant_connector_id_in_attempt(&self) -> Option { self.payment_attempt.merchant_connector_id.clone() } fn get_creds_identifier(&self) -> Option<&str> { @@ -8076,10 +8076,10 @@ impl OperationSessionGetters for PaymentIntentData { } fn get_payment_attempt_connector(&self) -> Option<&str> { - todo!() + todo!() } - fn get_merchant_connector_id_in_attempt(&self) -> Option{ + fn get_merchant_connector_id_in_attempt(&self) -> Option { todo!() } @@ -8292,11 +8292,10 @@ impl OperationSessionGetters for PaymentConfirmData { } fn get_payment_attempt_connector(&self) -> Option<&str> { - self.payment_attempt.connector.as_deref() } - - fn get_merchant_connector_id_in_attempt(&self) -> Option { + + fn get_merchant_connector_id_in_attempt(&self) -> Option { self.payment_attempt.merchant_connector_id.clone() } @@ -8419,7 +8418,6 @@ impl OperationSessionSetters for PaymentConfirmData { } } - #[cfg(feature = "v2")] impl OperationSessionGetters for PaymentStatusData { #[track_caller] @@ -8515,7 +8513,7 @@ impl OperationSessionGetters for PaymentStatusData { todo!() } - fn get_merchant_connector_id_in_attempt(&self) -> Option{ + fn get_merchant_connector_id_in_attempt(&self) -> Option { todo!() } @@ -8733,7 +8731,7 @@ impl OperationSessionGetters for PaymentCaptureData { todo!() } - fn get_merchant_connector_id_in_attempt(&self) -> Option{ + fn get_merchant_connector_id_in_attempt(&self) -> Option { todo!() } diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index b56a9eae64c..7eae0baca6a 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -251,7 +251,7 @@ impl GetTracker, PaymentsConfir payment_attempt, payment_method_data, payment_address, - mandate_data:None, + mandate_data: None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; @@ -404,7 +404,7 @@ impl UpdateTracker, PaymentsConfirmInt connector, merchant_connector_id, }; - + let updated_payment_intent = db .update_payment_intent( key_manager_state, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 60fb319d4e6..516cebe24d7 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -16,7 +16,7 @@ use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; #[cfg(feature = "v2")] use hyperswitch_domain_models::payments::{ - PaymentConfirmData, PaymentIntentData, PaymentStatusData + PaymentConfirmData, PaymentIntentData, PaymentStatusData, }; use router_derive; use router_env::{instrument, logger, tracing}; @@ -2213,8 +2213,6 @@ impl Operation for PaymentResp } } - - #[cfg(feature = "v2")] impl Operation for PaymentResponse { type Data = hyperswitch_domain_models::payments::PaymentCaptureData; diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index cb93ab62237..45c52698abb 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -1,4 +1,21 @@ -use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest,PostUpdateTracker}; +use api_models::{ + enums::FrmSuggestion, + payments::{ + ConnectorMandateReferenceId, MandateIds, MandateReferenceId, ProxyPaymentsIntentRequest, + }, +}; +// use diesel_models::payment_attempt::ConnectorMandateReferenceId; +use async_trait::async_trait; +use common_enums::enums; +use common_utils::types::keymanager::ToEncryptable; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, payments::PaymentConfirmData, +}; +use masking::PeekInterface; +use router_env::{instrument, tracing}; + +use super::{Domain, GetTracker, Operation, PostUpdateTracker, UpdateTracker, ValidateRequest}; use crate::{ core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, @@ -13,17 +30,6 @@ use crate::{ }, utils::OptionExt, }; -use api_models::payments::{ProxyPaymentsIntentRequest,MandateIds,MandateReferenceId,ConnectorMandateReferenceId}; -use hyperswitch_domain_models::payment_method_data::PaymentMethodData; -use api_models::enums::FrmSuggestion; -// use diesel_models::payment_attempt::ConnectorMandateReferenceId; -use async_trait::async_trait; -use common_utils::types::keymanager::ToEncryptable; -use error_stack::ResultExt; -use hyperswitch_domain_models::payments::PaymentConfirmData; -use masking::PeekInterface; -use router_env::{instrument, tracing}; -use common_enums::enums; #[derive(Debug, Clone, Copy)] pub struct PaymentProxyIntent; @@ -196,7 +202,7 @@ impl GetTracker, ProxyPaymentsI encrypted_data ) .await?; - + let payment_attempt = db .insert_payment_attempt( key_manager_state, @@ -224,7 +230,7 @@ impl GetTracker, ProxyPaymentsI .map(|address| address.into_inner()), Some(true), ); - let mandate_data_input= MandateIds { + let mandate_data_input = MandateIds { mandate_id: None, mandate_reference_id: Some(MandateReferenceId::ConnectorMandateId( ConnectorMandateReferenceId::new( @@ -233,8 +239,8 @@ impl GetTracker, ProxyPaymentsI None, None, None, - ) - )) + ), + )), }; let payment_data = PaymentConfirmData { flow: std::marker::PhantomData, @@ -252,35 +258,36 @@ impl GetTracker, ProxyPaymentsI } #[async_trait] -impl Domain> - for PaymentProxyIntent { - async fn get_customer_details<'a>( - &'a self, - _state: &SessionState, - _payment_data: &mut PaymentConfirmData, - _merchant_key_store: &domain::MerchantKeyStore, - _storage_scheme: storage_enums::MerchantStorageScheme, - ) -> CustomResult<(BoxedConfirmOperation<'a, F>, Option), errors::StorageError> - { - Ok((Box::new(self), None)) - } - - #[instrument(skip_all)] - async fn make_pm_data<'a>( - &'a self, - _state: &'a SessionState, - _payment_data: &mut PaymentConfirmData, - _storage_scheme: storage_enums::MerchantStorageScheme, - _key_store: &domain::MerchantKeyStore, - _customer: &Option, - _business_profile: &domain::Profile, - ) -> RouterResult<( - BoxedConfirmOperation<'a, F>, - Option, - Option, - )> { - Ok((Box::new(self), None, None)) - } +impl Domain> + for PaymentProxyIntent +{ + async fn get_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut PaymentConfirmData, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult<(BoxedConfirmOperation<'a, F>, Option), errors::StorageError> + { + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut PaymentConfirmData, + _storage_scheme: storage_enums::MerchantStorageScheme, + _key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + ) -> RouterResult<( + BoxedConfirmOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } async fn perform_routing<'a>( &'a self, @@ -292,27 +299,24 @@ impl Domain CustomResult { use crate::core::payments::OperationSessionGetters; - let connector_name = payment_data.get_payment_attempt_connector(); - if let Some(connector_name) = connector_name - - { - let merchant_connector_id= payment_data.get_merchant_connector_id_in_attempt(); - let connector_data = api::ConnectorData::get_connector_by_name( - &state.conf.connectors, - connector_name, - api::GetToken::Connector, - merchant_connector_id, - )?; - - Ok(ConnectorCallType::PreDetermined(connector_data)) - } - else { - Err(error_stack::Report::new(errors::ApiErrorResponse::InternalServerError)) - + let connector_name = payment_data.get_payment_attempt_connector(); + if let Some(connector_name) = connector_name { + let merchant_connector_id = payment_data.get_merchant_connector_id_in_attempt(); + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + connector_name, + api::GetToken::Connector, + merchant_connector_id, + )?; + + Ok(ConnectorCallType::PreDetermined(connector_data)) + } else { + Err(error_stack::Report::new( + errors::ApiErrorResponse::InternalServerError, + )) + } } } - -} #[async_trait] impl UpdateTracker, ProxyPaymentsIntentRequest> for PaymentProxyIntent @@ -401,7 +405,6 @@ impl UpdateTracker, ProxyPaymentsInten } } - #[cfg(feature = "v2")] #[async_trait] impl PostUpdateTracker, types::PaymentsAuthorizeData> diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 0f73d5456f6..b28a810b2c5 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -2393,7 +2393,6 @@ pub async fn payment_confirm_intent( .await } - #[cfg(feature = "v2")] #[instrument(skip_all, fields(flow = ?Flow::ProxyConfirmIntent, payment_id))] pub async fn proxy_confirm_intent( From 81c3cb54669ce93804d481a4bc9c39d89160c134 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 12 Feb 2025 14:06:39 +0530 Subject: [PATCH 13/26] update PaymentAttempt structure to use connector_token_details and card_discovery --- .../src/payments/payment_attempt.rs | 105 +++++++++--------- .../router/src/core/payments/transformers.rs | 10 +- 2 files changed, 62 insertions(+), 53 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ee322e2afca..b24016ac30f 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -526,8 +526,9 @@ impl PaymentAttempt { external_reference_id: None, payment_method_billing_address, error: None, - connector_mandate_detail: None, + connector_token_details: None, id, + card_discovery:None, }) } @@ -556,59 +557,57 @@ impl PaymentAttempt { .transpose() .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode billing address")?; - - // let (payment_method_type, payment_method_subtype) = payment_intent.feature_metadata.and_then(|metatdata| metatdata.revenue_recovery_metadata.map(|metadata|( metadata.payment_method_type, metadata.payment_method_subtype))); - - Ok(Self { - payment_id: payment_intent.id.clone(), - merchant_id: payment_intent.merchant_id.clone(), - amount_details: attempt_amount_details, - status: common_enums::AttemptStatus::Started, - // This will be decided by the routing algorithm and updated in update trackers - // right before calling the connector - connector: Some(request.connector.clone()), - authentication_type: payment_intent.authentication_type, - created_at: now, - modified_at: now, - last_synced: None, - cancellation_reason: None, - browser_info: request.browser_info.clone(), - payment_token: None, - connector_metadata: None, - payment_experience: None, - payment_method_data: None, - routing_result: None, - preprocessing_step_id: None, - multiple_capture_count: None, - connector_response_reference_id: None, - updated_by: storage_scheme.to_string(), - redirection_data: None, - encoded_data: None, - merchant_connector_id: Some(request.merchant_connector_id.clone()), - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - profile_id: payment_intent.profile_id.clone(), - organization_id: payment_intent.organization_id.clone(), - payment_method_type: storage_enums::PaymentMethod::Card, - payment_method_id: None, - connector_payment_id: None, - payment_method_subtype: storage_enums::PaymentMethodType::Credit, - authentication_applied: None, - external_reference_id: None, - payment_method_billing_address, - error: None, - connector_token_details: Some(diesel_models::ConnectorTokenDetails { + let connector_token=Some(diesel_models::ConnectorTokenDetails { connector_mandate_id: None, - connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( + connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), - }), + }); + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: Some(request.connector.clone()), + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + redirection_data: None, + encoded_data: None, + merchant_connector_id: Some(request.merchant_connector_id.clone()), + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: storage_enums::PaymentMethod::Card, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: storage_enums::PaymentMethodType::Credit, + authentication_applied: None, + external_reference_id: None, + payment_method_billing_address, + error: None, + connector_token_details: connector_token, id, card_discovery: None, }) @@ -2159,7 +2158,9 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_subtype: self.payment_method_subtype, payment_method_type_v2: self.payment_method_type, id: self.id, - connector_mandate_detail: self.connector_mandate_detail, + connector_token_details: self.connector_token_details, + card_discovery: self.card_discovery, + connector: self.connector, }) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 3a9ab07c287..f9f8fed12e1 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -17,6 +17,7 @@ use diesel_models::{ ephemeral_key, payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, }; +use diesel_models::types::BillingConnectorMitTokenDetails; use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] use hyperswitch_domain_models::ApiModelToDieselModelConvertor; @@ -308,6 +309,12 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .as_ref() .and_then(|detail| detail.get_connector_mandate_request_reference_id()); + let connector_customer_id = payment_data + .payment_intent + .feature_metadata + .as_ref() + .and_then(|fm| fm.revenue_recovery_metadata.as_ref()) + .map(|rrm| rrm.billing_connector_mit_token_details.connector_customer_id.clone()); // TODO: evaluate the fields in router data, if they are required or not let router_data = types::RouterData { flow: PhantomData, @@ -351,7 +358,8 @@ pub async fn construct_payment_router_data_for_authorize<'a>( reference_id: None, payment_method_status: None, payment_method_token: None, - connector_customer: None, + // fill this with the connector customer id + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id From 1345b0b2ac23f6820a47d76657e46ad6b0b55c6d Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 09:34:21 +0000 Subject: [PATCH 14/26] chore: run formatter --- .../src/payments/payment_attempt.rs | 104 +++++++++--------- .../router/src/core/payments/transformers.rs | 10 +- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 9ea878ab276..eefb9050d85 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -538,7 +538,7 @@ impl PaymentAttempt { error: None, connector_token_details: None, id, - card_discovery:None, + card_discovery: None, }) } @@ -567,57 +567,57 @@ impl PaymentAttempt { .transpose() .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode billing address")?; - let connector_token=Some(diesel_models::ConnectorTokenDetails { - connector_mandate_id: None, - connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( - consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, - )), - }); - Ok(Self { - payment_id: payment_intent.id.clone(), - merchant_id: payment_intent.merchant_id.clone(), - amount_details: attempt_amount_details, - status: common_enums::AttemptStatus::Started, - // This will be decided by the routing algorithm and updated in update trackers - // right before calling the connector - connector: Some(request.connector.clone()), - authentication_type: payment_intent.authentication_type, - created_at: now, - modified_at: now, - last_synced: None, - cancellation_reason: None, - browser_info: request.browser_info.clone(), - payment_token: None, - connector_metadata: None, - payment_experience: None, - payment_method_data: None, - routing_result: None, - preprocessing_step_id: None, - multiple_capture_count: None, - connector_response_reference_id: None, - updated_by: storage_scheme.to_string(), - redirection_data: None, - encoded_data: None, - merchant_connector_id: Some(request.merchant_connector_id.clone()), - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - profile_id: payment_intent.profile_id.clone(), - organization_id: payment_intent.organization_id.clone(), - payment_method_type: storage_enums::PaymentMethod::Card, - payment_method_id: None, - connector_payment_id: None, - payment_method_subtype: storage_enums::PaymentMethodType::Credit, - authentication_applied: None, - external_reference_id: None, - payment_method_billing_address, - error: None, - connector_token_details: connector_token, + let connector_token = Some(diesel_models::ConnectorTokenDetails { + connector_mandate_id: None, + connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( + consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, + )), + }); + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: Some(request.connector.clone()), + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + redirection_data: None, + encoded_data: None, + merchant_connector_id: Some(request.merchant_connector_id.clone()), + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: storage_enums::PaymentMethod::Card, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: storage_enums::PaymentMethodType::Credit, + authentication_applied: None, + external_reference_id: None, + payment_method_billing_address, + error: None, + connector_token_details: connector_token, id, card_discovery: None, }) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index eee89aff29f..1f2c8f879dd 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -16,8 +16,8 @@ use common_utils::{ use diesel_models::{ ephemeral_key, payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, + types::BillingConnectorMitTokenDetails, }; -use diesel_models::types::BillingConnectorMitTokenDetails; use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] use hyperswitch_domain_models::ApiModelToDieselModelConvertor; @@ -314,7 +314,11 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .feature_metadata .as_ref() .and_then(|fm| fm.revenue_recovery_metadata.as_ref()) - .map(|rrm| rrm.billing_connector_mit_token_details.connector_customer_id.clone()); + .map(|rrm| { + rrm.billing_connector_mit_token_details + .connector_customer_id + .clone() + }); // TODO: evaluate the fields in router data, if they are required or not let router_data = types::RouterData { flow: PhantomData, @@ -359,7 +363,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( payment_method_status: None, payment_method_token: None, // fill this with the connector customer id - connector_customer: connector_customer_id, + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id From fe61e7d4d3196b622a6ec6dd92c9e5cf6511882f Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 13 Feb 2025 23:23:07 +0530 Subject: [PATCH 15/26] add feature_metadata to PaymentIntentUpdate and update handling in proxy payments --- crates/api_models/src/payments.rs | 7 ++-- .../src/payments/payment_intent.rs | 4 +- .../src/router_data.rs | 42 +++++++++++++++++++ .../operations/proxy_payments_intent.rs | 6 ++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index f43712ee3ef..690e4584b24 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -34,10 +34,11 @@ use crate::payment_methods; use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, - ephemeral_key::EphemeralKeyCreateResponse, - mandates::{ProcessorPaymentToken, RecurringDetails}, + mandates:: RecurringDetails, refunds, }; +#[cfg(feature = "v2")] +use crate::mandates::ProcessorPaymentToken; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PaymentOp { @@ -4913,7 +4914,7 @@ pub struct ProxyPaymentsIntentRequest { #[schema(example = "stripe")] pub connector: String, - + #[schema(value_type = String)] pub merchant_connector_id: id_type::MerchantConnectorAccountId, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 2406e5f4b1b..9b8e6ff1244 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -302,6 +302,7 @@ pub enum PaymentIntentUpdate { status: common_enums::IntentStatus, amount_captured: Option, updated_by: String, + feature_metadata: Option, }, /// SyncUpdate of ConfirmIntent in PostUpdateTrackers SyncUpdate { @@ -412,6 +413,7 @@ impl From for diesel_models::PaymentIntentUpdateInternal { status, updated_by, amount_captured, + feature_metadata, } => Self { status: Some(status), active_attempt_id: None, @@ -440,7 +442,7 @@ impl From for diesel_models::PaymentIntentUpdateInternal { allowed_payment_method_types: None, metadata: None, connector_metadata: None, - feature_metadata: None, + feature_metadata, payment_link_config: None, request_incremental_authorization: None, session_expiry: None, diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 0c90b0d0244..c3189d8be02 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -471,6 +471,32 @@ impl storage_scheme: common_enums::MerchantStorageScheme, ) -> PaymentIntentUpdate { let amount_captured = self.get_captured_amount(payment_data); + let status=payment_data.payment_attempt.status.is_terminal_status(); + let mut updated_feature_metadata = payment_data + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = true; + } + updated_metadata + }); + if status { + updated_feature_metadata = payment_data + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = false; + } + updated_metadata + }); + } + match self.response { Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: common_enums::IntentStatus::from( @@ -478,6 +504,8 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), + feature_metadata: updated_feature_metadata + }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -486,6 +514,7 @@ impl .unwrap_or(common_enums::IntentStatus::Failed), amount_captured, updated_by: storage_scheme.to_string(), + feature_metadata: updated_feature_metadata, }, } } @@ -1124,6 +1153,17 @@ impl storage_scheme: common_enums::MerchantStorageScheme, ) -> PaymentIntentUpdate { let amount_captured = self.get_captured_amount(payment_data); + let updated_feature_metadata = payment_data + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = true; + } + updated_metadata + }); match self.response { Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: common_enums::IntentStatus::from( @@ -1131,6 +1171,7 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), + feature_metadata: updated_feature_metadata }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -1139,6 +1180,7 @@ impl .unwrap_or(common_enums::IntentStatus::Failed), amount_captured, updated_by: storage_scheme.to_string(), + feature_metadata: updated_feature_metadata }, } } diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 45c52698abb..8bb23632356 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -41,9 +41,10 @@ impl ValidateStatusForOperation for PaymentProxyIntent { intent_status: common_enums::IntentStatus, ) -> Result<(), errors::ApiErrorResponse> { match intent_status { - common_enums::IntentStatus::RequiresPaymentMethod => Ok(()), + //Failed state is included here so that in PCR, retries can be done for failed payments, otherwise for a failed attempt it was asking for new payment_intent + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::Failed => Ok(()), common_enums::IntentStatus::Succeeded - | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Processing | common_enums::IntentStatus::RequiresCustomerAction @@ -162,6 +163,7 @@ impl GetTracker, ProxyPaymentsI .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; self.validate_status_for_operation(payment_intent.status)?; + let client_secret = header_payload .client_secret .as_ref() From 40b7b36470f4fe87b9162c63cab2808e81d68e7c Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:54:19 +0000 Subject: [PATCH 16/26] chore: run formatter --- crates/api_models/src/payments.rs | 8 +- .../src/router_data.rs | 75 ++++++++++--------- .../operations/proxy_payments_intent.rs | 2 +- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 690e4584b24..f6463bc9879 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -30,15 +30,15 @@ use utoipa::ToSchema; #[cfg(feature = "v1")] use crate::ephemeral_key::EphemeralKeyCreateResponse; #[cfg(feature = "v2")] +use crate::mandates::ProcessorPaymentToken; +#[cfg(feature = "v2")] use crate::payment_methods; use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, - mandates:: RecurringDetails, + mandates::RecurringDetails, refunds, }; -#[cfg(feature = "v2")] -use crate::mandates::ProcessorPaymentToken; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PaymentOp { @@ -4914,7 +4914,7 @@ pub struct ProxyPaymentsIntentRequest { #[schema(example = "stripe")] pub connector: String, - + #[schema(value_type = String)] pub merchant_connector_id: id_type::MerchantConnectorAccountId, } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index c3189d8be02..1ebfd1c26b2 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -471,30 +471,31 @@ impl storage_scheme: common_enums::MerchantStorageScheme, ) -> PaymentIntentUpdate { let amount_captured = self.get_captured_amount(payment_data); - let status=payment_data.payment_attempt.status.is_terminal_status(); + let status = payment_data.payment_attempt.status.is_terminal_status(); let mut updated_feature_metadata = payment_data - .payment_intent - .feature_metadata - .as_ref() - .map(|fm| { - let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = true; - } - updated_metadata - }); + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = true; + } + updated_metadata + }); if status { - updated_feature_metadata = payment_data - .payment_intent - .feature_metadata - .as_ref() - .map(|fm| { - let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = false; - } - updated_metadata - }); + updated_feature_metadata = + payment_data + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = false; + } + updated_metadata + }); } match self.response { @@ -504,8 +505,7 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata - + feature_metadata: updated_feature_metadata, }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -1153,17 +1153,18 @@ impl storage_scheme: common_enums::MerchantStorageScheme, ) -> PaymentIntentUpdate { let amount_captured = self.get_captured_amount(payment_data); - let updated_feature_metadata = payment_data - .payment_intent - .feature_metadata - .as_ref() - .map(|fm| { - let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = true; - } - updated_metadata - }); + let updated_feature_metadata = + payment_data + .payment_intent + .feature_metadata + .as_ref() + .map(|fm| { + let mut updated_metadata = fm.clone(); + if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { + rrm.payment_connector_transmission = true; + } + updated_metadata + }); match self.response { Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: common_enums::IntentStatus::from( @@ -1171,7 +1172,7 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata + feature_metadata: updated_feature_metadata, }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -1180,7 +1181,7 @@ impl .unwrap_or(common_enums::IntentStatus::Failed), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata + feature_metadata: updated_feature_metadata, }, } } diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 8bb23632356..48f20035c00 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -163,7 +163,7 @@ impl GetTracker, ProxyPaymentsI .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; self.validate_status_for_operation(payment_intent.status)?; - + let client_secret = header_payload .client_secret .as_ref() From f53ecd53fafdfacd063b26805c264591dd42ce09 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 18:13:13 +0000 Subject: [PATCH 17/26] docs(openapi): re-generate OpenAPI specification --- api-reference-v2/openapi_spec.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index cadae4fa45c..f8698225a9e 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -9025,6 +9025,14 @@ } ], "nullable": true + }, + "revenue_recovery_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/RevenueRecoveryMetadata" + } + ], + "nullable": true } } }, From 0bf7d6b6e359c8bd8d4507ff23ab8a8e1f6a0a48 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 14 Feb 2025 00:34:11 +0530 Subject: [PATCH 18/26] update PaymentAttempt to handle authentication type and adjust charges field --- .../src/payments/payment_attempt.rs | 5 +++-- .../src/core/payments/operations/proxy_payments_intent.rs | 3 +++ crates/router/src/core/payments/transformers.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 9dcedf10bde..52bf4b711b2 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -574,6 +574,7 @@ impl PaymentAttempt { consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), }); + let authentication_type = payment_intent.authentication_type.unwrap_or_default(); Ok(Self { payment_id: payment_intent.id.clone(), merchant_id: payment_intent.merchant_id.clone(), @@ -582,7 +583,7 @@ impl PaymentAttempt { // This will be decided by the routing algorithm and updated in update trackers // right before calling the connector connector: Some(request.connector.clone()), - authentication_type: payment_intent.authentication_type, + authentication_type, created_at: now, modified_at: now, last_synced: None, @@ -604,7 +605,7 @@ impl PaymentAttempt { authentication_connector: None, authentication_id: None, fingerprint_id: None, - charge_id: None, + charges: None, client_source: None, client_version: None, customer_acceptance: None, diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 48f20035c00..4cab918b609 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -368,11 +368,14 @@ impl UpdateTracker, ProxyPaymentsInten active_attempt_id: payment_data.payment_attempt.id.clone(), }; + let authentication_type = payment_data.payment_intent.authentication_type.unwrap_or_default(); + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { status: attempt_status, updated_by: storage_scheme.to_string(), connector, merchant_connector_id, + authentication_type, }; let updated_payment_intent = db diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 2afdc6ef905..b413ac531b0 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -16,8 +16,10 @@ use common_utils::{ use diesel_models::{ ephemeral_key, payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, - types::BillingConnectorMitTokenDetails, }; +#[cfg(feature = "v2")] +use diesel_models::types::BillingConnectorMitTokenDetails; + use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] use hyperswitch_domain_models::ApiModelToDieselModelConvertor; From 155fc9cfecb7be788a40e6914db237267f977d81 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:05:35 +0000 Subject: [PATCH 19/26] chore: run formatter --- .../src/core/payments/operations/proxy_payments_intent.rs | 5 ++++- crates/router/src/core/payments/transformers.rs | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 4cab918b609..6541480138a 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -368,7 +368,10 @@ impl UpdateTracker, ProxyPaymentsInten active_attempt_id: payment_data.payment_attempt.id.clone(), }; - let authentication_type = payment_data.payment_intent.authentication_type.unwrap_or_default(); + let authentication_type = payment_data + .payment_intent + .authentication_type + .unwrap_or_default(); let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { status: attempt_status, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index b413ac531b0..42d65a181e7 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -13,13 +13,12 @@ use common_utils::{ StringMajorUnitForConnector, }, }; +#[cfg(feature = "v2")] +use diesel_models::types::BillingConnectorMitTokenDetails; use diesel_models::{ ephemeral_key, payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, }; -#[cfg(feature = "v2")] -use diesel_models::types::BillingConnectorMitTokenDetails; - use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] use hyperswitch_domain_models::ApiModelToDieselModelConvertor; From 1d204058ab6bb6deba462bf647600ad1c448c1e5 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 14 Feb 2025 13:05:37 +0530 Subject: [PATCH 20/26] pass ci_checks --- crates/api_models/src/payments.rs | 2 +- crates/hyperswitch_domain_models/src/mandates.rs | 2 +- crates/hyperswitch_domain_models/src/payments/payment_intent.rs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 716a872d6de..b6798b1e43a 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1684,7 +1684,7 @@ impl MandateIds { pub struct MandateData { /// A way to update the mandate's payment method details pub update_mandate_id: Option, - /// A concent from the customer to store the payment method + /// A consent from the customer to store the payment method pub customer_acceptance: Option, /// A way to select the type of mandate used pub mandate_type: Option, diff --git a/crates/hyperswitch_domain_models/src/mandates.rs b/crates/hyperswitch_domain_models/src/mandates.rs index 53104cc692f..6ab7a590890 100644 --- a/crates/hyperswitch_domain_models/src/mandates.rs +++ b/crates/hyperswitch_domain_models/src/mandates.rs @@ -60,7 +60,7 @@ pub struct MandateAmountData { pub struct MandateData { /// A way to update the mandate's payment method details pub update_mandate_id: Option, - /// A concent from the customer to store the payment method + /// A consent from the customer to store the payment method pub customer_acceptance: Option, /// A way to select the type of mandate used pub mandate_type: Option, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index c889573ca1c..33138e56abf 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -290,6 +290,7 @@ pub enum PaymentIntentUpdate { #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize)] +#[allow(clippy::large_enum_variant)] pub enum PaymentIntentUpdate { /// PreUpdate tracker of ConfirmIntent ConfirmIntent { From f67101932242906e6eb695da3eab5a69897b288e Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 17 Feb 2025 01:56:18 +0530 Subject: [PATCH 21/26] refactor(payments): update revenue recovery metadata references and default feature to v2 --- .../src/payments/payment_attempt.rs | 2 -- crates/hyperswitch_domain_models/src/router_data.rs | 12 ++++++------ crates/router/Cargo.toml | 2 +- crates/router/src/core/payments/transformers.rs | 6 ++---- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 41eafd0b569..611e60cfff5 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -14,8 +14,6 @@ use common_utils::{ ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, }, }; -#[cfg(feature = "v2")] -use diesel_models::types::RevenueRecoveryMetadata; use diesel_models::{ ConnectorMandateReferenceId, PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index c9f63af2b66..b7974ca094f 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -481,8 +481,8 @@ impl .as_ref() .map(|fm| { let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = true; + if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { + rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; } updated_metadata }); @@ -494,8 +494,8 @@ impl .as_ref() .map(|fm| { let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = false; + if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { + rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallFailed; } updated_metadata }); @@ -1163,8 +1163,8 @@ impl .as_ref() .map(|fm| { let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.revenue_recovery_metadata { - rrm.payment_connector_transmission = true; + if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { + rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; } updated_metadata }); diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index d590797d642..a72c4458b37 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" license.workspace = true [features] -default = ["common_default", "v1"] +default = ["common_default", "v2"] common_default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] olap = ["hyperswitch_domain_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] tls = ["actix-web/rustls-0_22"] diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 4fc448cec64..ac1a69a89e8 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -13,8 +13,6 @@ use common_utils::{ StringMajorUnitForConnector, }, }; -#[cfg(feature = "v2")] -use diesel_models::types::BillingConnectorMitTokenDetails; use diesel_models::{ ephemeral_key, payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, @@ -320,9 +318,9 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .payment_intent .feature_metadata .as_ref() - .and_then(|fm| fm.revenue_recovery_metadata.as_ref()) + .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) .map(|rrm| { - rrm.billing_connector_mit_token_details + rrm.billing_connector_payment_details .connector_customer_id .clone() }); From 90f5cda22b6973f334f406fc94582df0fc495e6a Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 20:28:30 +0000 Subject: [PATCH 22/26] chore: run formatter --- crates/hyperswitch_domain_models/src/router_data.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index b7974ca094f..e45f3e9878b 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -482,7 +482,8 @@ impl .map(|fm| { let mut updated_metadata = fm.clone(); if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { - rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; + rrm.payment_connector_transmission = + common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; } updated_metadata }); @@ -494,8 +495,11 @@ impl .as_ref() .map(|fm| { let mut updated_metadata = fm.clone(); - if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { - rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallFailed; + if let Some(ref mut rrm) = + updated_metadata.payment_revenue_recovery_metadata + { + rrm.payment_connector_transmission = + common_enums::PaymentConnectorTransmission::ConnectorCallFailed; } updated_metadata }); @@ -1164,7 +1168,8 @@ impl .map(|fm| { let mut updated_metadata = fm.clone(); if let Some(ref mut rrm) = updated_metadata.payment_revenue_recovery_metadata { - rrm.payment_connector_transmission = common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; + rrm.payment_connector_transmission = + common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded; } updated_metadata }); From ff0e36e16f80a490f78ad632a40dfe0d766a1c0c Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 17 Feb 2025 02:07:44 +0530 Subject: [PATCH 23/26] refactor(router): change default feature from v2 to v1 --- crates/router/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index a72c4458b37..d590797d642 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" license.workspace = true [features] -default = ["common_default", "v2"] +default = ["common_default", "v1"] common_default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] olap = ["hyperswitch_domain_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] tls = ["actix-web/rustls-0_22"] From bcb3aa04fe5f2b10bc46ea7ebe36eab067142483 Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 17 Feb 2025 11:32:26 +0530 Subject: [PATCH 24/26] ci checks pass --- crates/hyperswitch_domain_models/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 3652e075aff..6c8f499388a 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -93,7 +93,6 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, - payment_revenue_recovery_metadata, } = from; Self { @@ -109,7 +108,6 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, - payment_revenue_recovery_metadata, } = self; ApiFeatureMetadata { From 34e675f9b66bcfc6f61fdf26384912e048be06d2 Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 24 Feb 2025 23:17:16 +0530 Subject: [PATCH 25/26] resolve comments and update the branch --- .github/workflows/cypress-tests-runner.yml | 2 +- .typos.toml | 4 + CHANGELOG.md | 86 + Cargo.lock | 1 + add_connector_updated.md | 720 ++++ .../api-reference/payments/payments--list.mdx | 3 + api-reference-v2/mint.json | 3 +- api-reference-v2/openapi_spec.json | 1112 ++++- api-reference/openapi_spec.json | 306 +- config/config.example.toml | 2 +- config/deployments/integration_test.toml | 2 +- config/deployments/production.toml | 4 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- crates/api_models/Cargo.toml | 1 + crates/api_models/src/admin.rs | 21 +- crates/api_models/src/events.rs | 1 + .../src/events/external_service_auth.rs | 30 + crates/api_models/src/events/payment.rs | 23 +- .../api_models/src/external_service_auth.rs | 35 + crates/api_models/src/lib.rs | 10 + crates/api_models/src/payments.rs | 381 +- crates/api_models/src/payments/trait_impls.rs | 29 + crates/api_models/src/webhooks.rs | 35 + crates/common_enums/src/connector_enums.rs | 13 +- crates/common_enums/src/enums.rs | 1730 ++++++-- crates/common_types/src/domain.rs | 12 + crates/common_types/src/payments.rs | 97 +- crates/common_types/src/refunds.rs | 4 +- crates/common_utils/src/events.rs | 1 + crates/common_utils/src/ext_traits.rs | 52 +- crates/common_utils/src/types.rs | 7 + .../src/types/primitive_wrappers.rs | 107 + crates/connector_configs/src/common_config.rs | 1 + crates/connector_configs/src/connector.rs | 5 + .../connector_configs/toml/development.toml | 64 +- crates/connector_configs/toml/production.toml | 66 +- crates/connector_configs/toml/sandbox.toml | 66 +- crates/diesel_models/src/business_profile.rs | 11 +- crates/diesel_models/src/payment_attempt.rs | 43 +- crates/diesel_models/src/payment_intent.rs | 13 +- .../src/query/payment_attempt.rs | 58 + crates/diesel_models/src/schema.rs | 5 + crates/diesel_models/src/schema_v2.rs | 6 + crates/diesel_models/src/types.rs | 8 +- crates/diesel_models/src/user/sample_data.rs | 11 +- crates/hyperswitch_connectors/Cargo.toml | 1 + .../src/connectors/coingate.rs | 294 +- .../src/connectors/coingate/transformers.rs | 202 +- .../src/connectors/cybersource.rs | 1 + .../connectors/cybersource/transformers.rs | 65 +- .../src/connectors/datatrans/transformers.rs | 36 +- .../src/connectors/moneris.rs | 316 +- .../src/connectors/moneris/transformers.rs | 322 +- .../unified_authentication_service.rs | 110 +- .../transformers.rs | 95 +- .../src/connectors/wellsfargo/transformers.rs | 4 +- .../src/connectors/xendit.rs | 215 +- .../src/connectors/xendit/transformers.rs | 218 +- .../src/default_implementations.rs | 95 +- crates/hyperswitch_connectors/src/utils.rs | 3790 ++++++++++------- crates/hyperswitch_domain_models/Cargo.toml | 1 + .../src/business_profile.rs | 14 +- crates/hyperswitch_domain_models/src/lib.rs | 8 + .../hyperswitch_domain_models/src/payments.rs | 8 +- .../src/payments/payment_attempt.rs | 272 +- .../src/payments/payment_intent.rs | 152 +- .../src/revenue_recovery.rs | 190 + .../src/router_data.rs | 8 +- .../unified_authentication_service.rs | 3 + .../src/router_request_types.rs | 10 +- .../unified_authentication_service.rs | 24 +- crates/hyperswitch_domain_models/src/types.rs | 18 +- crates/hyperswitch_interfaces/Cargo.toml | 2 + crates/hyperswitch_interfaces/src/api.rs | 40 +- crates/hyperswitch_interfaces/src/types.rs | 14 +- crates/hyperswitch_interfaces/src/webhooks.rs | 29 + crates/masking/src/abs.rs | 3 + crates/masking/src/bytes.rs | 4 + crates/masking/src/secret.rs | 4 + crates/masking/src/strong_secret.rs | 4 + crates/openapi/src/openapi.rs | 7 + crates/openapi/src/openapi_v2.rs | 16 +- crates/openapi/src/routes/payments.rs | 19 + crates/router/Cargo.toml | 3 +- .../payment_connector_required_fields.rs | 175 +- .../src/connector/checkout/transformers.rs | 9 +- .../connector/netcetera/netcetera_types.rs | 7 +- .../src/connector/netcetera/transformers.rs | 23 +- .../router/src/connector/nmi/transformers.rs | 10 +- crates/router/src/connector/stripe.rs | 10 +- .../src/connector/stripe/transformers.rs | 14 +- .../connector/threedsecureio/transformers.rs | 3 +- crates/router/src/connector/utils.rs | 27 - crates/router/src/core.rs | 1 + crates/router/src/core/admin.rs | 9 + crates/router/src/core/errors.rs | 17 + .../router/src/core/external_service_auth.rs | 94 + crates/router/src/core/payment_link.rs | 66 +- .../payment_link_initiate/payment_link.css | 1 - .../payment_link_initiate/payment_link.js | 1 + .../payment_link_initiator.js | 1 + .../secure_payment_link_initiator.js | 1 + crates/router/src/core/payment_methods.rs | 12 +- .../router/src/core/payment_methods/vault.rs | 2 +- crates/router/src/core/payments.rs | 143 +- .../connector_integration_v2_impls.rs | 14 +- crates/router/src/core/payments/flows.rs | 61 +- .../src/core/payments/flows/authorize_flow.rs | 1 + .../payments/flows/complete_authorize_flow.rs | 1 + .../src/core/payments/flows/session_flow.rs | 164 +- crates/router/src/core/payments/helpers.rs | 124 +- crates/router/src/core/payments/operations.rs | 3 +- .../payments/operations/payment_confirm.rs | 297 +- .../payments/operations/payment_create.rs | 4 + .../operations/proxy_payments_intent.rs | 36 +- crates/router/src/core/payments/retry.rs | 3 + .../router/src/core/payments/tokenization.rs | 8 +- .../router/src/core/payments/transformers.rs | 114 +- crates/router/src/core/payments/types.rs | 17 +- crates/router/src/core/refunds/validator.rs | 29 + crates/router/src/core/routing/helpers.rs | 739 ++-- .../core/unified_authentication_service.rs | 94 +- .../unified_authentication_service/types.rs | 5 +- .../unified_authentication_service/utils.rs | 187 +- crates/router/src/core/utils.rs | 52 + crates/router/src/core/webhooks.rs | 2 + .../router/src/core/webhooks/incoming_v2.rs | 20 + .../src/core/webhooks/recovery_incoming.rs | 307 ++ crates/router/src/db/kafka_store.rs | 69 + crates/router/src/lib.rs | 1 + crates/router/src/macros.rs | 33 + crates/router/src/routes.rs | 10 +- crates/router/src/routes/app.rs | 35 + crates/router/src/routes/hypersense.rs | 76 + crates/router/src/routes/lock_utils.rs | 8 +- crates/router/src/routes/payments.rs | 37 +- crates/router/src/routes/recovery_webhooks.rs | 53 + crates/router/src/services/authentication.rs | 52 +- .../connector_integration_interface.rs | 28 + crates/router/src/types.rs | 3 +- crates/router/src/types/api.rs | 7 +- crates/router/src/types/api/admin.rs | 2 + crates/router/src/types/api/payments.rs | 9 +- .../src/types/storage/payment_attempt.rs | 9 + crates/router/src/types/transformers.rs | 8 +- crates/router/src/utils/user/sample_data.rs | 4 + crates/router/tests/connectors/moneris.rs | 2 +- crates/router/tests/payments.rs | 4 + crates/router/tests/payments2.rs | 4 + crates/router_env/src/logger/types.rs | 8 + .../src/mock_db/payment_attempt.rs | 19 + .../src/mock_db/payment_intent.rs | 29 + .../src/payments/payment_attempt.rs | 81 + .../src/payments/payment_intent.rs | 335 ++ crates/test_utils/src/connector_auth.rs | 2 +- .../cypress/e2e/configs/Payment/Moneris.js | 316 ++ .../cypress/e2e/configs/Payment/Utils.js | 2 + cypress-tests/cypress/support/commands.js | 14 + loadtest/config/development.toml | 2 +- .../down.sql | 16 + .../up.sql | 21 + scripts/add_connector.sh | 26 +- .../down.sql | 2 + .../up.sql | 2 + 166 files changed, 12465 insertions(+), 3586 deletions(-) create mode 100644 add_connector_updated.md create mode 100644 api-reference-v2/api-reference/payments/payments--list.mdx create mode 100644 crates/api_models/src/events/external_service_auth.rs create mode 100644 crates/api_models/src/external_service_auth.rs create mode 100644 crates/api_models/src/payments/trait_impls.rs create mode 100644 crates/common_utils/src/types/primitive_wrappers.rs create mode 100644 crates/hyperswitch_domain_models/src/revenue_recovery.rs create mode 100644 crates/router/src/core/external_service_auth.rs create mode 100644 crates/router/src/core/webhooks/recovery_incoming.rs create mode 100644 crates/router/src/routes/hypersense.rs create mode 100644 crates/router/src/routes/recovery_webhooks.rs create mode 100644 cypress-tests/cypress/e2e/configs/Payment/Moneris.js create mode 100644 migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql create mode 100644 migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql create mode 100644 v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql create mode 100644 v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index dbef78355de..44a0baa8d34 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -128,7 +128,7 @@ jobs: CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} DESTINATION_FILE_NAME: "creds.json.gpg" - S3_SOURCE_FILE_NAME: "5b3ebdad-1067-421b-83e5-3ceb73c3eeeb.json.gpg" + S3_SOURCE_FILE_NAME: "57a07166-894a-4f43-b4b5-ee1155138ec9.json.gpg" shell: bash run: | mkdir -p ".github/secrets" ".github/test" diff --git a/.typos.toml b/.typos.toml index 75d1290eb46..1955bd608cb 100644 --- a/.typos.toml +++ b/.typos.toml @@ -59,6 +59,10 @@ OltCounty = "OltCounty" # Is a state in Romania olt = "olt" # Is iso representation of a state in Romania Vas = "Vas" # Is iso representation of a state in Hungary vas = "vas" # Is iso representation of a state in Hungary +WHT = "WHT" # Is iso representation of a state in Belgium +CantonOfEschSurAlzette = "CantonOfEschSurAlzette" # Is a state in Luxembourg +GorenjaVasPoljane = "GorenjaVasPoljane" # Is a state in Slovenia +sur = "sur" # Is iso representation of a state in Luxembourg [default.extend-words] aci = "aci" # Name of a connector diff --git a/CHANGELOG.md b/CHANGELOG.md index d514121ec75..7351e838c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,92 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.24.0 + +### Features + +- **connector:** + - Add Samsung pay mandate support for Cybersource ([#7298](https://github.com/juspay/hyperswitch/pull/7298)) ([`9bc8fd4`](https://github.com/juspay/hyperswitch/commit/9bc8fd4d8c4be1b54050398dfb3b574e924e4b5f)) + - Add support for passive churn recovery webhooks ([#7109](https://github.com/juspay/hyperswitch/pull/7109)) ([`0688972`](https://github.com/juspay/hyperswitch/commit/0688972814cf03edbff4bf125a59c338a7e49593)) +- **router:** Add `merchant_configuration_id` in netcetera metadata and make other merchant configurations optional ([#7347](https://github.com/juspay/hyperswitch/pull/7347)) ([`57ab869`](https://github.com/juspay/hyperswitch/commit/57ab8693e26a54cd5fe73b28459c9b03235e5d5b)) +- **samsung_pay:** Collect customer address details form Samsung Pay based on business profile config and connector required fields ([#7320](https://github.com/juspay/hyperswitch/pull/7320)) ([`c14519e`](https://github.com/juspay/hyperswitch/commit/c14519ebd958abc79879244f8180686b2be30d31)) + +### Bug Fixes + +- **connector:** [DATATRANS] Fix Force Sync Flow ([#7331](https://github.com/juspay/hyperswitch/pull/7331)) ([`0e96e24`](https://github.com/juspay/hyperswitch/commit/0e96e2470c6906fe77b35c14821e3ebcfa454e39)) +- **routing:** Fixed 5xx error logs in dynamic routing metrics ([#7335](https://github.com/juspay/hyperswitch/pull/7335)) ([`049fcdb`](https://github.com/juspay/hyperswitch/commit/049fcdb3fb19c6af45392a5ef3a0cbf642598af8)) +- **samsung_pay:** Add payment_method_type duplication check ([#7337](https://github.com/juspay/hyperswitch/pull/7337)) ([`11ff437`](https://github.com/juspay/hyperswitch/commit/11ff437456f9d97205ce07e4d4df63006f3ad0c6)) + +### Miscellaneous Tasks + +- **masking:** Add peek_mut to PeekInterface ([#7281](https://github.com/juspay/hyperswitch/pull/7281)) ([`890a265`](https://github.com/juspay/hyperswitch/commit/890a265e7b1b06aeea100994efbebe3ce898a125)) + +**Full Changelog:** [`2025.02.21.0...2025.02.24.0`](https://github.com/juspay/hyperswitch/compare/2025.02.21.0...2025.02.24.0) + +- - - + +## 2025.02.21.0 + +### Features + +- **core:** Add support for confirmation flow for click to pay ([#6982](https://github.com/juspay/hyperswitch/pull/6982)) ([`74bbf4b`](https://github.com/juspay/hyperswitch/commit/74bbf4bf271d45e61e594857707250c95a86f43f)) +- **router:** + - Add Payments - List endpoint for v2 ([#7191](https://github.com/juspay/hyperswitch/pull/7191)) ([`d1f537e`](https://github.com/juspay/hyperswitch/commit/d1f537e22909a3580dbf9069852f4257fdbad66b)) + - [Xendit] add support for split payments ([#7143](https://github.com/juspay/hyperswitch/pull/7143)) ([`451acba`](https://github.com/juspay/hyperswitch/commit/451acba005a85228925da5b6c252815d58f96fd7)) + +### Bug Fixes + +- **connector:** [DATATRANS] Add new payment status ([#7327](https://github.com/juspay/hyperswitch/pull/7327)) ([`2b74a94`](https://github.com/juspay/hyperswitch/commit/2b74a94e3abf6f493058b4a4981f45be558ea3d2)) +- **payment_methods_v2:** Update fingerprint implementation in v2 ([#7270](https://github.com/juspay/hyperswitch/pull/7270)) ([`e475f63`](https://github.com/juspay/hyperswitch/commit/e475f63967a7922d1a0211d75ad4567a3182f3e8)) + +**Full Changelog:** [`2025.02.20.0...2025.02.21.0`](https://github.com/juspay/hyperswitch/compare/2025.02.20.0...2025.02.21.0) + +- - - + +## 2025.02.20.0 + +### Features + +- **core:** Add hypersense integration api ([#7218](https://github.com/juspay/hyperswitch/pull/7218)) ([`22633be`](https://github.com/juspay/hyperswitch/commit/22633be55cfc42dc4a7171c3193da594d0557bfb)) + +### Bug Fixes + +- **connector:** [SCRIPT] Update template generating script and updated connector doc ([#7301](https://github.com/juspay/hyperswitch/pull/7301)) ([`2d9df53`](https://github.com/juspay/hyperswitch/commit/2d9df53491b1ef662736efec60ec5e5368466bb4)) + +### Refactors + +- **utils:** Match string for state with SDK's naming convention ([#7300](https://github.com/juspay/hyperswitch/pull/7300)) ([`f3ca200`](https://github.com/juspay/hyperswitch/commit/f3ca2009c1902094a72b8bf43e89b406e44ecfd4)) + +**Full Changelog:** [`2025.02.19.0...2025.02.20.0`](https://github.com/juspay/hyperswitch/compare/2025.02.19.0...2025.02.20.0) + +- - - + +## 2025.02.19.0 + +### Features + +- **connector:** [Moneris] Add payments flow ([#7249](https://github.com/juspay/hyperswitch/pull/7249)) ([`d18d98a`](https://github.com/juspay/hyperswitch/commit/d18d98a1f687aef1e0f21f6a26387cb9ca7a347d)) +- **core:** Api ,domain and diesel model changes for extended authorization ([#6607](https://github.com/juspay/hyperswitch/pull/6607)) ([`e14d6c4`](https://github.com/juspay/hyperswitch/commit/e14d6c4465bb1276a348a668051c084af72de8e3)) +- **payments:** [Payment links] Add configs for payment link ([#7288](https://github.com/juspay/hyperswitch/pull/7288)) ([`72080c6`](https://github.com/juspay/hyperswitch/commit/72080c67c7927b53d5ca013983f379e9b027c51f)) + +**Full Changelog:** [`2025.02.18.0...2025.02.19.0`](https://github.com/juspay/hyperswitch/compare/2025.02.18.0...2025.02.19.0) + +- - - + +## 2025.02.18.0 + +### Features + +- **coingate:** Add Crypto Pay Flow ([#7247](https://github.com/juspay/hyperswitch/pull/7247)) ([`c868ff3`](https://github.com/juspay/hyperswitch/commit/c868ff38e0234fa83f1615e751af12cb7d32a3d9)) + +### Refactors + +- **utils:** Use to_state_code of hyperswitch_connectors in router ([#7278](https://github.com/juspay/hyperswitch/pull/7278)) ([`b97370d`](https://github.com/juspay/hyperswitch/commit/b97370d59fd167af9c24f4470f4668ce2ee76a89)) + +**Full Changelog:** [`2025.02.15.0...2025.02.18.0`](https://github.com/juspay/hyperswitch/compare/2025.02.15.0...2025.02.18.0) + +- - - + ## 2025.02.15.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index 5b4d9767029..b6b54a66a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4005,6 +4005,7 @@ dependencies = [ "bytes 1.7.1", "cards", "common_enums", + "common_types", "common_utils", "encoding_rs", "error-stack", diff --git a/add_connector_updated.md b/add_connector_updated.md new file mode 100644 index 00000000000..4ee8c895656 --- /dev/null +++ b/add_connector_updated.md @@ -0,0 +1,720 @@ +# Guide to Integrating a Connector + +## Table of Contents + +1. [Introduction](#introduction) +2. [Prerequisites](#prerequisites) +3. [Understanding Connectors and Payment Methods](#understanding-connectors-and-payment-methods) +4. [Integration Steps](#integration-steps) + - [Generate Template](#generate-template) + - [Implement Request & Response Types](#implement-request--response-types) + - [Implement transformers.rs](#implementing-transformersrs) + - [Handle Response Mapping](#handle-response-mapping) + - [Recommended Fields for Connector Request and Response](#recommended-fields-for-connector-request-and-response) + - [Error Handling](#error-handling) +5. [Implementing the Traits](#implementing-the-traits) + - [ConnectorCommon](#connectorcommon) + - [ConnectorIntegration](#connectorintegration) + - [ConnectorCommonExt](#connectorcommonext) + - [Other Traits](#othertraits) +6. [Set the Currency Unit](#set-the-currency-unit) +7. [Connector utility functions](#connector-utility-functions) +8. [Connector configs for control center](#connector-configs-for-control-center) +9. [Update `ConnectorTypes.res` and `ConnectorUtils.res`](#update-connectortypesres-and-connectorutilsres) +10. [Add Connector Icon](#add-connector-icon) +11. [Test the Connector](#test-the-connector) +12. [Build Payment Request and Response from JSON Schema](#build-payment-request-and-response-from-json-schema) + +## Introduction + +This guide provides instructions on integrating a new connector with Router, from setting up the environment to implementing API interactions. + +## Prerequisites + +- Familiarity with the Connector API you’re integrating +- A locally set up and running Router repository +- API credentials for testing (sign up for sandbox/UAT credentials on the connector’s website). +- Rust nightly toolchain installed for code formatting: + ```bash + rustup toolchain install nightly + ``` + +## Understanding Connectors and Payment Methods + +A **Connector** processes payments (e.g., Stripe, Adyen) or manages fraud risk (e.g., Signifyd). A **Payment Method** is a specific way to transact (e.g., credit card, PayPal). See the [Hyperswitch Payment Matrix](https://hyperswitch.io/pm-list) for details. + +## Integration Steps + +Integrating a connector is mainly an API integration task. You'll define request and response types and implement required traits. + +This tutorial covers card payments via [Billwerk](https://optimize-docs.billwerk.com/). Review the API reference and test APIs before starting. + +Follow these steps to integrate a new connector. + +### Generate Template + +Run the following script to create the connector structure: + +```bash +sh scripts/add_connector.sh +``` + +Example folder structure: + +``` +hyperswitch_connectors/src/connectors +├── billwerk +│ └── transformers.rs +└── billwerk.rs +crates/router/tests/connectors +└── billwerk.rs +``` + +**Note:** move the file `crates/hyperswitch_connectors/src/connectors/connector_name/test.rs` to `crates/router/tests/connectors/connector_name.rs` + + +Define API request/response types and conversions in `hyperswitch_connectors/src/connector/billwerk/transformers.rs` + +Implement connector traits in `hyperswitch_connectors/src/connector/billwerk.rs` + +Write basic payment flow tests in `crates/router/tests/connectors/billwerk.rs` + +Boilerplate code with todo!() is provided—follow the guide and complete the necessary implementations. + +### Implement Request & Response Types + +Integrating a new connector involves transforming Router's core data into the connector's API format. Since the Connector module is stateless, Router handles data persistence. + +#### Implementing transformers.rs + +Design request/response structures based on the connector's API spec. + +Define request format in `transformers.rs`: + +```rust +#[derive(Debug, Serialize)] +pub struct BillwerkCustomerObject { + handle: Option, + email: Option, + address: Option>, + address2: Option>, + city: Option, + country: Option, + first_name: Option>, + last_name: Option>, +} + +#[derive(Debug, Serialize)] +pub struct BillwerkPaymentsRequest { + handle: String, + amount: MinorUnit, + source: Secret, + currency: common_enums::Currency, + customer: BillwerkCustomerObject, + metadata: Option, + settle: bool, +} +``` + +Since Router is connector agnostic, only minimal data is sent to connector and optional fields may be ignored. + +We transform our `PaymentsAuthorizeRouterData` into Billwerk's `PaymentsRequest` structure by employing the `try_from` function. + +```rust +impl TryFrom<&BillwerkRouterData<&types::PaymentsAuthorizeRouterData>> for BillwerkPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &BillwerkRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + if item.router_data.is_three_ds() { + return Err(errors::ConnectorError::NotImplemented( + "Three_ds payments through Billwerk".to_string(), + ) + .into()); + }; + let source = match item.router_data.get_payment_method_token()? { + PaymentMethodToken::Token(pm_token) => Ok(pm_token), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_token", + }), + }?; + Ok(Self { + handle: item.router_data.connector_request_reference_id.clone(), + amount: item.amount, + source, + currency: item.router_data.request.currency, + customer: BillwerkCustomerObject { + handle: item.router_data.customer_id.clone(), + email: item.router_data.request.email.clone(), + address: item.router_data.get_optional_billing_line1(), + address2: item.router_data.get_optional_billing_line2(), + city: item.router_data.get_optional_billing_city(), + country: item.router_data.get_optional_billing_country(), + first_name: item.router_data.get_optional_billing_first_name(), + last_name: item.router_data.get_optional_billing_last_name(), + }, + metadata: item.router_data.request.metadata.clone().map(Into::into), + settle: item.router_data.request.is_auto_capture()?, + }) + } +} +``` + +### Handle Response Mapping + +When implementing the response type, the key enum to define for each connector is `PaymentStatus`. It represents the different status types returned by the connector, as specified in its API spec. Below is the definition for Billwerk. + +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum BillwerkPaymentState { + Created, + Authorized, + Pending, + Settled, + Failed, + Cancelled, +} + +impl From for enums::AttemptStatus { + fn from(item: BillwerkPaymentState) -> Self { + match item { + BillwerkPaymentState::Created | BillwerkPaymentState::Pending => Self::Pending, + BillwerkPaymentState::Authorized => Self::Authorized, + BillwerkPaymentState::Settled => Self::Charged, + BillwerkPaymentState::Failed => Self::Failure, + BillwerkPaymentState::Cancelled => Self::Voided, + } + } +} +``` + +Here are common payment attempt statuses: + +- **Charged:** Payment succeeded. +- **Pending:** Payment is processing. +- **Failure:** Payment failed. +- **Authorized:** Payment authorized; can be voided, captured, or partially captured. +- **AuthenticationPending:** Customer action required. +- **Voided:** Payment voided, funds returned to the customer. + +**Note:** Default status should be `Pending`. Only explicit success or failure from the connector should mark the payment as `Charged` or `Failure`. + +Define response format in `transformers.rs`: + +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BillwerkPaymentsResponse { + state: BillwerkPaymentState, + handle: String, + error: Option, + error_state: Option, +} +``` + +We transform our `ResponseRouterData` into `PaymentsResponseData` by employing the `try_from` function. + +```rust +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let error_response = if item.response.error.is_some() || item.response.error_state.is_some() + { + Some(ErrorResponse { + code: item + .response + .error_state + .clone() + .unwrap_or(NO_ERROR_CODE.to_string()), + message: item + .response + .error_state + .unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: item.response.error, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.handle.clone()), + }) + } else { + None + }; + let payments_response = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.handle.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.handle), + incremental_authorization_allowed: None, + charges: None, + }; + Ok(Self { + status: enums::AttemptStatus::from(item.response.state), + response: error_response.map_or_else(|| Ok(payments_response), Err), + ..item.data + }) + } +} +``` + +### Recommended Fields for Connector Request and Response + +- **connector_request_reference_id:** Merchant's reference ID in the payment request (e.g., `reference` in Checkout). + +```rust + reference: item.router_data.connector_request_reference_id.clone(), +``` +- **connector_response_reference_id:** ID used for transaction identification in the connector dashboard, linked to merchant_reference or connector_transaction_id. + +```rust + connector_response_reference_id: item.response.reference.or(Some(item.response.id)), +``` + +- **resource_id:** The connector's connector_transaction_id is used as the resource_id. If unavailable, set to NoResponseId. + +```rust + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), +``` + +- **redirection_data:** For redirection flows (e.g., 3D Secure), assign the redirection link. + +```rust + let redirection_data = item.response.links.redirect.map(|href| { + services::RedirectForm::from((href.redirection_url, services::Method::Get)) + }); +``` + +### Error Handling + +Define error responses: + +```rust +#[derive(Debug, Serialize, Deserialize)] +pub struct BillwerkErrorResponse { + pub code: Option, + pub error: String, + pub message: Option, +} +``` + +By following these steps, you can integrate a new connector efficiently while ensuring compatibility with Router's architecture. + +## Implementing the Traits + +The `mod.rs` file contains trait implementations using connector types in transformers. A struct with the connector name holds these implementations. Below are the mandatory traits: + +### ConnectorCommon +Contains common description of the connector, like the base endpoint, content-type, error response handling, id, currency unit. + +Within the `ConnectorCommon` trait, you'll find the following methods : + +- `id` method corresponds directly to the connector name. + +```rust + fn id(&self) -> &'static str { + "Billwerk" + } +``` + +- `get_currency_unit` method anticipates you to [specify the accepted currency unit](#set-the-currency-unit) for the connector. + +```rust + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } +``` + +- `common_get_content_type` method requires you to provide the accepted content type for the connector API. + +```rust + fn common_get_content_type(&self) -> &'static str { + "application/json" + } +``` + +- `get_auth_header` method accepts common HTTP Authorization headers that are accepted in all `ConnectorIntegration` flows. + +```rust + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = BillwerkAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Basic {encoded_api_key}").into_masked(), + )]) + } +``` + +- `base_url` method is for fetching the base URL of connector's API. Base url needs to be consumed from configs. + +```rust + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.billwerk.base_url.as_ref() + } +``` + +- `build_error_response` method is common error response handling for a connector if it is same in all cases + +```rust + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: BillwerkErrorResponse = res + .response + .parse_struct("BillwerkErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response + .code + .map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()), + message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: Some(response.error), + attempt_status: None, + connector_transaction_id: None, + }) + } +``` + +### ConnectorIntegration +For every api endpoint contains the url, using request transform and response transform and headers. +Within the `ConnectorIntegration` trait, you'll find the following methods implemented(below mentioned is example for authorized flow): + +- `get_url` method defines endpoint for authorize flow, base url is consumed from `ConnectorCommon` trait. + +```rust + fn get_url( + &self, + _req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult { + let base_url = connectors + .billwerk + .secondary_base_url + .as_ref() + .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + Ok(format!("{base_url}v1/token")) + } +``` + +- `get_headers` method accepts HTTP headers that are accepted for authorize flow. In this context, it is utilized from the `ConnectorCommonExt` trait, as the connector adheres to common headers across various flows. + +```rust + fn get_headers( + &self, + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } +``` + +- `get_request_body` method uses transformers to convert the Hyperswitch payment request to the connector's format. If successful, it returns the request as `RequestContent::Json`, supporting formats like JSON, form-urlencoded, XML, and raw bytes. + +```rust + fn get_request_body( + &self, + req: &TokenizationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = BillwerkTokenRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } +``` + +- `build_request` method assembles the API request by providing the method, URL, headers, and request body as parameters. + +```rust + fn build_request( + &self, + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::TokenizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::TokenizationType::get_headers(self, req, connectors)?) + .set_body(types::TokenizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } +``` + +- `handle_response` method calls transformers where connector response data is transformed into hyperswitch response. + +```rust + fn handle_response( + &self, + data: &TokenizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult + where + PaymentsResponseData: Clone, + { + let response: BillwerkTokenResponse = res + .response + .parse_struct("BillwerkTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } +``` + +- `get_error_response` method to manage error responses. As the handling of checkout errors remains consistent across various flows, we've incorporated it from the `build_error_response` method within the `ConnectorCommon` trait. + +```rust + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +``` + +### ConnectorCommonExt +Adds functions with a generic type, including the `build_headers` method. This method constructs both common headers and Authorization headers (from `get_auth_header`), returning them as a vector. + +```rust + impl ConnectorCommonExt for Billwerk + where + Self: ConnectorIntegration, + { + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + } +``` + +### OtherTraits +**Payment :** This trait includes several other traits and is meant to represent the functionality related to payments. + +**PaymentAuthorize :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment authorization. + +**PaymentCapture :** This trait extends the `api::ConnectorIntegration `trait with specific types related to manual payment capture. + +**PaymentSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment retrieve. + +**Refund :** This trait includes several other traits and is meant to represent the functionality related to Refunds. + +**RefundExecute :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds create. + +**RefundSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds retrieve. + +And the below derive traits + +- **Debug** +- **Clone** +- **Copy** + +### **Set the currency Unit** + +Part of the `ConnectorCommon` trait, it allows connectors to specify their accepted currency unit as either `Base` or `Minor`. For example, PayPal uses the base unit (e.g., USD), while Hyperswitch uses the minor unit (e.g., cents). Conversion is required if the connector uses the base unit. + +```rust +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for PaypalRouterData +{ + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} +``` + +### **Connector utility functions** + +Contains utility functions for constructing connector requests and responses. Use these helpers for retrieving fields like `get_billing_country`, `get_browser_info`, and `get_expiry_date_as_yyyymm`, as well as for validations like `is_three_ds` and `is_auto_capture`. + +```rust + let json_wallet_data: CheckoutGooglePayData = wallet_data.get_wallet_token_as_json()?; +``` + +### **Connector configs for control center** + +This section is for developers using the [Hyperswitch Control Center](https://github.com/juspay/hyperswitch-control-center). Update the connector configuration in development.toml and run the wasm-pack build command. Replace placeholders with actual paths. + +1. Install wasm-pack: + +```bash +cargo install wasm-pack +``` + +2. Add connector configuration: + + Open the development.toml file located at crates/connector_configs/toml/development.toml in your Hyperswitch project. + Find the [stripe] section and add the configuration for example_connector. Example: + + ```toml + # crates/connector_configs/toml/development.toml + + # Other connector configurations... + + [stripe] + [stripe.connector_auth.HeaderKey] + api_key="Secret Key" + + # Add any other Stripe-specific configuration here + + [example_connector] + # Your specific connector configuration for reference + # ... + + ``` + +3. Update paths: + Replace /absolute/path/to/hyperswitch-control-center and /absolute/path/to/hyperswitch with actual paths. + +4. Run `wasm-pack` build: + wasm-pack build --target web --out-dir /absolute/path/to/hyperswitch-control-center/public/hyperswitch/wasm --out-name euclid /absolute/path/to/hyperswitch/crates/euclid_wasm -- --features dummy_connector + +By following these steps, you should be able to update the connector configuration and build the WebAssembly files successfully. + +### Update `ConnectorTypes.res` and `ConnectorUtils.res` + +1. **Update `ConnectorTypes.res`**: + - Open `src/screens/HyperSwitch/Connectors/ConnectorTypes.res`. + - Add your connector to the `connectorName` enum: + ```reason + type connectorName = + | Stripe + | DummyConnector + | YourNewConnector + ``` + - Save the file. + +2. **Update `ConnectorUtils.res`**: + - Open `src/screens/HyperSwitch/Connectors/ConnectorUtils.res`. + - Update functions with your connector: + ```reason + let connectorList : array = [Stripe, YourNewConnector] + + let getConnectorNameString = (connectorName: connectorName) => + switch connectorName { + | Stripe => "Stripe" + | YourNewConnector => "Your New Connector" + }; + + let getConnectorInfo = (connectorName: connectorName) => + switch connectorName { + | Stripe => "Stripe description." + | YourNewConnector => "Your New Connector description." + }; + ``` + - Save the file. + +### Add Connector Icon + +1. **Prepare the Icon**: + Name your connector icon in uppercase (e.g., `YOURCONNECTOR.SVG`). + +2. **Add the Icon**: + Navigate to `public/hyperswitch/Gateway` and copy your SVG icon file there. + +3. **Verify Structure**: + Ensure the file is correctly placed in `public/hyperswitch/Gateway`: + + ``` + public + └── hyperswitch + └── Gateway + └── YOURCONNECTOR.SVG + ``` + Save the changes made to the `Gateway` folder. + +### **Test the Connector** + +1. **Template Code** + + The template script generates a test file with 20 sanity tests. Implement these tests when adding a new connector. + + Example test: + ```rust + #[serial_test::serial] + #[actix_web::test] + async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); + } + ``` + +2. **Utility Functions** + + Helper functions for tests are available in `tests/connector/utils`, making test writing easier. + +3. **Set API Keys** + + Before running tests, configure API keys in sample_auth.toml and set the environment variable: + + ```bash + export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml" + cargo test --package router --test connectors -- checkout --test-threads=1 + ``` + +### **Build Payment Request and Response from JSON Schema** + +1. **Install OpenAPI Generator:** + ```bash + brew install openapi-generator + ``` + +2. **Generate Rust Code:** + ```bash + export CONNECTOR_NAME="" + export SCHEMA_PATH="" + openapi-generator generate -g rust -i ${SCHEMA_PATH} -o temp && cat temp/src/models/* > crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && rm -rf temp && sed -i'' -r "s/^pub use.*//;s/^pub mod.*//;s/^\/.*//;s/^.\*.*//;s/crate::models:://g;" crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && cargo +nightly fmt + ``` \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--list.mdx b/api-reference-v2/api-reference/payments/payments--list.mdx new file mode 100644 index 00000000000..80350a5705e --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/list +--- \ No newline at end of file diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 8fd3b64230b..d935ec84a61 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -43,7 +43,8 @@ "api-reference/payments/payments--payment-methods-list", "api-reference/payments/payments--confirm-intent", "api-reference/payments/payments--get", - "api-reference/payments/payments--create-and-confirm-intent" + "api-reference/payments/payments--create-and-confirm-intent", + "api-reference/payments/payments--list" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index cedd4abcca6..16fcefac0ee 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -2258,6 +2258,341 @@ ] } }, + "/v2/payments/list": { + "get": { + "tags": [ + "Payments" + ], + "summary": "Payments - List", + "description": "To list the *payments*", + "operationId": "List all Payments", + "parameters": [ + { + "name": "payment_id", + "in": "path", + "description": "The identifier for payment", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "profile_id", + "in": "path", + "description": "The identifier for business profile", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "customer_id", + "in": "path", + "description": "The identifier for customer", + "required": true, + "schema": { + "type": "string", + "nullable": true, + "maxLength": 64, + "minLength": 1 + }, + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44" + }, + { + "name": "starting_after", + "in": "path", + "description": "A cursor for use in pagination, fetch the next list after some object", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "ending_before", + "in": "path", + "description": "A cursor for use in pagination, fetch the previous list before some object", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "limit", + "in": "path", + "description": "limit on the number of objects to return", + "required": true, + "schema": { + "type": "integer", + "format": "int32", + "default": 10, + "maximum": 100, + "minimum": 0 + } + }, + { + "name": "offset", + "in": "path", + "description": "The starting point within a list of objects", + "required": true, + "schema": { + "type": "integer", + "format": "int32", + "nullable": true, + "minimum": 0 + } + }, + { + "name": "created", + "in": "path", + "description": "The time at which payment is created", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.lt", + "in": "path", + "description": "Time less than the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.gt", + "in": "path", + "description": "Time greater than the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.lte", + "in": "path", + "description": "Time less than or equals to the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.gte", + "in": "path", + "description": "Time greater than or equals to the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "start_amount", + "in": "path", + "description": "The start amount to filter list of transactions which are greater than or equal to the start amount", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "end_amount", + "in": "path", + "description": "The end amount to filter list of transactions which are less than or equal to the end amount", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "connector", + "in": "path", + "description": "The connector to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Connector" + } + ], + "nullable": true + } + }, + { + "name": "currency", + "in": "path", + "description": "The currency to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Currency" + } + ], + "nullable": true + } + }, + { + "name": "status", + "in": "path", + "description": "The payment status to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/IntentStatus" + } + ], + "nullable": true + } + }, + { + "name": "payment_method_type", + "in": "path", + "description": "The payment method type to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethod" + } + ], + "nullable": true + } + }, + { + "name": "payment_method_subtype", + "in": "path", + "description": "The payment method subtype to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + } + }, + { + "name": "authentication_type", + "in": "path", + "description": "The authentication type to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationType" + } + ], + "nullable": true + } + }, + { + "name": "merchant_connector_id", + "in": "path", + "description": "The merchant connector id to filter payments list", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "order_on", + "in": "path", + "description": "The field on which the payments list should be sorted", + "required": true, + "schema": { + "$ref": "#/components/schemas/SortOn" + } + }, + { + "name": "order_by", + "in": "path", + "description": "The order in which payments list should be sorted", + "required": true, + "schema": { + "$ref": "#/components/schemas/SortBy" + } + }, + { + "name": "card_network", + "in": "path", + "description": "The card networks to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + } + }, + { + "name": "merchant_order_reference_id", + "in": "path", + "description": "The identifier for merchant order reference id", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved a payment list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentListResponse" + } + } + } + }, + "404": { + "description": "No payments found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "jwt_key": [] + } + ] + } + }, "/v2/payment-methods": { "post": { "tags": [ @@ -6967,6 +7302,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "ctp_mastercard", "cybersource", @@ -6992,6 +7328,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -7055,6 +7392,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -7931,15 +8279,7 @@ "type": "object", "description": "Details of customer attached to this payment", "properties": { - "id": { - "type": "string", - "description": "The identifier for the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "name": { + "name": { "type": "string", "description": "The customer's name", "example": "John Doe", @@ -12346,6 +12686,21 @@ } } }, + "Order": { + "type": "object", + "required": [ + "on", + "by" + ], + "properties": { + "on": { + "$ref": "#/components/schemas/SortOn" + }, + "by": { + "$ref": "#/components/schemas/SortBy" + } + } + }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -13042,6 +13397,19 @@ } } }, + "PaymentAttemptFeatureMetadata": { + "type": "object", + "properties": { + "revenue_recovery": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentAttemptRevenueRecoveryData" + } + ], + "nullable": true + } + } + }, "PaymentAttemptResponse": { "type": "object", "required": [ @@ -13050,7 +13418,8 @@ "amount", "authentication_type", "created_at", - "modified_at" + "modified_at", + "connector_payment_id" ], "properties": { "id": { @@ -13146,9 +13515,7 @@ }, "connector_payment_id": { "type": "string", - "description": "A unique identifier for a payment provided by the connector", - "example": "993672945374576J", - "nullable": true + "description": "A unique identifier for a payment provided by the connector" }, "payment_method_id": { "type": "string", @@ -13165,6 +13532,27 @@ "type": "string", "description": "Value passed in X-CLIENT-VERSION header during payments confirm request by the client", "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentAttemptFeatureMetadata" + } + ], + "nullable": true + } + } + }, + "PaymentAttemptRevenueRecoveryData": { + "type": "object", + "properties": { + "attempt_triggered_by": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggeredBy" + } + ], + "nullable": true } } }, @@ -13372,6 +13760,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -13462,6 +13860,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -13546,74 +13954,32 @@ } } }, - "PaymentListConstraints": { + "PaymentListResponse": { "type": "object", + "required": [ + "count", + "total_count", + "data" + ], "properties": { - "customer_id": { - "type": "string", - "description": "The identifier for customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "starting_after": { - "type": "string", - "description": "A cursor for use in pagination, fetch the next list after some object", - "example": "pay_fafa124123", - "nullable": true - }, - "ending_before": { - "type": "string", - "description": "A cursor for use in pagination, fetch the previous list before some object", - "example": "pay_fafa124123", - "nullable": true - }, - "limit": { + "count": { "type": "integer", - "format": "int32", - "description": "limit on the number of objects to return", - "default": 10, - "maximum": 100, + "description": "The number of payments included in the current response", "minimum": 0 }, - "created": { - "type": "string", - "format": "date-time", - "description": "The time at which payment is created", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.lt": { - "type": "string", - "format": "date-time", - "description": "Time less than the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.gt": { - "type": "string", - "format": "date-time", - "description": "Time greater than the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.lte": { - "type": "string", - "format": "date-time", - "description": "Time less than or equals to the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "total_count": { + "type": "integer", + "format": "int64", + "description": "The total number of available payments for given constraints" }, - "created.gte": { - "type": "string", - "format": "date-time", - "description": "Time greater than or equals to the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentsListResponseItem" + }, + "description": "The list of payments response objects" } - }, - "additionalProperties": false + } }, "PaymentMethod": { "type": "string", @@ -15663,116 +16029,327 @@ ], "nullable": true }, - "shipping": { + "shipping": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + }, + "customer_id": { + "type": "string", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "customer_present": { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + }, + "description": { + "type": "string", + "description": "A description for the payment", + "example": "It's my first payment request", + "nullable": true + }, + "return_url": { + "type": "string", + "description": "The URL to which you want the user to be redirected after the completion of the payment operation", + "example": "https://hyperswitch.io", + "nullable": true + }, + "setup_future_usage": { + "$ref": "#/components/schemas/FutureUsage" + }, + "apply_mit_exemption": { + "$ref": "#/components/schemas/MitExemptionRequest" + }, + "statement_descriptor": { + "type": "string", + "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", + "example": "Hyperswitch Router", + "nullable": true, + "maxLength": 22 + }, + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", + "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", + "nullable": true + }, + "allowed_payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true + }, + "connector_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorMetadata" + } + ], + "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/FeatureMetadata" + } + ], + "nullable": true + }, + "payment_link_enabled": { + "$ref": "#/components/schemas/EnablePaymentLinkRequest" + }, + "payment_link_config": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkConfigRequest" + } + ], + "nullable": true + }, + "request_incremental_authorization": { + "$ref": "#/components/schemas/RequestIncrementalAuthorization" + }, + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" + }, + "frm_metadata": { + "type": "object", + "description": "Additional data related to some frm(Fraud Risk Management) connectors", + "nullable": true + }, + "request_external_three_ds_authentication": { + "$ref": "#/components/schemas/External3dsAuthenticationRequest" + } + }, + "additionalProperties": false + }, + "PaymentsListResponseItem": { + "type": "object", + "required": [ + "id", + "merchant_id", + "profile_id", + "status", + "amount", + "created", + "attempt_count", + "return_url" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the payment", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "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_1668273825", + "maxLength": 255 + }, + "profile_id": { + "type": "string", + "description": "The business profile that is associated with this payment" + }, + "customer_id": { + "type": "string", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "nullable": true, + "maxLength": 64, + "minLength": 32 + }, + "payment_method_id": { + "type": "string", + "description": "Identifier for Payment Method used for the payment", + "nullable": true + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/IntentStatus" + } + ], + "default": "requires_confirmation" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAmountDetailsResponse" + }, + "created": { + "type": "string", + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" + }, + "payment_method_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethod" + } + ], + "nullable": true + }, + "payment_method_subtype": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + }, + "connector": { + "allOf": [ + { + "$ref": "#/components/schemas/Connector" + } + ], + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "nullable": true + }, + "customer": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerDetailsResponse" + } + ], + "nullable": true + }, + "merchant_reference_id": { + "type": "string", + "description": "The reference id for the order in the merchant's system. This value can be passed by the merchant.", + "nullable": true + }, + "connector_payment_id": { + "type": "string", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", + "nullable": true + }, + "connector_response_reference_id": { + "type": "string", + "description": "Reference to the capture at connector side", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true + }, + "description": { + "type": "string", + "description": "A description of the payment", + "example": "It's my first payment request", + "nullable": true + }, + "authentication_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationType" + } + ], + "default": "three_ds", + "nullable": true + }, + "capture_method": { + "allOf": [ + { + "$ref": "#/components/schemas/CaptureMethod" + } + ], + "nullable": true + }, + "setup_future_usage": { + "allOf": [ + { + "$ref": "#/components/schemas/FutureUsage" + } + ], + "nullable": true + }, + "attempt_count": { + "type": "integer", + "format": "int32", + "description": "Total number of attempts associated with this payment" + }, + "error": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/ErrorDetails" } ], "nullable": true }, - "customer_id": { + "cancellation_reason": { "type": "string", - "description": "The identifier for the customer", - "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", - "maxLength": 64, - "minLength": 32 - }, - "customer_present": { - "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + "description": "If the payment was cancelled the reason will be provided here", + "nullable": true }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", + "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", "nullable": true }, "return_url": { "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "$ref": "#/components/schemas/FutureUsage" - }, - "apply_mit_exemption": { - "$ref": "#/components/schemas/MitExemptionRequest" + "description": "The URL to redirect after the completion of the operation", + "example": "https://hyperswitch.io" }, "statement_descriptor": { "type": "string", "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", "example": "Hyperswitch Router", "nullable": true, - "maxLength": 22 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true + "maxLength": 255 }, "allowed_payment_method_types": { "type": "array", "items": { "$ref": "#/components/schemas/PaymentMethodType" }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "feature_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/FeatureMetadata" - } - ], + "description": "Allowed Payment Method Types for a given PaymentIntent", "nullable": true }, - "payment_link_enabled": { - "$ref": "#/components/schemas/EnablePaymentLinkRequest" - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentLinkConfigRequest" - } - ], + "authorization_count": { + "type": "integer", + "format": "int32", + "description": "Total number of authorizations happened in an incremental_authorization payment", "nullable": true }, - "request_incremental_authorization": { - "$ref": "#/components/schemas/RequestIncrementalAuthorization" - }, - "expires_on": { + "modified_at": { "type": "string", "format": "date-time", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", + "description": "Date time at which payment was updated", + "example": "2022-09-10T10:11:12Z", "nullable": true - }, - "request_external_three_ds_authentication": { - "$ref": "#/components/schemas/External3dsAuthenticationRequest" } - }, - "additionalProperties": false + } }, "PaymentsRequest": { "type": "object", @@ -19316,6 +19893,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", @@ -19339,6 +19917,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", @@ -19742,7 +20321,9 @@ "merchant", "amount", "protocol", - "allowed_brands" + "allowed_brands", + "billing_address_required", + "shipping_address_required" ], "properties": { "version": { @@ -19772,6 +20353,14 @@ "type": "string" }, "description": "List of supported card brands" + }, + "billing_address_required": { + "type": "boolean", + "description": "Is billing address required to be collected from wallet" + }, + "shipping_address_required": { + "type": "boolean", + "description": "Is shipping address required to be collected from wallet" } } }, @@ -20352,6 +20941,20 @@ "contain" ] }, + "SortBy": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SortOn": { + "type": "string", + "enum": [ + "amount", + "created" + ] + }, "SplitPaymentsRequest": { "oneOf": [ { @@ -20375,6 +20978,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -20402,6 +21016,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -20934,6 +21559,13 @@ "payout" ] }, + "TriggeredBy": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, "UIWidgetFormLayout": { "type": "string", "enum": [ @@ -21887,6 +22519,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index e9b50395320..8e62ba302ab 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9533,6 +9533,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "ctp_mastercard", "cybersource", @@ -9558,6 +9559,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -9621,6 +9623,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -16428,6 +16441,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -16518,6 +16541,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -18270,6 +18303,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -18663,6 +18702,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -19210,6 +19255,17 @@ "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", "nullable": true }, + "extended_authorization_applied": { + "type": "boolean", + "description": "flag that indicates if extended authorization is applied on this payment or not", + "nullable": true + }, + "capture_before": { + "type": "string", + "format": "date-time", + "description": "date and time after which this payment cannot be captured", + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -19888,6 +19944,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -20461,6 +20523,17 @@ "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", "nullable": true }, + "extended_authorization_applied": { + "type": "boolean", + "description": "flag that indicates if extended authorization is applied on this payment or not", + "nullable": true + }, + "capture_before": { + "type": "string", + "format": "date-time", + "description": "date and time after which this payment cannot be captured", + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -20952,6 +21025,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -22957,6 +23036,11 @@ "nullable": true, "minimum": 0 }, + "always_request_extended_authorization": { + "type": "boolean", + "description": "Bool indicating if extended authentication must be requested for all payments", + "nullable": true + }, "is_click_to_pay_enabled": { "type": "boolean", "description": "Indicates if click to pay is enabled or not." @@ -23196,6 +23280,11 @@ "description": "Maximum number of auto retries allowed for a payment", "nullable": true }, + "always_request_extended_authorization": { + "type": "boolean", + "description": "Bool indicating if extended authentication must be requested for all payments", + "nullable": true + }, "is_click_to_pay_enabled": { "type": "boolean", "description": "Indicates if click to pay is enabled or not.", @@ -24352,6 +24441,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", @@ -24375,6 +24465,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", @@ -24769,7 +24860,9 @@ "merchant", "amount", "protocol", - "allowed_brands" + "allowed_brands", + "billing_address_required", + "shipping_address_required" ], "properties": { "version": { @@ -24799,6 +24892,14 @@ "type": "string" }, "description": "List of supported card brands" + }, + "billing_address_required": { + "type": "boolean", + "description": "Is billing address required to be collected from wallet" + }, + "shipping_address_required": { + "type": "boolean", + "description": "Is shipping address required to be collected from wallet" } } }, @@ -25402,6 +25503,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -25429,6 +25541,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -26021,6 +26144,13 @@ "payout" ] }, + "TriggeredBy": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, "UIWidgetFormLayout": { "type": "string", "enum": [ @@ -26974,6 +27104,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/config/config.example.toml b/config/config.example.toml index 17b0d5b8c7b..3457d330c9a 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -196,7 +196,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 952cdff80e6..079134e6d03 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -42,7 +42,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index ac5a48007e1..79ed26906ef 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -46,7 +46,7 @@ cashtocode.base_url = "https://cluster14.api.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api.coingate.com/v2" +coingate.base_url = "https://api.coingate.com" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" @@ -78,7 +78,7 @@ klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" -moneris.base_url = "https://api.sb.moneris.io" +moneris.base_url = "https://api.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://api.payengine.de/v1" nexixpay.base_url = "https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 42ec46a6cad..a92deafb20a 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -46,7 +46,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" checkout.base_url = "https://api.sandbox.checkout.com/" chargebee.base_url = "https://$.chargebee.com/api/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/development.toml b/config/development.toml index 153c8a996de..13ed82c81f6 100644 --- a/config/development.toml +++ b/config/development.toml @@ -266,7 +266,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 48581fbea06..0031c37f022 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -128,7 +128,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index c159af97249..2b84c50bba9 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -22,6 +22,7 @@ customer_v2 = ["common_utils/customer_v2"] payment_methods_v2 = ["common_utils/payment_methods_v2"] dynamic_routing = [] control_center_theme = ["dep:actix-web", "dep:actix-multipart"] +revenue_recovery = [] [dependencies] actix-multipart = { version = "0.6.1", optional = true } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index c1312433947..77488262c75 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -8,7 +8,10 @@ use common_utils::{ id_type, link_utils, pii, }; #[cfg(feature = "v1")] -use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; +use common_utils::{ + crypto::OptionalEncryptableName, ext_traits::ValueExt, + types::AlwaysRequestExtendedAuthorization, +}; #[cfg(feature = "v2")] use masking::ExposeInterface; use masking::{PeekInterface, Secret}; @@ -1897,6 +1900,10 @@ pub struct ProfileCreate { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + /// Bool indicating if extended authentication must be requested for all payments + #[schema(value_type = Option)] + pub always_request_extended_authorization: Option, + /// Indicates if click to pay is enabled or not. #[serde(default)] pub is_click_to_pay_enabled: bool, @@ -2152,6 +2159,10 @@ pub struct ProfileResponse { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + /// Bool indicating if extended authentication must be requested for all payments + #[schema(value_type = Option)] + pub always_request_extended_authorization: Option, + /// Indicates if click to pay is enabled or not. #[schema(default = false, example = false)] pub is_click_to_pay_enabled: bool, @@ -2708,6 +2719,10 @@ pub struct PaymentLinkConfigRequest { pub details_layout: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] @@ -2779,6 +2794,10 @@ pub struct PaymentLinkConfig { pub branding_visibility: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 2124ff1aff9..9a2a0ca8396 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -2,6 +2,7 @@ pub mod apple_pay_certificates_migration; pub mod connector_onboarding; pub mod customer; pub mod dispute; +pub mod external_service_auth; pub mod gsm; mod locker_migration; pub mod payment; diff --git a/crates/api_models/src/events/external_service_auth.rs b/crates/api_models/src/events/external_service_auth.rs new file mode 100644 index 00000000000..31196150b2c --- /dev/null +++ b/crates/api_models/src/events/external_service_auth.rs @@ -0,0 +1,30 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::external_service_auth::{ + ExternalSignoutTokenRequest, ExternalTokenResponse, ExternalVerifyTokenRequest, + ExternalVerifyTokenResponse, +}; + +impl ApiEventMetric for ExternalTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalVerifyTokenRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalVerifyTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalSignoutTokenRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 0687df88f2a..eddc8ce5fce 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -11,7 +11,7 @@ use super::{ ))] use crate::payment_methods::CustomerPaymentMethodsListResponse; #[cfg(feature = "v1")] -use crate::payments::{PaymentListResponse, PaymentListResponseV2}; +use crate::payments::{PaymentListFilterConstraints, PaymentListResponseV2}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::{events, payment_methods::CustomerPaymentMethodsListResponse}; use crate::{ @@ -23,16 +23,15 @@ use crate::{ PaymentMethodUpdate, }, payments::{ - self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, - PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, - PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, - PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, - PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, - PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, - PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, - PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsResponse, - PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, + self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilters, + PaymentListFiltersV2, PaymentListResponse, PaymentsAggregateResponse, + PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, + PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, + PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, + PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, + PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, + PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, RedirectionResponse, }, }; @@ -359,6 +358,7 @@ impl ApiEventMetric for PaymentMethodCollectLinkResponse { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListFilterConstraints { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) @@ -382,7 +382,6 @@ impl ApiEventMetric for PaymentListConstraints { } } -#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) diff --git a/crates/api_models/src/external_service_auth.rs b/crates/api_models/src/external_service_auth.rs new file mode 100644 index 00000000000..775529c4359 --- /dev/null +++ b/crates/api_models/src/external_service_auth.rs @@ -0,0 +1,35 @@ +use common_utils::{id_type, pii}; +use masking::Secret; + +#[derive(Debug, serde::Serialize)] +pub struct ExternalTokenResponse { + pub token: Secret, +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ExternalVerifyTokenRequest { + pub token: Secret, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ExternalSignoutTokenRequest { + pub token: Secret, +} + +#[derive(serde::Serialize, Debug)] +#[serde(untagged)] +pub enum ExternalVerifyTokenResponse { + Hypersense { + user_id: String, + merchant_id: id_type::MerchantId, + name: Secret, + email: pii::Email, + }, +} + +impl ExternalVerifyTokenResponse { + pub fn get_user_id(&self) -> &str { + match self { + Self::Hypersense { user_id, .. } => user_id, + } + } +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 60d1d11f27d..c3cf1f1d25a 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -16,6 +16,7 @@ pub mod ephemeral_key; #[cfg(feature = "errors")] pub mod errors; pub mod events; +pub mod external_service_auth; pub mod feature_matrix; pub mod files; pub mod gsm; @@ -41,3 +42,12 @@ pub mod verifications; pub mod verify_connector; pub mod webhook_events; pub mod webhooks; + +pub trait ValidateFieldAndGet { + fn validate_field_and_get( + &self, + request: &Request, + ) -> common_utils::errors::CustomResult + where + Self: Sized; +} diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 6e21dc7ed1d..5a9bc55a232 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4,12 +4,11 @@ use std::{ num::NonZeroI64, }; pub mod additional_info; +pub mod trait_impls; use cards::CardNumber; #[cfg(feature = "v2")] use common_enums::enums::PaymentConnectorTransmission; use common_enums::ProductType; -#[cfg(feature = "v2")] -use common_utils::id_type::GlobalPaymentId; use common_utils::{ consts::default_payments_list_limit, crypto, @@ -18,7 +17,10 @@ use common_utils::{ hashing::HashedString, id_type, pii::{self, Email}, - types::{MinorUnit, StringMajorUnit}, + types::{ + ExtendedAuthorizationAppliedBool, MinorUnit, RequestExtendedAuthorizationBool, + StringMajorUnit, + }, }; use error_stack::ResultExt; use masking::{PeekInterface, Secret, WithType}; @@ -39,7 +41,7 @@ use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, mandates::RecurringDetails, - refunds, + refunds, ValidateFieldAndGet, }; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -99,6 +101,7 @@ pub struct CustomerDetails { pub phone_country_code: Option, } +#[cfg(feature = "v1")] /// Details of customer attached to this payment #[derive( Debug, Default, serde::Serialize, serde::Deserialize, Clone, ToSchema, PartialEq, Setter, @@ -125,6 +128,27 @@ pub struct CustomerDetailsResponse { pub phone_country_code: Option, } +#[cfg(feature = "v2")] +/// Details of customer attached to this payment +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema, PartialEq, Setter)] +pub struct CustomerDetailsResponse { + /// The customer's name + #[schema(max_length = 255, value_type = Option, example = "John Doe")] + pub name: Option>, + + /// The customer's email address + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + pub email: Option, + + /// The customer's phone number + #[schema(value_type = Option, max_length = 10, example = "9123456789")] + pub phone: Option>, + + /// The country code for the customer's phone number + #[schema(max_length = 2, example = "+1")] + pub phone_country_code: Option, +} + // Serialize is required because the api event requires Serialize to be implemented #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] @@ -1044,6 +1068,12 @@ pub struct PaymentsRequest { #[schema(value_type = Option)] pub split_payments: Option, + /// Optional boolean value to extent authorization period of this payment + /// + /// capture method must be manual or manual_multiple + #[schema(value_type = Option, default = false)] + pub request_extended_authorization: Option, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. @@ -1103,6 +1133,18 @@ impl PaymentsRequest { .or(self.customer.as_ref().map(|customer| &customer.id)) } + pub fn validate_and_get_request_extended_authorization( + &self, + ) -> common_utils::errors::CustomResult, ValidationError> + { + self.request_extended_authorization + .as_ref() + .map(|request_extended_authorization| { + request_extended_authorization.validate_field_and_get(self) + }) + .transpose() + } + /// Checks if the customer details are passed in both places /// If they are passed in both places, check for both the values to be equal /// Or else, return the field which has inconsistent data @@ -1471,8 +1513,8 @@ pub struct PaymentAttemptResponse { pub payment_method_subtype: Option, /// A unique identifier for a payment provided by the connector - #[schema(value_type = Option, example = "993672945374576J")] - pub connector_payment_id: Option, + #[schema(value_type = String)] + pub connector_payment_id: Option, /// Identifier for Payment Method used for the payment attempt #[schema(value_type = Option, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] @@ -1482,6 +1524,24 @@ pub struct PaymentAttemptResponse { pub client_source: Option, /// Value passed in X-CLIENT-VERSION header during payments confirm request by the client pub client_version: Option, + + /// Additional data that might be required by hyperswitch, to enable some specific features. + pub feature_metadata: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentAttemptFeatureMetadata { + /// Revenue recovery metadata that might be required by hyperswitch. + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentAttemptRevenueRecoveryData { + /// Flag to find out whether an attempt was created by external or internal system. + #[schema(value_type = Option, example = "internal")] + pub attempt_triggered_by: common_enums::TriggeredBy, } #[derive( @@ -4844,6 +4904,14 @@ pub struct PaymentsResponse { #[schema(value_type = Option, example = r#"{ "fulfillment_method" : "deliver", "coverage_request" : "fraud" }"#)] pub frm_metadata: Option, + /// flag that indicates if extended authorization is applied on this payment or not + #[schema(value_type = Option)] + pub extended_authorization_applied: Option, + + /// date and time after which this payment cannot be captured + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub capture_before: Option, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. @@ -4864,6 +4932,139 @@ pub struct PaymentsResponse { pub card_discovery: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct PaymentsListResponseItem { + /// Unique identifier for the payment + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + /// This is an identifier for the merchant account. This is inferred from the API key + /// provided during the request + #[schema(max_length = 255, example = "merchant_1668273825", value_type = String)] + pub merchant_id: id_type::MerchantId, + + /// The business profile that is associated with this payment + #[schema(value_type = String)] + pub profile_id: id_type::ProfileId, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = Option + )] + pub customer_id: Option, + + /// Identifier for Payment Method used for the payment + #[schema(value_type = Option)] + pub payment_method_id: Option, + + /// Status of the payment + #[schema(value_type = IntentStatus, example = "failed", default = "requires_confirmation")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method type for this payment attempt + #[schema(value_type = Option, example = "wallet")] + pub payment_method_type: Option, + + #[schema(value_type = Option, example = "apple_pay")] + pub payment_method_subtype: Option, + + /// The connector used for the payment + #[schema(value_type = Option, example = "stripe")] + pub connector: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = Option)] + pub merchant_connector_id: Option, + + /// Details of the customer + pub customer: Option, + + /// The reference id for the order in the merchant's system. This value can be passed by the merchant. + #[schema(value_type = Option)] + pub merchant_reference_id: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_payment_id: Option, + + /// Reference to the capture at connector side + pub connector_response_reference_id: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] + pub metadata: Option>, + + /// A description of the payment + #[schema(example = "It's my first payment request")] + pub description: Option, + + /// The transaction authentication can be set to undergo payer authentication. By default, the authentication will be marked as NO_THREE_DS + #[schema(value_type = Option, example = "no_three_ds", default = "three_ds")] + pub authentication_type: Option, + + /// This is the instruction for capture/ debit the money from the users' card. On the other hand authorization refers to blocking the amount on the users' payment method. + #[schema(value_type = Option, example = "automatic")] + pub capture_method: Option, + + /// Indicates that you intend to make future payments with this Payment’s payment method. Providing this parameter will attach the payment method to the Customer, if present, after the Payment is confirmed and any required actions from the user are complete. + #[schema(value_type = Option, example = "off_session")] + pub setup_future_usage: Option, + + /// Total number of attempts associated with this payment + pub attempt_count: i16, + + /// Error details for the payment if any + pub error: Option, + + /// If the payment was cancelled the reason will be provided here + pub cancellation_reason: Option, + + /// Information about the product , quantity and amount for connectors. (e.g. Klarna) + #[schema(value_type = Option>, example = r#"[{ + "product_name": "gillete creme", + "quantity": 15, + "amount" : 900 + }]"#)] + pub order_details: Option>>, + + /// The URL to redirect after the completion of the operation + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub return_url: Option, + + /// For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters. + #[schema(value_type = Option, max_length = 255, example = "Hyperswitch Router")] + pub statement_descriptor: Option, + + /// Allowed Payment Method Types for a given PaymentIntent + #[schema(value_type = Option>)] + pub allowed_payment_method_types: Option>, + + /// Total number of authorizations happened in an incremental_authorization payment + pub authorization_count: Option, + + /// Date time at which payment was updated + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub modified_at: Option, +} + // Serialize is implemented because, this will be serialized in the api events. // Usually request types should not have serialize implemented. // @@ -4903,7 +5104,7 @@ pub struct PaymentsConfirmIntentRequest { #[cfg(feature = "v2")] #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(deny_unknown_fields)] -pub struct ProxyPaymentsIntentRequest { +pub struct ProxyPaymentsRequest { /// The URL to which you want the user to be redirected after the completion of the payment operation /// If this url is not passed, the url configured in the business profile will be used #[schema(value_type = Option, example = "https://hyperswitch.io")] @@ -5475,6 +5676,26 @@ pub struct PaymentsRetrieveResponse { pub attempts: Option>, } +#[cfg(feature = "v2")] +impl PaymentsRetrieveResponse { + pub fn find_attempt_in_attempts_list_using_connector_transaction_id( + self, + connector_transaction_id: &common_utils::types::ConnectorTransactionId, + ) -> Option { + self.attempts + .as_ref() + .and_then(|attempts| { + attempts.iter().find(|attempt| { + attempt + .connector_payment_id + .as_ref() + .is_some_and(|txn_id| txn_id == connector_transaction_id) + }) + }) + .cloned() + } +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[cfg(feature = "v2")] pub struct PaymentStartRedirectionRequest { @@ -5512,6 +5733,7 @@ pub struct ExternalAuthenticationDetailsResponse { pub error_message: Option, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] #[serde(deny_unknown_fields)] pub struct PaymentListConstraints { @@ -5576,6 +5798,131 @@ pub struct PaymentListConstraints { pub created_gte: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, utoipa::IntoParams)] +#[serde(deny_unknown_fields)] +pub struct PaymentListConstraints { + /// The identifier for payment + #[param(example = "pay_fafa124123", value_type = Option)] + pub payment_id: Option, + + /// The identifier for business profile + #[param(example = "pay_fafa124123", value_type = Option)] + pub profile_id: Option, + + /// The identifier for customer + #[param( + max_length = 64, + min_length = 1, + example = "cus_y3oqhf46pyzuxjbcn2giaqnb44", + value_type = Option, + )] + pub customer_id: Option, + + /// A cursor for use in pagination, fetch the next list after some object + #[param(example = "pay_fafa124123", value_type = Option)] + pub starting_after: Option, + + /// A cursor for use in pagination, fetch the previous list before some object + #[param(example = "pay_fafa124123", value_type = Option)] + pub ending_before: Option, + + /// limit on the number of objects to return + #[param(default = 10, maximum = 100)] + #[serde(default = "default_payments_list_limit")] + pub limit: u32, + + /// The starting point within a list of objects + pub offset: Option, + + /// The time at which payment is created + #[param(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub created: Option, + + /// Time less than the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.lt" + )] + pub created_lt: Option, + + /// Time greater than the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.gt" + )] + pub created_gt: Option, + + /// Time less than or equals to the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.lte" + )] + pub created_lte: Option, + + /// Time greater than or equals to the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(rename = "created.gte")] + pub created_gte: Option, + + /// The start amount to filter list of transactions which are greater than or equal to the start amount + pub start_amount: Option, + /// The end amount to filter list of transactions which are less than or equal to the end amount + pub end_amount: Option, + /// The connector to filter payments list + #[param(value_type = Option)] + pub connector: Option, + /// The currency to filter payments list + #[param(value_type = Option)] + pub currency: Option, + /// The payment status to filter payments list + #[param(value_type = Option)] + pub status: Option, + /// The payment method type to filter payments list + #[param(value_type = Option)] + pub payment_method_type: Option, + /// The payment method subtype to filter payments list + #[param(value_type = Option)] + pub payment_method_subtype: Option, + /// The authentication type to filter payments list + #[param(value_type = Option)] + pub authentication_type: Option, + /// The merchant connector id to filter payments list + #[param(value_type = Option)] + pub merchant_connector_id: Option, + /// The field on which the payments list should be sorted + #[serde(default)] + pub order_on: SortOn, + /// The order in which payments list should be sorted + #[serde(default)] + pub order_by: SortBy, + /// The card networks to filter payments list + #[param(value_type = Option)] + pub card_network: Option, + /// The identifier for merchant order reference id + pub merchant_order_reference_id: Option, +} + +#[cfg(feature = "v2")] +impl PaymentListConstraints { + pub fn has_no_attempt_filters(&self) -> bool { + self.connector.is_none() + && self.payment_method_type.is_none() + && self.payment_method_subtype.is_none() + && self.authentication_type.is_none() + && self.merchant_connector_id.is_none() + && self.card_network.is_none() + } +} + #[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Serialize, ToSchema)] pub struct PaymentListResponse { @@ -5585,6 +5932,16 @@ pub struct PaymentListResponse { pub data: Vec, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct PaymentListResponse { + /// The number of payments included in the current response + pub count: usize, + /// The total number of available payments for given constraints + pub total_count: i64, + /// The list of payments response objects + pub data: Vec, +} #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct IncrementalAuthorizationResponse { /// The unique identifier of authorization @@ -5614,6 +5971,7 @@ pub struct PaymentListResponseV2 { pub data: Vec, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct PaymentListFilterConstraints { /// The identifier for payment @@ -5657,6 +6015,7 @@ pub struct PaymentListFilterConstraints { pub card_discovery: Option>, } +#[cfg(feature = "v1")] impl PaymentListFilterConstraints { pub fn has_no_attempt_filters(&self) -> bool { self.connector.is_none() @@ -6635,6 +6994,10 @@ pub struct SamsungPaySessionTokenResponse { pub protocol: SamsungPayProtocolType, /// List of supported card brands pub allowed_brands: Vec, + /// Is billing address required to be collected from wallet + pub billing_address_required: bool, + /// Is shipping address required to be collected from wallet + pub shipping_address_required: bool, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] @@ -7626,6 +7989,8 @@ pub struct PaymentLinkDetails { pub details_layout: Option, pub branding_visibility: Option, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Debug, serde::Serialize, Clone)] @@ -7636,6 +8001,8 @@ pub struct SecurePaymentLinkDetails { #[serde(flatten)] pub payment_link_details: PaymentLinkDetails, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/payments/trait_impls.rs b/crates/api_models/src/payments/trait_impls.rs new file mode 100644 index 00000000000..5df07a24a98 --- /dev/null +++ b/crates/api_models/src/payments/trait_impls.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "v1")] +use common_enums::enums; +#[cfg(feature = "v1")] +use common_utils::errors; + +#[cfg(feature = "v1")] +use crate::payments; + +#[cfg(feature = "v1")] +impl crate::ValidateFieldAndGet + for common_utils::types::RequestExtendedAuthorizationBool +{ + fn validate_field_and_get( + &self, + request: &payments::PaymentsRequest, + ) -> errors::CustomResult + where + Self: Sized, + { + match request.capture_method{ + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::Scheduled) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None => Err(error_stack::report!(errors::ValidationError::InvalidValue { message: "request_extended_authorization must be sent only if capture method is manual or manual_multiple".to_string() })), + Some(enums::CaptureMethod::Manual) + | Some(enums::CaptureMethod::ManualMultiple) => Ok(*self) + } + } +} diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index ddefea542c2..c3e96c47c64 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -57,6 +57,14 @@ pub enum IncomingWebhookEvent { PayoutExpired, #[cfg(feature = "payouts")] PayoutReversed, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentFailure, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentSuccess, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentPending, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryInvoiceCancel, } pub enum WebhookFlow { @@ -71,6 +79,8 @@ pub enum WebhookFlow { Mandate, ExternalAuthentication, FraudCheck, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + Recovery, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -197,6 +207,11 @@ impl From for WebhookFlow { | IncomingWebhookEvent::PayoutCreated | IncomingWebhookEvent::PayoutExpired | IncomingWebhookEvent::PayoutReversed => Self::Payout, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + IncomingWebhookEvent::RecoveryInvoiceCancel + | IncomingWebhookEvent::RecoveryPaymentFailure + | IncomingWebhookEvent::RecoveryPaymentPending + | IncomingWebhookEvent::RecoveryPaymentSuccess => Self::Recovery, } } } @@ -236,6 +251,14 @@ pub enum ObjectReferenceId { ExternalAuthenticationID(AuthenticationIdType), #[cfg(feature = "payouts")] PayoutId(PayoutIdType), + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + InvoiceId(InvoiceIdType), +} + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +#[derive(Clone)] +pub enum InvoiceIdType { + ConnectorInvoiceId(String), } pub struct IncomingWebhookDetails { @@ -303,3 +326,15 @@ pub struct ConnectorWebhookSecrets { pub secret: Vec, pub additional_secret: Option>, } + +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +impl IncomingWebhookEvent { + pub fn is_recovery_transaction_event(&self) -> bool { + matches!( + self, + Self::RecoveryPaymentFailure + | Self::RecoveryPaymentSuccess + | Self::RecoveryPaymentPending + ) + } +} diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index ce7335ddeae..c52bb6816d7 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -68,7 +68,7 @@ pub enum RoutableConnectors { // Chargebee, Checkout, Coinbase, - // Coingate, + Coingate, Cryptopay, Cybersource, Datatrans, @@ -93,7 +93,7 @@ pub enum RoutableConnectors { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Nexinets, Nexixpay, @@ -206,6 +206,7 @@ pub enum Connector { // Chargebee, Checkout, Coinbase, + Coingate, Cryptopay, CtpMastercard, Cybersource, @@ -232,7 +233,7 @@ pub enum Connector { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Netcetera, Nexinets, @@ -317,6 +318,7 @@ impl Connector { | (Self::Deutschebank, _) | (Self::Globalpay, _) | (Self::Jpmorgan, _) + | (Self::Moneris, _) | (Self::Paypal, _) | (Self::Payu, _) | (Self::Trustpay, PaymentMethod::BankRedirect) @@ -359,6 +361,7 @@ impl Connector { | Self::Cashtocode // | Self::Chargebee | Self::Coinbase + |Self::Coingate | Self::Cryptopay | Self::Deutschebank | Self::Digitalvirgo @@ -382,7 +385,7 @@ impl Connector { | Self::Klarna | Self::Mifinity | Self::Mollie - // | Self::Moneris + | Self::Moneris | Self::Multisafepay | Self::Nexinets | Self::Nexixpay @@ -511,6 +514,7 @@ impl From for Connector { RoutableConnectors::Klarna => Self::Klarna, RoutableConnectors::Mifinity => Self::Mifinity, RoutableConnectors::Mollie => Self::Mollie, + RoutableConnectors::Moneris => Self::Moneris, RoutableConnectors::Multisafepay => Self::Multisafepay, RoutableConnectors::Nexinets => Self::Nexinets, RoutableConnectors::Nexixpay => Self::Nexixpay, @@ -547,6 +551,7 @@ impl From for Connector { RoutableConnectors::Zsl => Self::Zsl, RoutableConnectors::Xendit => Self::Xendit, RoutableConnectors::Inespay => Self::Inespay, + RoutableConnectors::Coingate => Self::Coingate, } } } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index b8efe58b4e2..660474e91ee 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1651,6 +1651,12 @@ pub enum PaymentMethodType { DirectCarrierBilling, } +impl PaymentMethodType { + pub fn should_check_for_customer_saved_payment_method_type(self) -> bool { + matches!(self, Self::ApplePay | Self::GooglePay | Self::SamsungPay) + } +} + impl masking::SerializableSecret for PaymentMethodType {} /// Indicates the type of payment method. Eg: 'card', 'wallet', etc. @@ -2943,128 +2949,274 @@ pub enum FinlandStatesAbbreviation { Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum FranceStatesAbbreviation { - #[strum(serialize = "WF-AL")] - Alo, - #[strum(serialize = "A")] + #[strum(serialize = "01")] + Ain, + #[strum(serialize = "02")] + Aisne, + #[strum(serialize = "03")] + Allier, + #[strum(serialize = "04")] + AlpesDeHauteProvence, + #[strum(serialize = "06")] + AlpesMaritimes, + #[strum(serialize = "6AE")] Alsace, - #[strum(serialize = "B")] - Aquitaine, - #[strum(serialize = "C")] - Auvergne, + #[strum(serialize = "07")] + Ardeche, + #[strum(serialize = "08")] + Ardennes, + #[strum(serialize = "09")] + Ariege, + #[strum(serialize = "10")] + Aube, + #[strum(serialize = "11")] + Aude, #[strum(serialize = "ARA")] AuvergneRhoneAlpes, + #[strum(serialize = "12")] + Aveyron, + #[strum(serialize = "67")] + BasRhin, + #[strum(serialize = "13")] + BouchesDuRhone, #[strum(serialize = "BFC")] BourgogneFrancheComte, #[strum(serialize = "BRE")] - Brittany, - #[strum(serialize = "D")] - Burgundy, + Bretagne, + #[strum(serialize = "14")] + Calvados, + #[strum(serialize = "15")] + Cantal, #[strum(serialize = "CVL")] CentreValDeLoire, - #[strum(serialize = "G")] - ChampagneArdenne, - #[strum(serialize = "COR")] - Corsica, - #[strum(serialize = "I")] - FrancheComte, - #[strum(serialize = "GF")] + #[strum(serialize = "16")] + Charente, + #[strum(serialize = "17")] + CharenteMaritime, + #[strum(serialize = "18")] + Cher, + #[strum(serialize = "CP")] + Clipperton, + #[strum(serialize = "19")] + Correze, + #[strum(serialize = "20R")] + Corse, + #[strum(serialize = "2A")] + CorseDuSud, + #[strum(serialize = "21")] + CoteDor, + #[strum(serialize = "22")] + CotesDarmor, + #[strum(serialize = "23")] + Creuse, + #[strum(serialize = "79")] + DeuxSevres, + #[strum(serialize = "24")] + Dordogne, + #[strum(serialize = "25")] + Doubs, + #[strum(serialize = "26")] + Drome, + #[strum(serialize = "91")] + Essonne, + #[strum(serialize = "27")] + Eure, + #[strum(serialize = "28")] + EureEtLoir, + #[strum(serialize = "29")] + Finistere, + #[strum(serialize = "973")] FrenchGuiana, #[strum(serialize = "PF")] FrenchPolynesia, + #[strum(serialize = "TF")] + FrenchSouthernAndAntarcticLands, + #[strum(serialize = "30")] + Gard, + #[strum(serialize = "32")] + Gers, + #[strum(serialize = "33")] + Gironde, #[strum(serialize = "GES")] GrandEst, - #[strum(serialize = "GP")] + #[strum(serialize = "971")] Guadeloupe, + #[strum(serialize = "68")] + HautRhin, + #[strum(serialize = "2B")] + HauteCorse, + #[strum(serialize = "31")] + HauteGaronne, + #[strum(serialize = "43")] + HauteLoire, + #[strum(serialize = "52")] + HauteMarne, + #[strum(serialize = "70")] + HauteSaone, + #[strum(serialize = "74")] + HauteSavoie, + #[strum(serialize = "87")] + HauteVienne, + #[strum(serialize = "05")] + HautesAlpes, + #[strum(serialize = "65")] + HautesPyrenees, #[strum(serialize = "HDF")] HautsDeFrance, - #[strum(serialize = "K")] - LanguedocRoussillon, - #[strum(serialize = "L")] - Limousin, - #[strum(serialize = "M")] - Lorraine, - #[strum(serialize = "P")] - LowerNormandy, - #[strum(serialize = "MQ")] + #[strum(serialize = "92")] + HautsDeSeine, + #[strum(serialize = "34")] + Herault, + #[strum(serialize = "IDF")] + IleDeFrance, + #[strum(serialize = "35")] + IlleEtVilaine, + #[strum(serialize = "36")] + Indre, + #[strum(serialize = "37")] + IndreEtLoire, + #[strum(serialize = "38")] + Isere, + #[strum(serialize = "39")] + Jura, + #[strum(serialize = "974")] + LaReunion, + #[strum(serialize = "40")] + Landes, + #[strum(serialize = "41")] + LoirEtCher, + #[strum(serialize = "42")] + Loire, + #[strum(serialize = "44")] + LoireAtlantique, + #[strum(serialize = "45")] + Loiret, + #[strum(serialize = "46")] + Lot, + #[strum(serialize = "47")] + LotEtGaronne, + #[strum(serialize = "48")] + Lozere, + #[strum(serialize = "49")] + MaineEtLoire, + #[strum(serialize = "50")] + Manche, + #[strum(serialize = "51")] + Marne, + #[strum(serialize = "972")] Martinique, - #[strum(serialize = "YT")] + #[strum(serialize = "53")] + Mayenne, + #[strum(serialize = "976")] Mayotte, - #[strum(serialize = "O")] - NordPasDeCalais, + #[strum(serialize = "69M")] + MetropoleDeLyon, + #[strum(serialize = "54")] + MeurtheEtMoselle, + #[strum(serialize = "55")] + Meuse, + #[strum(serialize = "56")] + Morbihan, + #[strum(serialize = "57")] + Moselle, + #[strum(serialize = "58")] + Nievre, + #[strum(serialize = "59")] + Nord, #[strum(serialize = "NOR")] - Normandy, + Normandie, #[strum(serialize = "NAQ")] NouvelleAquitaine, #[strum(serialize = "OCC")] - Occitania, - #[strum(serialize = "75")] + Occitanie, + #[strum(serialize = "60")] + Oise, + #[strum(serialize = "61")] + Orne, + #[strum(serialize = "75C")] Paris, + #[strum(serialize = "62")] + PasDeCalais, #[strum(serialize = "PDL")] PaysDeLaLoire, - #[strum(serialize = "S")] - Picardy, - #[strum(serialize = "T")] - PoitouCharentes, #[strum(serialize = "PAC")] - ProvenceAlpesCoteDAzur, - #[strum(serialize = "V")] - RhoneAlpes, - #[strum(serialize = "RE")] - Reunion, + ProvenceAlpesCoteDazur, + #[strum(serialize = "63")] + PuyDeDome, + #[strum(serialize = "64")] + PyreneesAtlantiques, + #[strum(serialize = "66")] + PyreneesOrientales, + #[strum(serialize = "69")] + Rhone, + #[strum(serialize = "PM")] + SaintPierreAndMiquelon, #[strum(serialize = "BL")] SaintBarthelemy, #[strum(serialize = "MF")] SaintMartin, - #[strum(serialize = "PM")] - SaintPierreAndMiquelon, - #[strum(serialize = "WF-SG")] - Sigave, - #[strum(serialize = "Q")] - UpperNormandy, - #[strum(serialize = "WF-UV")] - Uvea, + #[strum(serialize = "71")] + SaoneEtLoire, + #[strum(serialize = "72")] + Sarthe, + #[strum(serialize = "73")] + Savoie, + #[strum(serialize = "77")] + SeineEtMarne, + #[strum(serialize = "76")] + SeineMaritime, + #[strum(serialize = "93")] + SeineSaintDenis, + #[strum(serialize = "80")] + Somme, + #[strum(serialize = "81")] + Tarn, + #[strum(serialize = "82")] + TarnEtGaronne, + #[strum(serialize = "90")] + TerritoireDeBelfort, + #[strum(serialize = "95")] + ValDoise, + #[strum(serialize = "94")] + ValDeMarne, + #[strum(serialize = "83")] + Var, + #[strum(serialize = "84")] + Vaucluse, + #[strum(serialize = "85")] + Vendee, + #[strum(serialize = "86")] + Vienne, + #[strum(serialize = "88")] + Vosges, #[strum(serialize = "WF")] WallisAndFutuna, - #[strum(serialize = "IDF")] - IleDeFrance, + #[strum(serialize = "89")] + Yonne, + #[strum(serialize = "78")] + Yvelines, } #[derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum GermanyStatesAbbreviation { - #[strum(serialize = "BW")] - BadenWurttemberg, - #[strum(serialize = "BY")] - Bavaria, - #[strum(serialize = "BE")] - Berlin, - #[strum(serialize = "BB")] - Brandenburg, - #[strum(serialize = "HB")] - Bremen, - #[strum(serialize = "HH")] - Hamburg, - #[strum(serialize = "HE")] - Hesse, - #[strum(serialize = "NI")] - LowerSaxony, - #[strum(serialize = "MV")] - MecklenburgVorpommern, - #[strum(serialize = "NW")] - NorthRhineWestphalia, - #[strum(serialize = "RP")] - RhinelandPalatinate, - #[strum(serialize = "SL")] - Saarland, - #[strum(serialize = "SN")] - Saxony, - #[strum(serialize = "ST")] - SaxonyAnhalt, - #[strum(serialize = "SH")] - SchleswigHolstein, - #[strum(serialize = "TH")] - Thuringia, + BW, + BY, + BE, + BB, + HB, + HH, + HE, + NI, + MV, + NW, + RP, + SL, + SN, + ST, + SH, + TH, } #[derive( @@ -3802,7 +3954,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "04")] Birkirkara, #[strum(serialize = "05")] - Birzebbuga, + Birżebbuġa, #[strum(serialize = "06")] Cospicua, #[strum(serialize = "07")] @@ -3816,19 +3968,19 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "11")] Gudja, #[strum(serialize = "12")] - Gzira, + Gżira, #[strum(serialize = "13")] - Ghajnsielem, + Għajnsielem, #[strum(serialize = "14")] - Gharb, + Għarb, #[strum(serialize = "15")] - Gharghur, + Għargħur, #[strum(serialize = "16")] - Ghasri, + Għasri, #[strum(serialize = "17")] - Ghaxaq, + Għaxaq, #[strum(serialize = "18")] - Hamrun, + Ħamrun, #[strum(serialize = "19")] Iklin, #[strum(serialize = "20")] @@ -3836,7 +3988,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "21")] Kalkara, #[strum(serialize = "22")] - Kercem, + Kerċem, #[strum(serialize = "23")] Kirkop, #[strum(serialize = "24")] @@ -3852,9 +4004,9 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "29")] Mdina, #[strum(serialize = "30")] - Mellieha, + Mellieħa, #[strum(serialize = "31")] - Mgarr, + Mġarr, #[strum(serialize = "32")] Mosta, #[strum(serialize = "33")] @@ -3874,7 +4026,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "40")] Pembroke, #[strum(serialize = "41")] - Pieta, + Pietà, #[strum(serialize = "42")] Qala, #[strum(serialize = "43")] @@ -3888,7 +4040,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "48")] StJulians, #[strum(serialize = "49")] - SanGwann, + SanĠwann, #[strum(serialize = "50")] SaintLawrence, #[strum(serialize = "51")] @@ -3896,11 +4048,11 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "52")] Sannat, #[strum(serialize = "53")] - SantaLucija, + SantaLuċija, #[strum(serialize = "54")] SantaVenera, #[strum(serialize = "55")] - Siggiewi, + Siġġiewi, #[strum(serialize = "56")] Sliema, #[strum(serialize = "57")] @@ -3912,21 +4064,21 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "60")] Valletta, #[strum(serialize = "61")] - Xaghra, + Xagħra, #[strum(serialize = "62")] Xewkija, #[strum(serialize = "63")] - Xghajra, + Xgħajra, #[strum(serialize = "64")] - Zabbar, + Żabbar, #[strum(serialize = "65")] - ZebbugGozo, + ŻebbuġGozo, #[strum(serialize = "66")] - ZebbugMalta, + ŻebbuġMalta, #[strum(serialize = "67")] - Zejtun, + Żejtun, #[strum(serialize = "68")] - Zurrieq, + Żurrieq, } #[derive( @@ -3942,69 +4094,69 @@ pub enum MoldovaStatesAbbreviation { #[strum(serialize = "BR")] BriceniDistrict, #[strum(serialize = "BA")] - BaltiMunicipality, + BălțiMunicipality, #[strum(serialize = "CA")] CahulDistrict, #[strum(serialize = "CT")] CantemirDistrict, #[strum(serialize = "CU")] - ChisinauMunicipality, + ChișinăuMunicipality, #[strum(serialize = "CM")] - CimisliaDistrict, + CimișliaDistrict, #[strum(serialize = "CR")] CriuleniDistrict, #[strum(serialize = "CL")] - CalarasiDistrict, + CălărașiDistrict, #[strum(serialize = "CS")] - CauseniDistrict, + CăușeniDistrict, #[strum(serialize = "DO")] - DonduseniDistrict, + DondușeniDistrict, #[strum(serialize = "DR")] DrochiaDistrict, #[strum(serialize = "DU")] - DubasariDistrict, + DubăsariDistrict, #[strum(serialize = "ED")] - EdinetDistrict, + EdinețDistrict, #[strum(serialize = "FL")] - FlorestiDistrict, + FloreștiDistrict, #[strum(serialize = "FA")] - FalestiDistrict, + FăleștiDistrict, #[strum(serialize = "GA")] - Gagauzia, + Găgăuzia, #[strum(serialize = "GL")] GlodeniDistrict, #[strum(serialize = "HI")] - HincestiDistrict, + HînceștiDistrict, #[strum(serialize = "IA")] IaloveniDistrict, #[strum(serialize = "NI")] NisporeniDistrict, #[strum(serialize = "OC")] - OcnitaDistrict, + OcnițaDistrict, #[strum(serialize = "OR")] OrheiDistrict, #[strum(serialize = "RE")] RezinaDistrict, #[strum(serialize = "RI")] - RiscaniDistrict, + RîșcaniDistrict, #[strum(serialize = "SO")] SorocaDistrict, #[strum(serialize = "ST")] - StraseniDistrict, + StrășeniDistrict, #[strum(serialize = "SI")] - SingereiDistrict, + SîngereiDistrict, #[strum(serialize = "TA")] TaracliaDistrict, #[strum(serialize = "TE")] - TelenestiDistrict, + TeleneștiDistrict, #[strum(serialize = "SN")] TransnistriaAutonomousTerritorialUnit, #[strum(serialize = "UN")] UngheniDistrict, #[strum(serialize = "SD")] - SoldanestiDistrict, + ȘoldăneștiDistrict, #[strum(serialize = "SV")] - StefanVodaDistrict, + ȘtefanVodăDistrict, } #[derive( @@ -4049,11 +4201,11 @@ pub enum MontenegroStatesAbbreviation { #[strum(serialize = "14")] PljevljaMunicipality, #[strum(serialize = "15")] - PluzineMunicipality, + PlužineMunicipality, #[strum(serialize = "16")] PodgoricaMunicipality, #[strum(serialize = "17")] - RozajeMunicipality, + RožajeMunicipality, #[strum(serialize = "19")] TivatMunicipality, #[strum(serialize = "20")] @@ -4061,7 +4213,7 @@ pub enum MontenegroStatesAbbreviation { #[strum(serialize = "18")] SavnikMunicipality, #[strum(serialize = "21")] - ZabljakMunicipality, + ŽabljakMunicipality, } #[derive( @@ -4211,7 +4363,7 @@ pub enum NorthMacedoniaStatesAbbreviation { #[strum(serialize = "62")] PrilepMunicipality, #[strum(serialize = "63")] - ProbistipMunicipality, + ProbishtipMunicipality, #[strum(serialize = "64")] RadovisMunicipality, #[strum(serialize = "65")] @@ -4269,7 +4421,7 @@ pub enum NorthMacedoniaStatesAbbreviation { #[strum(serialize = "83")] StipMunicipality, #[strum(serialize = "84")] - SutoOrizariMunicipality, + ShutoOrizariMunicipality, #[strum(serialize = "30")] ZelinoMunicipality, } @@ -4326,40 +4478,38 @@ pub enum NorwayStatesAbbreviation { Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum PolandStatesAbbreviation { - #[strum(serialize = "WP")] - GreaterPolandVoivodeship, - #[strum(serialize = "KI")] - Kielce, - #[strum(serialize = "KP")] - KuyavianPomeranianVoivodeship, - #[strum(serialize = "MA")] - LesserPolandVoivodeship, - #[strum(serialize = "DS")] - LowerSilesianVoivodeship, - #[strum(serialize = "LU")] - LublinVoivodeship, - #[strum(serialize = "LB")] - LubuszVoivodeship, - #[strum(serialize = "MZ")] - MasovianVoivodeship, - #[strum(serialize = "OP")] - OpoleVoivodeship, - #[strum(serialize = "PK")] - PodkarpackieVoivodeship, - #[strum(serialize = "PD")] - PodlaskieVoivodeship, - #[strum(serialize = "PM")] - PomeranianVoivodeship, - #[strum(serialize = "SL")] - SilesianVoivodeship, - #[strum(serialize = "WN")] - WarmianMasurianVoivodeship, - #[strum(serialize = "ZP")] - WestPomeranianVoivodeship, - #[strum(serialize = "LD")] - LodzVoivodeship, - #[strum(serialize = "SK")] - SwietokrzyskieVoivodeship, + #[strum(serialize = "30")] + GreaterPoland, + #[strum(serialize = "26")] + HolyCross, + #[strum(serialize = "04")] + KuyaviaPomerania, + #[strum(serialize = "12")] + LesserPoland, + #[strum(serialize = "02")] + LowerSilesia, + #[strum(serialize = "06")] + Lublin, + #[strum(serialize = "08")] + Lubusz, + #[strum(serialize = "10")] + Łódź, + #[strum(serialize = "14")] + Mazovia, + #[strum(serialize = "20")] + Podlaskie, + #[strum(serialize = "22")] + Pomerania, + #[strum(serialize = "24")] + Silesia, + #[strum(serialize = "18")] + Subcarpathia, + #[strum(serialize = "16")] + UpperSilesia, + #[strum(serialize = "28")] + WarmiaMasuria, + #[strum(serialize = "32")] + WestPomerania, } #[derive( @@ -4603,23 +4753,33 @@ pub enum SwitzerlandStatesAbbreviation { )] pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "ABE")] - AberdeenCity, + Aberdeen, #[strum(serialize = "ABD")] Aberdeenshire, #[strum(serialize = "ANS")] Angus, + #[strum(serialize = "ANT")] + Antrim, #[strum(serialize = "ANN")] AntrimAndNewtownabbey, + #[strum(serialize = "ARD")] + Ards, #[strum(serialize = "AND")] ArdsAndNorthDown, #[strum(serialize = "AGB")] ArgyllAndBute, + #[strum(serialize = "ARM")] + ArmaghCityAndDistrictCouncil, #[strum(serialize = "ABC")] - ArmaghCityBanbridgeAndCraigavon, - #[strum(serialize = "BDG")] - BarkingAndDagenham, - #[strum(serialize = "BNE")] - Barnet, + ArmaghBanbridgeAndCraigavon, + #[strum(serialize = "SH-AC")] + AscensionIsland, + #[strum(serialize = "BLA")] + BallymenaBorough, + #[strum(serialize = "BLY")] + Ballymoney, + #[strum(serialize = "BNB")] + Banbridge, #[strum(serialize = "BNS")] Barnsley, #[strum(serialize = "BAS")] @@ -4627,9 +4787,7 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "BDF")] Bedford, #[strum(serialize = "BFS")] - BelfastCity, - #[strum(serialize = "BEX")] - Bexley, + BelfastDistrict, #[strum(serialize = "BIR")] Birmingham, #[strum(serialize = "BBD")] @@ -4637,41 +4795,35 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "BPL")] Blackpool, #[strum(serialize = "BGW")] - BlaenauGwent, + BlaenauGwentCountyBorough, #[strum(serialize = "BOL")] Bolton, - #[strum(serialize = "BCP")] - BournemouthChristchurchAndPoole, + #[strum(serialize = "BMH")] + Bournemouth, #[strum(serialize = "BRC")] BracknellForest, #[strum(serialize = "BRD")] Bradford, - #[strum(serialize = "BEN")] - Brent, #[strum(serialize = "BGE")] - Bridgend, + BridgendCountyBorough, #[strum(serialize = "BNH")] BrightonAndHove, - #[strum(serialize = "BST")] - BristolCityOf, - #[strum(serialize = "BRY")] - Bromley, #[strum(serialize = "BKM")] Buckinghamshire, #[strum(serialize = "BUR")] Bury, #[strum(serialize = "CAY")] - Caerphilly, + CaerphillyCountyBorough, #[strum(serialize = "CLD")] Calderdale, #[strum(serialize = "CAM")] Cambridgeshire, - #[strum(serialize = "CMD")] - Camden, - #[strum(serialize = "CRF")] - Cardiff, #[strum(serialize = "CMN")] Carmarthenshire, + #[strum(serialize = "CKF")] + CarrickfergusBoroughCouncil, + #[strum(serialize = "CSR")] + Castlereagh, #[strum(serialize = "CCG")] CausewayCoastAndGlens, #[strum(serialize = "CBF")] @@ -4682,44 +4834,84 @@ pub enum UnitedKingdomStatesAbbreviation { CheshireEast, #[strum(serialize = "CHW")] CheshireWestAndChester, + #[strum(serialize = "CRF")] + CityAndCountyOfCardiff, + #[strum(serialize = "SWA")] + CityAndCountyOfSwansea, + #[strum(serialize = "BST")] + CityOfBristol, + #[strum(serialize = "DER")] + CityOfDerby, + #[strum(serialize = "KHL")] + CityOfKingstonUponHull, + #[strum(serialize = "LCE")] + CityOfLeicester, + #[strum(serialize = "LND")] + CityOfLondon, + #[strum(serialize = "NGM")] + CityOfNottingham, + #[strum(serialize = "PTE")] + CityOfPeterborough, + #[strum(serialize = "PLY")] + CityOfPlymouth, + #[strum(serialize = "POR")] + CityOfPortsmouth, + #[strum(serialize = "STH")] + CityOfSouthampton, + #[strum(serialize = "STE")] + CityOfStokeOnTrent, + #[strum(serialize = "SND")] + CityOfSunderland, + #[strum(serialize = "WSM")] + CityOfWestminster, + #[strum(serialize = "WLV")] + CityOfWolverhampton, + #[strum(serialize = "YOR")] + CityOfYork, #[strum(serialize = "CLK")] Clackmannanshire, + #[strum(serialize = "CLR")] + ColeraineBoroughCouncil, #[strum(serialize = "CWY")] - Conwy, + ConwyCountyBorough, + #[strum(serialize = "CKT")] + CookstownDistrictCouncil, #[strum(serialize = "CON")] Cornwall, + #[strum(serialize = "DUR")] + CountyDurham, #[strum(serialize = "COV")] Coventry, - #[strum(serialize = "CRY")] - Croydon, + #[strum(serialize = "CGV")] + CraigavonBoroughCouncil, #[strum(serialize = "CMA")] Cumbria, #[strum(serialize = "DAL")] Darlington, #[strum(serialize = "DEN")] Denbighshire, - #[strum(serialize = "DER")] - Derby, #[strum(serialize = "DBY")] Derbyshire, #[strum(serialize = "DRS")] - DerryAndStrabane, + DerryCityAndStrabane, + #[strum(serialize = "DRY")] + DerryCityCouncil, #[strum(serialize = "DEV")] Devon, #[strum(serialize = "DNC")] Doncaster, #[strum(serialize = "DOR")] Dorset, + #[strum(serialize = "DOW")] + DownDistrictCouncil, #[strum(serialize = "DUD")] Dudley, #[strum(serialize = "DGY")] DumfriesAndGalloway, #[strum(serialize = "DND")] - DundeeCity, - #[strum(serialize = "DUR")] - DurhamCounty, - #[strum(serialize = "EAL")] - Ealing, + Dundee, + #[strum(serialize = "DGN")] + DungannonAndSouthTyroneBoroughCouncil, #[strum(serialize = "EAY")] EastAyrshire, #[strum(serialize = "EDU")] @@ -4733,17 +4925,17 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "ESX")] EastSussex, #[strum(serialize = "EDH")] - EdinburghCityOf, - #[strum(serialize = "ELS")] - EileanSiar, - #[strum(serialize = "ENF")] - Enfield, + Edinburgh, + #[strum(serialize = "ENG")] + England, #[strum(serialize = "ESS")] Essex, #[strum(serialize = "FAL")] Falkirk, #[strum(serialize = "FMO")] FermanaghAndOmagh, + #[strum(serialize = "FER")] + FermanaghDistrictCouncil, #[strum(serialize = "FIF")] Fife, #[strum(serialize = "FLN")] @@ -4751,91 +4943,119 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "GAT")] Gateshead, #[strum(serialize = "GLG")] - GlasgowCity, + Glasgow, #[strum(serialize = "GLS")] Gloucestershire, - #[strum(serialize = "GRE")] - Greenwich, #[strum(serialize = "GWN")] Gwynedd, - #[strum(serialize = "HCK")] - Hackney, #[strum(serialize = "HAL")] Halton, - #[strum(serialize = "HMF")] - HammersmithAndFulham, #[strum(serialize = "HAM")] Hampshire, - #[strum(serialize = "HRY")] - Haringey, - #[strum(serialize = "HRW")] - Harrow, #[strum(serialize = "HPL")] Hartlepool, - #[strum(serialize = "HAV")] - Havering, #[strum(serialize = "HEF")] Herefordshire, #[strum(serialize = "HRT")] Hertfordshire, #[strum(serialize = "HLD")] Highland, - #[strum(serialize = "HIL")] - Hillingdon, - #[strum(serialize = "HNS")] - Hounslow, #[strum(serialize = "IVC")] Inverclyde, - #[strum(serialize = "AGY")] - IsleOfAnglesey, #[strum(serialize = "IOW")] IsleOfWight, #[strum(serialize = "IOS")] IslesOfScilly, - #[strum(serialize = "ISL")] - Islington, - #[strum(serialize = "KEC")] - KensingtonAndChelsea, #[strum(serialize = "KEN")] Kent, - #[strum(serialize = "KHL")] - KingstonUponHull, - #[strum(serialize = "KTT")] - KingstonUponThames, #[strum(serialize = "KIR")] Kirklees, #[strum(serialize = "KWL")] Knowsley, - #[strum(serialize = "LBH")] - Lambeth, #[strum(serialize = "LAN")] Lancashire, + #[strum(serialize = "LRN")] + LarneBoroughCouncil, #[strum(serialize = "LDS")] Leeds, - #[strum(serialize = "LCE")] - Leicester, #[strum(serialize = "LEC")] Leicestershire, - #[strum(serialize = "LEW")] - Lewisham, + #[strum(serialize = "LMV")] + LimavadyBoroughCouncil, #[strum(serialize = "LIN")] Lincolnshire, #[strum(serialize = "LBC")] LisburnAndCastlereagh, + #[strum(serialize = "LSB")] + LisburnCityCouncil, #[strum(serialize = "LIV")] Liverpool, - #[strum(serialize = "LND")] - LondonCityOf, - #[strum(serialize = "LUT")] - Luton, + #[strum(serialize = "BDG")] + LondonBoroughOfBarkingAndDagenham, + #[strum(serialize = "BNE")] + LondonBoroughOfBarnet, + #[strum(serialize = "BEX")] + LondonBoroughOfBexley, + #[strum(serialize = "BEN")] + LondonBoroughOfBrent, + #[strum(serialize = "BRY")] + LondonBoroughOfBromley, + #[strum(serialize = "CMD")] + LondonBoroughOfCamden, + #[strum(serialize = "CRY")] + LondonBoroughOfCroydon, + #[strum(serialize = "EAL")] + LondonBoroughOfEaling, + #[strum(serialize = "ENF")] + LondonBoroughOfEnfield, + #[strum(serialize = "HCK")] + LondonBoroughOfHackney, + #[strum(serialize = "HMF")] + LondonBoroughOfHammersmithAndFulham, + #[strum(serialize = "HRY")] + LondonBoroughOfHaringey, + #[strum(serialize = "HRW")] + LondonBoroughOfHarrow, + #[strum(serialize = "HAV")] + LondonBoroughOfHavering, + #[strum(serialize = "HIL")] + LondonBoroughOfHillingdon, + #[strum(serialize = "HNS")] + LondonBoroughOfHounslow, + #[strum(serialize = "ISL")] + LondonBoroughOfIslington, + #[strum(serialize = "LBH")] + LondonBoroughOfLambeth, + #[strum(serialize = "LEW")] + LondonBoroughOfLewisham, + #[strum(serialize = "MRT")] + LondonBoroughOfMerton, + #[strum(serialize = "NWM")] + LondonBoroughOfNewham, + #[strum(serialize = "RDB")] + LondonBoroughOfRedbridge, + #[strum(serialize = "RIC")] + LondonBoroughOfRichmondUponThames, + #[strum(serialize = "SWK")] + LondonBoroughOfSouthwark, + #[strum(serialize = "STN")] + LondonBoroughOfSutton, + #[strum(serialize = "TWH")] + LondonBoroughOfTowerHamlets, + #[strum(serialize = "WFT")] + LondonBoroughOfWalthamForest, + #[strum(serialize = "WND")] + LondonBoroughOfWandsworth, + #[strum(serialize = "MFT")] + MagherafeltDistrictCouncil, #[strum(serialize = "MAN")] Manchester, #[strum(serialize = "MDW")] Medway, #[strum(serialize = "MTY")] - MerthyrTydfil, - #[strum(serialize = "MRT")] - Merton, + MerthyrTydfilCountyBorough, + #[strum(serialize = "WGN")] + MetropolitanBoroughOfWigan, #[strum(serialize = "MEA")] MidAndEastAntrim, #[strum(serialize = "MUL")] @@ -4850,20 +5070,26 @@ pub enum UnitedKingdomStatesAbbreviation { Monmouthshire, #[strum(serialize = "MRY")] Moray, + #[strum(serialize = "MYL")] + MoyleDistrictCouncil, #[strum(serialize = "NTL")] - NeathPortTalbot, + NeathPortTalbotCountyBorough, #[strum(serialize = "NET")] NewcastleUponTyne, - #[strum(serialize = "NWM")] - Newham, #[strum(serialize = "NWP")] Newport, + #[strum(serialize = "NYM")] + NewryAndMourneDistrictCouncil, #[strum(serialize = "NMD")] NewryMourneAndDown, + #[strum(serialize = "NTA")] + NewtownabbeyBoroughCouncil, #[strum(serialize = "NFK")] Norfolk, #[strum(serialize = "NAY")] NorthAyrshire, + #[strum(serialize = "NDN")] + NorthDownBoroughCouncil, #[strum(serialize = "NEL")] NorthEastLincolnshire, #[strum(serialize = "NLK")] @@ -4878,52 +5104,58 @@ pub enum UnitedKingdomStatesAbbreviation { NorthYorkshire, #[strum(serialize = "NTH")] Northamptonshire, + #[strum(serialize = "NIR")] + NorthernIreland, #[strum(serialize = "NBL")] Northumberland, - #[strum(serialize = "NGM")] - Nottingham, #[strum(serialize = "NTT")] Nottinghamshire, #[strum(serialize = "OLD")] Oldham, + #[strum(serialize = "OMH")] + OmaghDistrictCouncil, #[strum(serialize = "ORK")] OrkneyIslands, + #[strum(serialize = "ELS")] + OuterHebrides, #[strum(serialize = "OXF")] Oxfordshire, #[strum(serialize = "PEM")] Pembrokeshire, #[strum(serialize = "PKN")] PerthAndKinross, - #[strum(serialize = "PTE")] - Peterborough, - #[strum(serialize = "PLY")] - Plymouth, - #[strum(serialize = "POR")] - Portsmouth, + #[strum(serialize = "POL")] + Poole, #[strum(serialize = "POW")] Powys, #[strum(serialize = "RDG")] Reading, - #[strum(serialize = "RDB")] - Redbridge, #[strum(serialize = "RCC")] RedcarAndCleveland, #[strum(serialize = "RFW")] Renfrewshire, #[strum(serialize = "RCT")] - RhonddaCynonTaff, - #[strum(serialize = "RIC")] - RichmondUponThames, + RhonddaCynonTaf, #[strum(serialize = "RCH")] Rochdale, #[strum(serialize = "ROT")] Rotherham, + #[strum(serialize = "GRE")] + RoyalBoroughOfGreenwich, + #[strum(serialize = "KEC")] + RoyalBoroughOfKensingtonAndChelsea, + #[strum(serialize = "KTT")] + RoyalBoroughOfKingstonUponThames, #[strum(serialize = "RUT")] Rutland, + #[strum(serialize = "SH-HL")] + SaintHelena, #[strum(serialize = "SLF")] Salford, #[strum(serialize = "SAW")] Sandwell, + #[strum(serialize = "SCT")] + Scotland, #[strum(serialize = "SCB")] ScottishBorders, #[strum(serialize = "SFT")] @@ -4948,12 +5180,8 @@ pub enum UnitedKingdomStatesAbbreviation { SouthLanarkshire, #[strum(serialize = "STY")] SouthTyneside, - #[strum(serialize = "STH")] - Southampton, #[strum(serialize = "SOS")] SouthendOnSea, - #[strum(serialize = "SWK")] - Southwark, #[strum(serialize = "SHN")] StHelens, #[strum(serialize = "STS")] @@ -4964,18 +5192,12 @@ pub enum UnitedKingdomStatesAbbreviation { Stockport, #[strum(serialize = "STT")] StocktonOnTees, - #[strum(serialize = "STE")] - StokeOnTrent, + #[strum(serialize = "STB")] + StrabaneDistrictCouncil, #[strum(serialize = "SFK")] Suffolk, - #[strum(serialize = "SND")] - Sunderland, #[strum(serialize = "SRY")] Surrey, - #[strum(serialize = "STN")] - Sutton, - #[strum(serialize = "SWA")] - Swansea, #[strum(serialize = "SWD")] Swindon, #[strum(serialize = "TAM")] @@ -4988,20 +5210,18 @@ pub enum UnitedKingdomStatesAbbreviation { Torbay, #[strum(serialize = "TOF")] Torfaen, - #[strum(serialize = "TWH")] - TowerHamlets, #[strum(serialize = "TRF")] Trafford, + #[strum(serialize = "UKM")] + UnitedKingdom, #[strum(serialize = "VGL")] ValeOfGlamorgan, #[strum(serialize = "WKF")] Wakefield, + #[strum(serialize = "WLS")] + Wales, #[strum(serialize = "WLL")] Walsall, - #[strum(serialize = "WFT")] - WalthamForest, - #[strum(serialize = "WND")] - Wandsworth, #[strum(serialize = "WRT")] Warrington, #[strum(serialize = "WAR")] @@ -5014,10 +5234,6 @@ pub enum UnitedKingdomStatesAbbreviation { WestLothian, #[strum(serialize = "WSX")] WestSussex, - #[strum(serialize = "WSM")] - Westminster, - #[strum(serialize = "WGN")] - Wigan, #[strum(serialize = "WIL")] Wiltshire, #[strum(serialize = "WNM")] @@ -5026,29 +5242,924 @@ pub enum UnitedKingdomStatesAbbreviation { Wirral, #[strum(serialize = "WOK")] Wokingham, - #[strum(serialize = "WLV")] - Wolverhampton, #[strum(serialize = "WOR")] Worcestershire, #[strum(serialize = "WRX")] - Wrexham, - #[strum(serialize = "YOR")] - York, + WrexhamCountyBorough, } #[derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] -pub enum RomaniaStatesAbbreviation { - #[strum(serialize = "AB")] - Alba, - #[strum(serialize = "AR")] - AradCounty, - #[strum(serialize = "AG")] - Arges, - #[strum(serialize = "BC")] - BacauCounty, - #[strum(serialize = "BH")] +pub enum BelgiumStatesAbbreviation { + #[strum(serialize = "VAN")] + Antwerp, + #[strum(serialize = "BRU")] + BrusselsCapitalRegion, + #[strum(serialize = "VOV")] + EastFlanders, + #[strum(serialize = "VLG")] + Flanders, + #[strum(serialize = "VBR")] + FlemishBrabant, + #[strum(serialize = "WHT")] + Hainaut, + #[strum(serialize = "VLI")] + Limburg, + #[strum(serialize = "WLG")] + Liege, + #[strum(serialize = "WLX")] + Luxembourg, + #[strum(serialize = "WNA")] + Namur, + #[strum(serialize = "WAL")] + Wallonia, + #[strum(serialize = "WBR")] + WalloonBrabant, + #[strum(serialize = "VWV")] + WestFlanders, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum LuxembourgStatesAbbreviation { + #[strum(serialize = "CA")] + CantonOfCapellen, + #[strum(serialize = "CL")] + CantonOfClervaux, + #[strum(serialize = "DI")] + CantonOfDiekirch, + #[strum(serialize = "EC")] + CantonOfEchternach, + #[strum(serialize = "ES")] + CantonOfEschSurAlzette, + #[strum(serialize = "GR")] + CantonOfGrevenmacher, + #[strum(serialize = "LU")] + CantonOfLuxembourg, + #[strum(serialize = "ME")] + CantonOfMersch, + #[strum(serialize = "RD")] + CantonOfRedange, + #[strum(serialize = "RM")] + CantonOfRemich, + #[strum(serialize = "VD")] + CantonOfVianden, + #[strum(serialize = "WI")] + CantonOfWiltz, + #[strum(serialize = "D")] + DiekirchDistrict, + #[strum(serialize = "G")] + GrevenmacherDistrict, + #[strum(serialize = "L")] + LuxembourgDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum RussiaStatesAbbreviation { + #[strum(serialize = "ALT")] + AltaiKrai, + #[strum(serialize = "AL")] + AltaiRepublic, + #[strum(serialize = "AMU")] + AmurOblast, + #[strum(serialize = "ARK")] + Arkhangelsk, + #[strum(serialize = "AST")] + AstrakhanOblast, + #[strum(serialize = "BEL")] + BelgorodOblast, + #[strum(serialize = "BRY")] + BryanskOblast, + #[strum(serialize = "CE")] + ChechenRepublic, + #[strum(serialize = "CHE")] + ChelyabinskOblast, + #[strum(serialize = "CHU")] + ChukotkaAutonomousOkrug, + #[strum(serialize = "CU")] + ChuvashRepublic, + #[strum(serialize = "IRK")] + Irkutsk, + #[strum(serialize = "IVA")] + IvanovoOblast, + #[strum(serialize = "YEV")] + JewishAutonomousOblast, + #[strum(serialize = "KB")] + KabardinoBalkarRepublic, + #[strum(serialize = "KGD")] + Kaliningrad, + #[strum(serialize = "KLU")] + KalugaOblast, + #[strum(serialize = "KAM")] + KamchatkaKrai, + #[strum(serialize = "KC")] + KarachayCherkessRepublic, + #[strum(serialize = "KEM")] + KemerovoOblast, + #[strum(serialize = "KHA")] + KhabarovskKrai, + #[strum(serialize = "KHM")] + KhantyMansiAutonomousOkrug, + #[strum(serialize = "KIR")] + KirovOblast, + #[strum(serialize = "KO")] + KomiRepublic, + #[strum(serialize = "KOS")] + KostromaOblast, + #[strum(serialize = "KDA")] + KrasnodarKrai, + #[strum(serialize = "KYA")] + KrasnoyarskKrai, + #[strum(serialize = "KGN")] + KurganOblast, + #[strum(serialize = "KRS")] + KurskOblast, + #[strum(serialize = "LEN")] + LeningradOblast, + #[strum(serialize = "LIP")] + LipetskOblast, + #[strum(serialize = "MAG")] + MagadanOblast, + #[strum(serialize = "ME")] + MariElRepublic, + #[strum(serialize = "MOW")] + Moscow, + #[strum(serialize = "MOS")] + MoscowOblast, + #[strum(serialize = "MUR")] + MurmanskOblast, + #[strum(serialize = "NEN")] + NenetsAutonomousOkrug, + #[strum(serialize = "NIZ")] + NizhnyNovgorodOblast, + #[strum(serialize = "NGR")] + NovgorodOblast, + #[strum(serialize = "NVS")] + Novosibirsk, + #[strum(serialize = "OMS")] + OmskOblast, + #[strum(serialize = "ORE")] + OrenburgOblast, + #[strum(serialize = "ORL")] + OryolOblast, + #[strum(serialize = "PNZ")] + PenzaOblast, + #[strum(serialize = "PER")] + PermKrai, + #[strum(serialize = "PRI")] + PrimorskyKrai, + #[strum(serialize = "PSK")] + PskovOblast, + #[strum(serialize = "AD")] + RepublicOfAdygea, + #[strum(serialize = "BA")] + RepublicOfBashkortostan, + #[strum(serialize = "BU")] + RepublicOfBuryatia, + #[strum(serialize = "DA")] + RepublicOfDagestan, + #[strum(serialize = "IN")] + RepublicOfIngushetia, + #[strum(serialize = "KL")] + RepublicOfKalmykia, + #[strum(serialize = "KR")] + RepublicOfKarelia, + #[strum(serialize = "KK")] + RepublicOfKhakassia, + #[strum(serialize = "MO")] + RepublicOfMordovia, + #[strum(serialize = "SE")] + RepublicOfNorthOssetiaAlania, + #[strum(serialize = "TA")] + RepublicOfTatarstan, + #[strum(serialize = "ROS")] + RostovOblast, + #[strum(serialize = "RYA")] + RyazanOblast, + #[strum(serialize = "SPE")] + SaintPetersburg, + #[strum(serialize = "SA")] + SakhaRepublic, + #[strum(serialize = "SAK")] + Sakhalin, + #[strum(serialize = "SAM")] + SamaraOblast, + #[strum(serialize = "SAR")] + SaratovOblast, + #[strum(serialize = "UA-40")] + Sevastopol, + #[strum(serialize = "SMO")] + SmolenskOblast, + #[strum(serialize = "STA")] + StavropolKrai, + #[strum(serialize = "SVE")] + Sverdlovsk, + #[strum(serialize = "TAM")] + TambovOblast, + #[strum(serialize = "TOM")] + TomskOblast, + #[strum(serialize = "TUL")] + TulaOblast, + #[strum(serialize = "TY")] + TuvaRepublic, + #[strum(serialize = "TVE")] + TverOblast, + #[strum(serialize = "TYU")] + TyumenOblast, + #[strum(serialize = "UD")] + UdmurtRepublic, + #[strum(serialize = "ULY")] + UlyanovskOblast, + #[strum(serialize = "VLA")] + VladimirOblast, + #[strum(serialize = "VLG")] + VologdaOblast, + #[strum(serialize = "VOR")] + VoronezhOblast, + #[strum(serialize = "YAN")] + YamaloNenetsAutonomousOkrug, + #[strum(serialize = "YAR")] + YaroslavlOblast, + #[strum(serialize = "ZAB")] + ZabaykalskyKrai, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SanMarinoStatesAbbreviation { + #[strum(serialize = "01")] + Acquaviva, + #[strum(serialize = "06")] + BorgoMaggiore, + #[strum(serialize = "02")] + Chiesanuova, + #[strum(serialize = "03")] + Domagnano, + #[strum(serialize = "04")] + Faetano, + #[strum(serialize = "05")] + Fiorentino, + #[strum(serialize = "08")] + Montegiardino, + #[strum(serialize = "07")] + SanMarino, + #[strum(serialize = "09")] + Serravalle, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SerbiaStatesAbbreviation { + #[strum(serialize = "00")] + Belgrade, + + #[strum(serialize = "01")] + BorDistrict, + + #[strum(serialize = "02")] + BraničevoDistrict, + + #[strum(serialize = "03")] + CentralBanatDistrict, + + #[strum(serialize = "04")] + JablanicaDistrict, + + #[strum(serialize = "05")] + KolubaraDistrict, + + #[strum(serialize = "06")] + MačvaDistrict, + + #[strum(serialize = "07")] + MoravicaDistrict, + + #[strum(serialize = "08")] + NišavaDistrict, + + #[strum(serialize = "09")] + NorthBanatDistrict, + + #[strum(serialize = "10")] + NorthBačkaDistrict, + + #[strum(serialize = "11")] + PirotDistrict, + + #[strum(serialize = "12")] + PodunavljeDistrict, + + #[strum(serialize = "13")] + PomoravljeDistrict, + + #[strum(serialize = "14")] + PčinjaDistrict, + + #[strum(serialize = "15")] + RasinaDistrict, + + #[strum(serialize = "16")] + RaškaDistrict, + + #[strum(serialize = "17")] + SouthBanatDistrict, + + #[strum(serialize = "18")] + SouthBačkaDistrict, + + #[strum(serialize = "19")] + SremDistrict, + + #[strum(serialize = "20")] + ToplicaDistrict, + + #[strum(serialize = "21")] + Vojvodina, + + #[strum(serialize = "22")] + WestBačkaDistrict, + + #[strum(serialize = "23")] + ZaječarDistrict, + + #[strum(serialize = "24")] + ZlatiborDistrict, + + #[strum(serialize = "25")] + ŠumadijaDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SlovakiaStatesAbbreviation { + #[strum(serialize = "BC")] + BanskaBystricaRegion, + #[strum(serialize = "BL")] + BratislavaRegion, + #[strum(serialize = "KI")] + KosiceRegion, + #[strum(serialize = "NI")] + NitraRegion, + #[strum(serialize = "PV")] + PresovRegion, + #[strum(serialize = "TC")] + TrencinRegion, + #[strum(serialize = "TA")] + TrnavaRegion, + #[strum(serialize = "ZI")] + ZilinaRegion, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SloveniaStatesAbbreviation { + #[strum(serialize = "001")] + Ajdovščina, + #[strum(serialize = "213")] + Ankaran, + #[strum(serialize = "002")] + Beltinci, + #[strum(serialize = "148")] + Benedikt, + #[strum(serialize = "149")] + BistricaObSotli, + #[strum(serialize = "003")] + Bled, + #[strum(serialize = "150")] + Bloke, + #[strum(serialize = "004")] + Bohinj, + #[strum(serialize = "005")] + Borovnica, + #[strum(serialize = "006")] + Bovec, + #[strum(serialize = "151")] + Braslovče, + #[strum(serialize = "007")] + Brda, + #[strum(serialize = "008")] + Brezovica, + #[strum(serialize = "009")] + Brežice, + #[strum(serialize = "152")] + Cankova, + #[strum(serialize = "012")] + CerkljeNaGorenjskem, + #[strum(serialize = "013")] + Cerknica, + #[strum(serialize = "014")] + Cerkno, + #[strum(serialize = "153")] + Cerkvenjak, + #[strum(serialize = "011")] + CityMunicipalityOfCelje, + #[strum(serialize = "085")] + CityMunicipalityOfNovoMesto, + #[strum(serialize = "018")] + Destrnik, + #[strum(serialize = "019")] + Divača, + #[strum(serialize = "154")] + Dobje, + #[strum(serialize = "020")] + Dobrepolje, + #[strum(serialize = "155")] + Dobrna, + #[strum(serialize = "021")] + DobrovaPolhovGradec, + #[strum(serialize = "156")] + Dobrovnik, + #[strum(serialize = "022")] + DolPriLjubljani, + #[strum(serialize = "157")] + DolenjskeToplice, + #[strum(serialize = "023")] + Domžale, + #[strum(serialize = "024")] + Dornava, + #[strum(serialize = "025")] + Dravograd, + #[strum(serialize = "026")] + Duplek, + #[strum(serialize = "027")] + GorenjaVasPoljane, + #[strum(serialize = "028")] + Gorišnica, + #[strum(serialize = "207")] + Gorje, + #[strum(serialize = "029")] + GornjaRadgona, + #[strum(serialize = "030")] + GornjiGrad, + #[strum(serialize = "031")] + GornjiPetrovci, + #[strum(serialize = "158")] + Grad, + #[strum(serialize = "032")] + Grosuplje, + #[strum(serialize = "159")] + Hajdina, + #[strum(serialize = "161")] + Hodoš, + #[strum(serialize = "162")] + Horjul, + #[strum(serialize = "160")] + HočeSlivnica, + #[strum(serialize = "034")] + Hrastnik, + #[strum(serialize = "035")] + HrpeljeKozina, + #[strum(serialize = "036")] + Idrija, + #[strum(serialize = "037")] + Ig, + #[strum(serialize = "039")] + IvančnaGorica, + #[strum(serialize = "040")] + Izola, + #[strum(serialize = "041")] + Jesenice, + #[strum(serialize = "163")] + Jezersko, + #[strum(serialize = "042")] + Jursinci, + #[strum(serialize = "043")] + Kamnik, + #[strum(serialize = "044")] + KanalObSoci, + #[strum(serialize = "045")] + Kidricevo, + #[strum(serialize = "046")] + Kobarid, + #[strum(serialize = "047")] + Kobilje, + #[strum(serialize = "049")] + Komen, + #[strum(serialize = "164")] + Komenda, + #[strum(serialize = "050")] + Koper, + #[strum(serialize = "197")] + KostanjevicaNaKrki, + #[strum(serialize = "165")] + Kostel, + #[strum(serialize = "051")] + Kozje, + #[strum(serialize = "048")] + Kocevje, + #[strum(serialize = "052")] + Kranj, + #[strum(serialize = "053")] + KranjskaGora, + #[strum(serialize = "166")] + Krizevci, + #[strum(serialize = "055")] + Kungota, + #[strum(serialize = "056")] + Kuzma, + #[strum(serialize = "057")] + Lasko, + #[strum(serialize = "058")] + Lenart, + #[strum(serialize = "059")] + Lendava, + #[strum(serialize = "060")] + Litija, + #[strum(serialize = "061")] + Ljubljana, + #[strum(serialize = "062")] + Ljubno, + #[strum(serialize = "063")] + Ljutomer, + #[strum(serialize = "064")] + Logatec, + #[strum(serialize = "208")] + LogDragomer, + #[strum(serialize = "167")] + LovrencNaPohorju, + #[strum(serialize = "065")] + LoskaDolina, + #[strum(serialize = "066")] + LoskiPotok, + #[strum(serialize = "068")] + Lukovica, + #[strum(serialize = "067")] + Luče, + #[strum(serialize = "069")] + Majsperk, + #[strum(serialize = "198")] + Makole, + #[strum(serialize = "070")] + Maribor, + #[strum(serialize = "168")] + Markovci, + #[strum(serialize = "071")] + Medvode, + #[strum(serialize = "072")] + Menges, + #[strum(serialize = "073")] + Metlika, + #[strum(serialize = "074")] + Mezica, + #[strum(serialize = "169")] + MiklavzNaDravskemPolju, + #[strum(serialize = "075")] + MirenKostanjevica, + #[strum(serialize = "212")] + Mirna, + #[strum(serialize = "170")] + MirnaPec, + #[strum(serialize = "076")] + Mislinja, + #[strum(serialize = "199")] + MokronogTrebelno, + #[strum(serialize = "078")] + MoravskeToplice, + #[strum(serialize = "077")] + Moravce, + #[strum(serialize = "079")] + Mozirje, + #[strum(serialize = "195")] + Apače, + #[strum(serialize = "196")] + Cirkulane, + #[strum(serialize = "038")] + IlirskaBistrica, + #[strum(serialize = "054")] + Krsko, + #[strum(serialize = "123")] + Skofljica, + #[strum(serialize = "080")] + MurskaSobota, + #[strum(serialize = "081")] + Muta, + #[strum(serialize = "082")] + Naklo, + #[strum(serialize = "083")] + Nazarje, + #[strum(serialize = "084")] + NovaGorica, + #[strum(serialize = "086")] + Odranci, + #[strum(serialize = "171")] + Oplotnica, + #[strum(serialize = "087")] + Ormoz, + #[strum(serialize = "088")] + Osilnica, + #[strum(serialize = "089")] + Pesnica, + #[strum(serialize = "090")] + Piran, + #[strum(serialize = "091")] + Pivka, + #[strum(serialize = "172")] + Podlehnik, + #[strum(serialize = "093")] + Podvelka, + #[strum(serialize = "092")] + Podcetrtek, + #[strum(serialize = "200")] + Poljcane, + #[strum(serialize = "173")] + Polzela, + #[strum(serialize = "094")] + Postojna, + #[strum(serialize = "174")] + Prebold, + #[strum(serialize = "095")] + Preddvor, + #[strum(serialize = "175")] + Prevalje, + #[strum(serialize = "096")] + Ptuj, + #[strum(serialize = "097")] + Puconci, + #[strum(serialize = "100")] + Radenci, + #[strum(serialize = "099")] + Radece, + #[strum(serialize = "101")] + RadljeObDravi, + #[strum(serialize = "102")] + Radovljica, + #[strum(serialize = "103")] + RavneNaKoroskem, + #[strum(serialize = "176")] + Razkrizje, + #[strum(serialize = "098")] + RaceFram, + #[strum(serialize = "201")] + RenčeVogrsko, + #[strum(serialize = "209")] + RecicaObSavinji, + #[strum(serialize = "104")] + Ribnica, + #[strum(serialize = "177")] + RibnicaNaPohorju, + #[strum(serialize = "107")] + Rogatec, + #[strum(serialize = "106")] + RogaskaSlatina, + #[strum(serialize = "105")] + Rogasovci, + #[strum(serialize = "108")] + Ruse, + #[strum(serialize = "178")] + SelnicaObDravi, + #[strum(serialize = "109")] + Semic, + #[strum(serialize = "110")] + Sevnica, + #[strum(serialize = "111")] + Sezana, + #[strum(serialize = "112")] + SlovenjGradec, + #[strum(serialize = "113")] + SlovenskaBistrica, + #[strum(serialize = "114")] + SlovenskeKonjice, + #[strum(serialize = "179")] + Sodrazica, + #[strum(serialize = "180")] + Solcava, + #[strum(serialize = "202")] + SredisceObDravi, + #[strum(serialize = "115")] + Starse, + #[strum(serialize = "203")] + Straza, + #[strum(serialize = "181")] + SvetaAna, + #[strum(serialize = "204")] + SvetaTrojica, + #[strum(serialize = "182")] + SvetiAndraz, + #[strum(serialize = "116")] + SvetiJurijObScavnici, + #[strum(serialize = "210")] + SvetiJurijVSlovenskihGoricah, + #[strum(serialize = "205")] + SvetiTomaz, + #[strum(serialize = "184")] + Tabor, + #[strum(serialize = "010")] + Tišina, + #[strum(serialize = "128")] + Tolmin, + #[strum(serialize = "129")] + Trbovlje, + #[strum(serialize = "130")] + Trebnje, + #[strum(serialize = "185")] + TrnovskaVas, + #[strum(serialize = "186")] + Trzin, + #[strum(serialize = "131")] + Tržič, + #[strum(serialize = "132")] + Turnišče, + #[strum(serialize = "187")] + VelikaPolana, + #[strum(serialize = "134")] + VelikeLašče, + #[strum(serialize = "188")] + Veržej, + #[strum(serialize = "135")] + Videm, + #[strum(serialize = "136")] + Vipava, + #[strum(serialize = "137")] + Vitanje, + #[strum(serialize = "138")] + Vodice, + #[strum(serialize = "139")] + Vojnik, + #[strum(serialize = "189")] + Vransko, + #[strum(serialize = "140")] + Vrhnika, + #[strum(serialize = "141")] + Vuzenica, + #[strum(serialize = "142")] + ZagorjeObSavi, + #[strum(serialize = "143")] + Zavrč, + #[strum(serialize = "144")] + Zreče, + #[strum(serialize = "015")] + Črenšovci, + #[strum(serialize = "016")] + ČrnaNaKoroškem, + #[strum(serialize = "017")] + Črnomelj, + #[strum(serialize = "033")] + Šalovci, + #[strum(serialize = "183")] + ŠempeterVrtojba, + #[strum(serialize = "118")] + Šentilj, + #[strum(serialize = "119")] + Šentjernej, + #[strum(serialize = "120")] + Šentjur, + #[strum(serialize = "211")] + Šentrupert, + #[strum(serialize = "117")] + Šenčur, + #[strum(serialize = "121")] + Škocjan, + #[strum(serialize = "122")] + ŠkofjaLoka, + #[strum(serialize = "124")] + ŠmarjePriJelšah, + #[strum(serialize = "206")] + ŠmarješkeToplice, + #[strum(serialize = "125")] + ŠmartnoObPaki, + #[strum(serialize = "194")] + ŠmartnoPriLitiji, + #[strum(serialize = "126")] + Šoštanj, + #[strum(serialize = "127")] + Štore, + #[strum(serialize = "190")] + Žalec, + #[strum(serialize = "146")] + Železniki, + #[strum(serialize = "191")] + Žetale, + #[strum(serialize = "147")] + Žiri, + #[strum(serialize = "192")] + Žirovnica, + #[strum(serialize = "193")] + Žužemberk, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SwedenStatesAbbreviation { + #[strum(serialize = "K")] + Blekinge, + #[strum(serialize = "W")] + DalarnaCounty, + #[strum(serialize = "I")] + GotlandCounty, + #[strum(serialize = "X")] + GävleborgCounty, + #[strum(serialize = "N")] + HallandCounty, + #[strum(serialize = "F")] + JönköpingCounty, + #[strum(serialize = "H")] + KalmarCounty, + #[strum(serialize = "G")] + KronobergCounty, + #[strum(serialize = "BD")] + NorrbottenCounty, + #[strum(serialize = "M")] + SkåneCounty, + #[strum(serialize = "AB")] + StockholmCounty, + #[strum(serialize = "D")] + SödermanlandCounty, + #[strum(serialize = "C")] + UppsalaCounty, + #[strum(serialize = "S")] + VärmlandCounty, + #[strum(serialize = "AC")] + VästerbottenCounty, + #[strum(serialize = "Y")] + VästernorrlandCounty, + #[strum(serialize = "U")] + VästmanlandCounty, + #[strum(serialize = "O")] + VästraGötalandCounty, + #[strum(serialize = "T")] + ÖrebroCounty, + #[strum(serialize = "E")] + ÖstergötlandCounty, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum UkraineStatesAbbreviation { + #[strum(serialize = "43")] + AutonomousRepublicOfCrimea, + #[strum(serialize = "71")] + CherkasyOblast, + #[strum(serialize = "74")] + ChernihivOblast, + #[strum(serialize = "77")] + ChernivtsiOblast, + #[strum(serialize = "12")] + DnipropetrovskOblast, + #[strum(serialize = "14")] + DonetskOblast, + #[strum(serialize = "26")] + IvanoFrankivskOblast, + #[strum(serialize = "63")] + KharkivOblast, + #[strum(serialize = "65")] + KhersonOblast, + #[strum(serialize = "68")] + KhmelnytskyOblast, + #[strum(serialize = "30")] + Kiev, + #[strum(serialize = "35")] + KirovohradOblast, + #[strum(serialize = "32")] + KyivOblast, + #[strum(serialize = "09")] + LuhanskOblast, + #[strum(serialize = "46")] + LvivOblast, + #[strum(serialize = "48")] + MykolaivOblast, + #[strum(serialize = "51")] + OdessaOblast, + #[strum(serialize = "56")] + RivneOblast, + #[strum(serialize = "59")] + SumyOblast, + #[strum(serialize = "61")] + TernopilOblast, + #[strum(serialize = "05")] + VinnytsiaOblast, + #[strum(serialize = "07")] + VolynOblast, + #[strum(serialize = "21")] + ZakarpattiaOblast, + #[strum(serialize = "23")] + ZaporizhzhyaOblast, + #[strum(serialize = "18")] + ZhytomyrOblast, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum RomaniaStatesAbbreviation { + #[strum(serialize = "AB")] + Alba, + #[strum(serialize = "AR")] + AradCounty, + #[strum(serialize = "AG")] + Arges, + #[strum(serialize = "BC")] + BacauCounty, + #[strum(serialize = "BH")] BihorCounty, #[strum(serialize = "BN")] BistritaNasaudCounty, @@ -6480,3 +7591,28 @@ pub enum PaymentConnectorTransmission { /// Payment Connector call succeeded ConnectorCallSucceeded, } + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum TriggeredBy { + /// Denotes payment attempt is been created by internal system. + #[default] + Internal, + /// Denotes payment attempt is been created by external system. + External, +} diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs index 65f6ae9ad68..0361f5b0e46 100644 --- a/crates/common_types/src/domain.rs +++ b/crates/common_types/src/domain.rs @@ -41,3 +41,15 @@ pub struct AdyenSplitItem { pub description: Option, } impl_to_sql_from_sql_json!(AdyenSplitItem); + +/// Fee information to be charged on the payment being collected for sub-merchant via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitSubMerchantData { + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitSubMerchantData); diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index ba96d618c41..996478e8bfa 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -12,7 +12,7 @@ use euclid::frontend::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -26,6 +26,8 @@ pub enum SplitPaymentsRequest { StripeSplitPayment(StripeSplitPaymentRequest), /// AdyenSplitPayment AdyenSplitPayment(AdyenSplitData), + /// XenditSplitPayment + XenditSplitPayment(XenditSplitRequest), } impl_to_sql_from_sql_json!(SplitPaymentsRequest); @@ -156,6 +158,99 @@ pub enum ConnectorChargeResponseData { StripeSplitPayment(StripeChargeResponseData), /// AdyenChargeResponseData AdyenSplitPayment(AdyenSplitData), + /// XenditChargeResponseData + XenditSplitPayment(XenditChargeResponseData), } impl_to_sql_from_sql_json!(ConnectorChargeResponseData); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitRoute { + /// Amount of payments to be split + pub flat_amount: Option, + /// Amount of payments to be split, using a percent rate as unit + pub percent_amount: Option, + /// Currency code + #[schema(value_type = Currency, example = "USD")] + pub currency: enums::Currency, + /// ID of the destination account where the amount will be routed to + pub destination_account_id: String, + /// Reference ID which acts as an identifier of the route itself + pub reference_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitRoute); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitRequest { + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitRequest); + +/// Xendit Charge Request +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditSplitRequest { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitRequest), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditSplitRequest); + +/// Charge Information +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditChargeResponseData { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitResponse), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditChargeResponseData); + +/// Fee information charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitResponse { + /// Identifier for split rule created for the payment + pub split_rule_id: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitResponse); diff --git a/crates/common_types/src/refunds.rs b/crates/common_types/src/refunds.rs index c96937021c8..f7c12f90f84 100644 --- a/crates/common_types/src/refunds.rs +++ b/crates/common_types/src/refunds.rs @@ -5,7 +5,7 @@ use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -19,6 +19,8 @@ pub enum SplitRefund { StripeSplitRefund(StripeSplitRefundRequest), /// AdyenSplitRefundRequest AdyenSplitRefund(AdyenSplitData), + /// XenditSplitRefundRequest + XenditSplitRefund(XenditSplitSubMerchantData), } impl_to_sql_from_sql_json!(SplitRefund); diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 2e90d646e9e..556090858fb 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -99,6 +99,7 @@ pub enum ApiEventsType { ApplePayCertificatesMigration, FraudCheck, Recon, + ExternalServiceAuth, Dispute { dispute_id: String, }, diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs index 945056aafef..6bdeae9b6d9 100644 --- a/crates/common_utils/src/ext_traits.rs +++ b/crates/common_utils/src/ext_traits.rs @@ -4,6 +4,8 @@ use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret, Strategy}; use quick_xml::de; +#[cfg(all(feature = "logs", feature = "async_ext"))] +use router_env::logger; use serde::{Deserialize, Serialize}; use crate::{ @@ -295,28 +297,34 @@ impl StringExt for String { /// Extending functionalities of Wrapper types for idiomatic #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -pub trait AsyncExt { +pub trait AsyncExt { /// Output type of the map function type WrappedSelf; /// Extending map by allowing functions which are async - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send; /// Extending the `and_then` by allowing functions which are async - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send; + + /// Extending `unwrap_or_else` to allow async fallback + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send; } #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -impl AsyncExt for Result { +impl AsyncExt for Result { type WrappedSelf = Result; - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send, @@ -327,7 +335,7 @@ impl AsyncExt for Result { } } - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send, @@ -337,13 +345,28 @@ impl AsyncExt for Result { Err(err) => Err(err), } } + + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Ok(a) => a, + Err(_err) => { + #[cfg(feature = "logs")] + logger::error!("Error: {:?}", _err); + func().await + } + } + } } #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -impl AsyncExt for Option { +impl AsyncExt for Option { type WrappedSelf = Option; - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send, @@ -354,7 +377,7 @@ impl AsyncExt for Option { } } - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send, @@ -364,6 +387,17 @@ impl AsyncExt for Option { None => None, } } + + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Some(a) => a, + None => func().await, + } + } } /// Extension trait for validating application configuration. This trait provides utilities to diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 0377a3e8183..9a105933e14 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -6,6 +6,9 @@ pub mod authentication; /// Enum for Theme Lineage pub mod theme; +/// types that are wrappers around primitive types +pub mod primitive_wrappers; + use std::{ borrow::Cow, fmt::Display, @@ -26,6 +29,10 @@ use diesel::{ AsExpression, FromSqlRow, Queryable, }; use error_stack::{report, ResultExt}; +pub use primitive_wrappers::bool_wrappers::{ + AlwaysRequestExtendedAuthorization, ExtendedAuthorizationAppliedBool, + RequestExtendedAuthorizationBool, +}; use rust_decimal::{ prelude::{FromPrimitive, ToPrimitive}, Decimal, diff --git a/crates/common_utils/src/types/primitive_wrappers.rs b/crates/common_utils/src/types/primitive_wrappers.rs new file mode 100644 index 00000000000..850f6a7cb5f --- /dev/null +++ b/crates/common_utils/src/types/primitive_wrappers.rs @@ -0,0 +1,107 @@ +pub(crate) mod bool_wrappers { + use serde::{Deserialize, Serialize}; + + /// Bool that represents if Extended Authorization is Applied or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct ExtendedAuthorizationAppliedBool(bool); + impl From for ExtendedAuthorizationAppliedBool { + fn from(value: bool) -> Self { + Self(value) + } + } + impl diesel::serialize::ToSql for ExtendedAuthorizationAppliedBool + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for ExtendedAuthorizationAppliedBool + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } + + /// Bool that represents if Extended Authorization is Requested or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct RequestExtendedAuthorizationBool(bool); + impl From for RequestExtendedAuthorizationBool { + fn from(value: bool) -> Self { + Self(value) + } + } + impl RequestExtendedAuthorizationBool { + /// returns the inner bool value + pub fn is_true(&self) -> bool { + self.0 + } + } + impl diesel::serialize::ToSql for RequestExtendedAuthorizationBool + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for RequestExtendedAuthorizationBool + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } + + /// Bool that represents if Extended Authorization is always Requested or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, diesel::expression::AsExpression, Serialize, Deserialize, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct AlwaysRequestExtendedAuthorization(bool); + impl diesel::serialize::ToSql + for AlwaysRequestExtendedAuthorization + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for AlwaysRequestExtendedAuthorization + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } +} diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index 599d22ae3f6..ebb5870655c 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -107,6 +107,7 @@ pub struct ApiModelMetaData { pub locale: Option, pub card_brands: Option>, pub merchant_category_code: Option, + pub merchant_configuration_id: Option, } #[serde_with::skip_serializing_none] diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index c24beba0a47..2d1d2e4ede8 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -118,6 +118,7 @@ pub struct ConfigMetadata { pub locale: Option, pub card_brands: Option, pub merchant_category_code: Option, + pub merchant_configuration_id: Option, } #[serde_with::skip_serializing_none] @@ -173,6 +174,7 @@ pub struct ConnectorConfig { pub cashtocode: Option, pub checkout: Option, pub coinbase: Option, + pub coingate: Option, pub cryptopay: Option, pub cybersource: Option, #[cfg(feature = "payouts")] @@ -202,6 +204,7 @@ pub struct ConnectorConfig { pub klarna: Option, pub mifinity: Option, pub mollie: Option, + pub moneris: Option, pub multisafepay: Option, pub nexinets: Option, pub nexixpay: Option, @@ -338,6 +341,7 @@ impl ConnectorConfig { Connector::Cashtocode => Ok(connector_data.cashtocode), Connector::Checkout => Ok(connector_data.checkout), Connector::Coinbase => Ok(connector_data.coinbase), + Connector::Coingate => Ok(connector_data.coingate), Connector::Cryptopay => Ok(connector_data.cryptopay), Connector::Cybersource => Ok(connector_data.cybersource), Connector::Iatapay => Ok(connector_data.iatapay), @@ -365,6 +369,7 @@ impl ConnectorConfig { Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), + Connector::Moneris => Ok(connector_data.moneris), Connector::Multisafepay => Ok(connector_data.multisafepay), Connector::Nexinets => Ok(connector_data.nexinets), Connector::Nexixpay => Ok(connector_data.nexixpay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 72272b425d6..ba54730681f 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1159,6 +1159,12 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + [cryptopay] [[cryptopay.crypto]] payment_method_type = "crypto_currency" @@ -1872,6 +1878,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" @@ -4055,7 +4103,7 @@ private_key="Base64 encoded PEM formatted private key" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.endpoint_prefix] name="endpoint_prefix" @@ -4067,25 +4115,31 @@ type="Text" name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" [taxjar] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 13f0409ebf1..88bba56c820 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -996,6 +996,14 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" + +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + + [cybersource] [[cybersource.credit]] payment_method_type = "Mastercard" @@ -1580,6 +1588,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" @@ -2991,31 +3041,37 @@ type="Text" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.merchant_country_code] name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" [taxjar] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 70895d48c8d..db440656fba 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1157,6 +1157,12 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + [cryptopay] [[cryptopay.crypto]] payment_method_type = "crypto_currency" @@ -1209,6 +1215,8 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[cybersource.wallet]] payment_method_type = "paze" +[[cybersource.wallet]] + payment_method_type = "samsung_pay" [cybersource.connector_auth.SignatureKey] api_key="Key" key1="Merchant ID" @@ -1820,6 +1828,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" @@ -3997,31 +4047,37 @@ type="Text" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.merchant_country_code] name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 2cbd7deb426..92da1978458 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use common_enums::{AuthenticationConnectors, UIWidgetFormLayout}; -use common_utils::{encryption::Encryption, pii}; +use common_utils::{encryption::Encryption, pii, types::AlwaysRequestExtendedAuthorization}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use masking::Secret; @@ -57,6 +57,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -146,6 +147,7 @@ pub struct ProfileUpdateInternal { pub is_network_tokenization_enabled: Option, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, @@ -188,6 +190,7 @@ impl ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization, is_click_to_pay_enabled, authentication_product_ids, } = self; @@ -249,6 +252,8 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + always_request_extended_authorization: always_request_extended_authorization + .or(source.always_request_extended_authorization), is_click_to_pay_enabled: is_click_to_pay_enabled .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids @@ -307,6 +312,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -527,6 +533,7 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + always_request_extended_authorization: None, is_click_to_pay_enabled: is_click_to_pay_enabled .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids @@ -584,6 +591,8 @@ pub struct PaymentLinkConfigRequest { pub background_image: Option, pub details_layout: Option, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index b54a240c727..7f81a57e639 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,6 +1,9 @@ use common_utils::{ id_type, pii, - types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, + types::{ + ConnectorTransactionId, ConnectorTransactionIdTrait, ExtendedAuthorizationAppliedBool, + MinorUnit, RequestExtendedAuthorizationBool, + }, }; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -93,8 +96,12 @@ pub struct PaymentAttempt { pub id: id_type::GlobalAttemptId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, pub charges: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -174,6 +181,9 @@ pub struct PaymentAttempt { /// INFO: This field is deprecated and replaced by processor_transaction_data pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub processor_transaction_data: Option, pub card_discovery: Option, pub charges: Option, @@ -303,8 +313,12 @@ pub struct PaymentAttemptNew { pub id: id_type::GlobalAttemptId, pub connector_token_details: Option, pub card_discovery: Option, - pub connector: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub charges: Option, + pub feature_metadata: Option, + pub connector: Option, } #[cfg(feature = "v1")] @@ -377,6 +391,10 @@ pub struct PaymentAttemptNew { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub capture_before: Option, pub card_discovery: Option, } @@ -820,6 +838,7 @@ pub struct PaymentAttemptUpdateInternal { // customer_acceptance: Option, // card_network: Option, pub connector_token_details: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -3510,6 +3529,26 @@ pub enum RedirectForm { common_utils::impl_to_sql_from_sql_json!(RedirectForm); +#[cfg(feature = "v2")] +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::pg::sql_types::Jsonb)] +pub struct PaymentAttemptFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::pg::sql_types::Jsonb)] +pub struct PaymentAttemptRecoveryData { + pub attempt_triggered_by: common_enums::TriggeredBy, +} +#[cfg(feature = "v2")] +common_utils::impl_to_sql_from_sql_json!(PaymentAttemptFeatureMetadata); + mod tests { #[test] diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 48c63f9501f..eb05d352154 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -1,5 +1,9 @@ use common_enums::{PaymentMethodType, RequestIncrementalAuthorization}; -use common_utils::{encryption::Encryption, pii, types::MinorUnit}; +use common_utils::{ + encryption::Encryption, + pii, + types::{MinorUnit, RequestExtendedAuthorizationBool}, +}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -72,6 +76,7 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, pub payment_link_config: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub split_payments: Option, pub platform_merchant_id: Option, @@ -139,6 +144,7 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub split_payments: Option, pub platform_merchant_id: Option, @@ -171,6 +177,10 @@ pub struct PaymentLinkConfigRequestForPayments { pub details_layout: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } common_utils::impl_to_sql_from_sql_json!(PaymentLinkConfigRequestForPayments); @@ -371,6 +381,7 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub platform_merchant_id: Option, pub split_payments: Option, diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 60393211968..cfcbc2bd199 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -412,6 +412,64 @@ impl PaymentAttempt { )) } + #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] + pub async fn get_total_count_of_attempts( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + ) -> StorageResult { + let mut filter = ::table() + .count() + .filter(dsl::merchant_id.eq(merchant_id.to_owned())) + .filter(dsl::id.eq_any(active_attempt_ids.to_owned())) + .into_boxed(); + + if let Some(connector) = connector { + filter = filter.filter(dsl::connector.eq(connector)); + } + + if let Some(payment_method_type) = payment_method_type { + filter = filter.filter(dsl::payment_method_type_v2.eq(payment_method_type)); + } + if let Some(payment_method_subtype) = payment_method_subtype { + filter = filter.filter(dsl::payment_method_subtype.eq(payment_method_subtype)); + } + if let Some(authentication_type) = authentication_type { + filter = filter.filter(dsl::authentication_type.eq(authentication_type)); + } + if let Some(merchant_connector_id) = merchant_connector_id { + filter = filter.filter(dsl::merchant_connector_id.eq(merchant_connector_id)) + } + if let Some(card_network) = card_network { + filter = filter.filter(dsl::card_network.eq(card_network)) + } + + router_env::logger::debug!(query = %debug_query::(&filter).to_string()); + + // TODO: Remove these logs after debugging the issue for delay in count query + let start_time = std::time::Instant::now(); + router_env::logger::debug!("Executing count query start_time: {:?}", start_time); + let result = db_metrics::track_database_call::<::Table, _, _>( + filter.get_result_async::(conn), + db_metrics::DatabaseOperation::Filter, + ) + .await + .change_context(DatabaseError::Others) + .attach_printable("Error filtering count of payments"); + + let duration = start_time.elapsed(); + router_env::logger::debug!("Completed count query in {:?}", duration); + + result + } + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn get_total_count_of_attempts( diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 2e8219c5ab4..cfc0f28e4d0 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -216,6 +216,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, } @@ -910,6 +911,9 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, connector_mandate_detail -> Nullable, + request_extended_authorization -> Nullable, + extended_authorization_applied -> Nullable, + capture_before -> Nullable, processor_transaction_data -> Nullable, card_discovery -> Nullable, charges -> Nullable, @@ -993,6 +997,7 @@ diesel::table! { organization_id -> Varchar, tax_details -> Nullable, skip_external_tax_calculation -> Nullable, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, #[max_length = 64] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index ab2195526f5..69ba37e8df6 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -224,6 +224,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, three_ds_decision_manager_config -> Nullable, @@ -877,8 +878,12 @@ diesel::table! { id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, + request_extended_authorization -> Nullable, + extended_authorization_applied -> Nullable, + capture_before -> Nullable, card_discovery -> Nullable, charges -> Nullable, + feature_metadata -> Nullable, } } @@ -952,6 +957,7 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, #[max_length = 64] diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 6bc8aee2893..22d99ca2268 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -1,5 +1,5 @@ #[cfg(feature = "v2")] -use common_enums::{enums::PaymentConnectorTransmission, PaymentMethod, PaymentMethodType}; +use common_enums::enums::PaymentConnectorTransmission; use common_utils::{hashing::HashedString, pii, types::MinorUnit}; use diesel::{ sql_types::{Json, Jsonb}, @@ -43,7 +43,7 @@ impl masking::SerializableSecret for OrderDetailsWithAmount {} common_utils::impl_to_sql_from_sql_json!(OrderDetailsWithAmount); #[cfg(feature = "v2")] -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow,AsExpression)] #[diesel(sql_type = Json)] pub struct FeatureMetadata { /// Redirection response coming in request as metadata field only for redirection scenarios @@ -137,9 +137,9 @@ pub struct PaymentRevenueRecoveryMetadata { /// Billing Connector Payment Details pub billing_connector_payment_details: BillingConnectorPaymentDetails, ///Payment Method Type - pub payment_method_type: PaymentMethod, + pub payment_method_type: common_enums::enums::PaymentMethod, /// PaymentMethod Subtype - pub payment_method_subtype: PaymentMethodType, + pub payment_method_subtype: common_enums::enums::PaymentMethodType, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 27085424083..d6c8d5616fa 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -2,7 +2,10 @@ use common_enums::{ AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod, PaymentMethodType, }; -use common_utils::types::{ConnectorTransactionId, MinorUnit}; +use common_utils::types::{ + ConnectorTransactionId, ExtendedAuthorizationAppliedBool, MinorUnit, + RequestExtendedAuthorizationBool, +}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -203,6 +206,9 @@ pub struct PaymentAttemptBatchNew { pub order_tax_amount: Option, pub processor_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, } @@ -282,6 +288,9 @@ impl PaymentAttemptBatchNew { shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, } } diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 13d5266dabd..915b87a9189 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -49,6 +49,7 @@ lazy_static = "1.4.0" api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false } cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } +common_types = { version = "0.1.0", path = "../common_types" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics"] } hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces", default-features = false } diff --git a/crates/hyperswitch_connectors/src/connectors/coingate.rs b/crates/hyperswitch_connectors/src/connectors/coingate.rs index e3bece084b3..81bb9116950 100644 --- a/crates/hyperswitch_connectors/src/connectors/coingate.rs +++ b/crates/hyperswitch_connectors/src/connectors/coingate.rs @@ -1,10 +1,11 @@ pub mod transformers; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -31,25 +32,26 @@ use hyperswitch_interfaces::{ ConnectorValidation, }, configs::Connectors, + consts::NO_ERROR_CODE, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{Mask, PeekInterface}; use transformers as coingate; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Coingate { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Coingate { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -70,7 +72,6 @@ impl api::PaymentToken for Coingate {} impl ConnectorIntegration for Coingate { - // Not Implemented (R) } impl ConnectorCommonExt for Coingate @@ -99,13 +100,10 @@ impl ConnectorCommon for Coingate { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { - "application/json" + "application/x-www-form-urlencoded" } fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { @@ -120,7 +118,7 @@ impl ConnectorCommon for Coingate { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Bearer {}", auth.api_key.peek()).into_masked(), )]) } @@ -139,19 +137,15 @@ impl ConnectorCommon for Coingate { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, + code: NO_ERROR_CODE.to_string(), message: response.message, - reason: response.reason, + reason: Some(response.reason), attempt_status: None, connector_transaction_id: None, }) } } -impl ConnectorValidation for Coingate { - //TODO: implement functions when support enabled -} - impl ConnectorIntegration for Coingate { //TODO: implement sessions flow } @@ -179,9 +173,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}{}", self.base_url(connectors), "/api/v2/orders")) } fn get_request_body( @@ -197,7 +191,7 @@ impl ConnectorIntegration for Coi fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/{}{}", + self.base_url(connectors), + "api/v2/orders/", + connector_id + )) } fn build_request( @@ -315,232 +319,78 @@ impl ConnectorIntegration for Coi } impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( + fn build_request( &self, _req: &PaymentsCaptureRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "Capture".to_string(), + connector: "Coingate".to_string(), + } + .into()) } +} +impl ConnectorIntegration for Coingate { fn build_request( &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, + _req: &RouterData, + _connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: coingate::CoingatePaymentsResponse = res - .response - .parse_struct("Coingate PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Void".to_string(), + connector: "Coingate".to_string(), + } + .into()) } } -impl ConnectorIntegration for Coingate {} - impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &RefundsRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundsRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - req: &RefundsRouterData, - _connectors: &Connectors, - ) -> CustomResult { - let refund_amount = utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; - - let connector_router_data = coingate::CoingateRouterData::from((refund_amount, req)); - let connector_req = coingate::CoingateRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn build_request( - &self, - req: &RefundsRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - let request = RequestBuilder::new() - .method(Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - let response: coingate::RefundResponse = res - .response - .parse_struct("coingate RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "RefundSync".to_string(), + connector: "Coingate".to_string(), + } + .into()) } } impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundSyncRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: coingate::RefundResponse = res - .response - .parse_struct("coingate RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Refund".to_string(), + connector: "Coingate".to_string(), + } + .into()) } +} - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) +impl ConnectorValidation for Coingate { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + CaptureMethod::Automatic | CaptureMethod::SequentialAutomatic => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled | CaptureMethod::Manual => { + Err(utils::construct_not_supported_error_report( + capture_method, + self.id(), + )) + } + } } } diff --git a/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs index 831f30453ed..77128580c29 100644 --- a/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs @@ -1,31 +1,27 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use std::collections::HashMap; + +use common_enums::{enums, Currency}; +use common_utils::{request::Method, types::StringMajorUnit}; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_response_types::{PaymentsResponseData, RedirectForm}, + types::PaymentsAuthorizeRouterData, }; use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; -use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, -}; +use crate::{types::ResponseRouterData, utils}; -//TODO: Fill the struct with respective fields pub struct CoingateRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for CoingateRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for CoingateRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +29,15 @@ impl From<(StringMinorUnit, T)> for CoingateRouterData { } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, PartialEq)] pub struct CoingatePaymentsRequest { - amount: StringMinorUnit, - card: CoingateCard, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct CoingateCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + price_amount: StringMajorUnit, + price_currency: Currency, + receive_currency: String, + callback_url: Option, + success_url: Option, + cancel_url: Option, + title: String, } impl TryFrom<&CoingateRouterData<&PaymentsAuthorizeRouterData>> for CoingatePaymentsRequest { @@ -54,27 +45,23 @@ impl TryFrom<&CoingateRouterData<&PaymentsAuthorizeRouterData>> for CoingatePaym fn try_from( item: &CoingateRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = CoingateCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), - } + Ok(match item.router_data.request.payment_method_data { + PaymentMethodData::Crypto(_) => Ok(Self { + price_amount: item.amount.clone(), + price_currency: item.router_data.request.currency, + receive_currency: "DO_NOT_CONVERT".to_string(), + callback_url: item.router_data.request.router_return_url.clone(), + success_url: item.router_data.request.router_return_url.clone(), + cancel_url: item.router_data.request.router_return_url.clone(), + title: item.router_data.connector_request_reference_id.clone(), + }), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Coingate"), + )), + }?) } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct CoingateAuthType { pub(super) api_key: Secret, } @@ -90,32 +77,40 @@ impl TryFrom<&ConnectorAuthType> for CoingateAuthType { } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum CoingatePaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + New, + Pending, + Confirming, + Paid, + Invalid, + Expired, + Canceled, } impl From for common_enums::AttemptStatus { fn from(item: CoingatePaymentStatus) -> Self { match item { - CoingatePaymentStatus::Succeeded => Self::Charged, - CoingatePaymentStatus::Failed => Self::Failure, - CoingatePaymentStatus::Processing => Self::Authorizing, + CoingatePaymentStatus::Paid => Self::Charged, + CoingatePaymentStatus::Canceled + | CoingatePaymentStatus::Expired + | CoingatePaymentStatus::Invalid => Self::Failure, + CoingatePaymentStatus::Confirming | CoingatePaymentStatus::New => { + Self::AuthenticationPending + } + CoingatePaymentStatus::Pending => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct CoingatePaymentsResponse { status: CoingatePaymentStatus, - id: String, + id: i64, + payment_url: String, + order_id: String, } impl TryFrom> @@ -126,14 +121,18 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: enums::AttemptStatus::from(item.response.status.clone()), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), + resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), + redirection_data: Box::new(Some(RedirectForm::Form { + endpoint: item.response.payment_url.clone(), + method: Method::Get, + form_fields: HashMap::new(), + })), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.order_id.clone()), incremental_authorization_allowed: None, charges: None, }), @@ -141,88 +140,9 @@ impl TryFrom TryFrom<&CoingateRouterData<&RefundsRouterData>> for CoingateRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &CoingateRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - amount: item.amount.to_owned(), - }) - } -} - -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping - } - } -} - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, -} - -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } -} - -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } -} - -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct CoingateErrorResponse { pub status_code: u16, - pub code: String, pub message: String, - pub reason: Option, + pub reason: String, } diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource.rs b/crates/hyperswitch_connectors/src/connectors/cybersource.rs index 5a60f6d705a..812d0391dec 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource.rs @@ -312,6 +312,7 @@ impl ConnectorValidation for Cybersource { PaymentMethodDataType::Card, PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, + PaymentMethodDataType::SamsungPay, ]); utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index b953fa73754..4c65218c5a3 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -251,6 +251,11 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { )), Some(PaymentSolution::GooglePay), ), + WalletData::SamsungPay(samsung_pay_data) => ( + (get_samsung_pay_payment_information(&samsung_pay_data) + .attach_printable("Failed to get samsung pay payment information")?), + Some(PaymentSolution::SamsungPay), + ), WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) @@ -269,7 +274,6 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) - | WalletData::SamsungPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) @@ -1270,13 +1274,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); @@ -1353,13 +1357,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); @@ -1434,13 +1438,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); @@ -1859,25 +1863,8 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); - let samsung_pay_fluid_data_value = - get_samsung_pay_fluid_data_value(&samsung_pay_data.payment_credential.token_data)?; - - let samsung_pay_fluid_data_str = serde_json::to_string(&samsung_pay_fluid_data_value) - .change_context(errors::ConnectorError::RequestEncodingFailed) - .attach_printable("Failed to serialize samsung pay fluid data")?; - - let payment_information = - PaymentInformation::SamsungPay(Box::new(SamsungPayPaymentInformation { - fluid_data: FluidData { - value: Secret::new(consts::BASE64_ENGINE.encode(samsung_pay_fluid_data_str)), - descriptor: Some( - consts::BASE64_ENGINE.encode(FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY), - ), - }, - tokenized_card: SamsungPayTokenizedCard { - transaction_type: TransactionType::SamsungPay, - }, - })); + let payment_information = get_samsung_pay_payment_information(&samsung_pay_data) + .attach_printable("Failed to get samsung pay payment information")?; let processing_information = ProcessingInformation::try_from(( item, @@ -1903,6 +1890,32 @@ impl } } +fn get_samsung_pay_payment_information( + samsung_pay_data: &SamsungPayWalletData, +) -> Result> { + let samsung_pay_fluid_data_value = + get_samsung_pay_fluid_data_value(&samsung_pay_data.payment_credential.token_data)?; + + let samsung_pay_fluid_data_str = serde_json::to_string(&samsung_pay_fluid_data_value) + .change_context(errors::ConnectorError::RequestEncodingFailed) + .attach_printable("Failed to serialize samsung pay fluid data")?; + + let payment_information = + PaymentInformation::SamsungPay(Box::new(SamsungPayPaymentInformation { + fluid_data: FluidData { + value: Secret::new(consts::BASE64_ENGINE.encode(samsung_pay_fluid_data_str)), + descriptor: Some( + consts::BASE64_ENGINE.encode(FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY), + ), + }, + tokenized_card: SamsungPayTokenizedCard { + transaction_type: TransactionType::SamsungPay, + }, + })); + + Ok(payment_information) +} + fn get_samsung_pay_fluid_data_value( samsung_pay_token_data: &hyperswitch_domain_models::payment_method_data::SamsungPayTokenData, ) -> Result> { diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index 8a52433275e..9297f783d3e 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -93,6 +93,8 @@ pub enum TransactionStatus { Canceled, Transmitted, Failed, + ChallengeOngoing, + ChallengeRequired, } #[derive(Debug, Deserialize, Clone, Serialize)] @@ -127,7 +129,7 @@ pub struct SyncResponse { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct SyncCardDetails { - pub alias: String, + pub alias: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -189,12 +191,12 @@ pub enum ThreeDSecureData { #[serde(rename_all = "camelCase")] pub struct ThreeDSData { #[serde(rename = "threeDSTransactionId")] - pub three_ds_transaction_id: Secret, + pub three_ds_transaction_id: Option>, pub cavv: Secret, pub eci: Option, pub xid: Option>, #[serde(rename = "threeDSVersion")] - pub three_ds_version: String, + pub three_ds_version: Option, #[serde(rename = "authenticationResponse")] pub authentication_response: String, } @@ -452,11 +454,17 @@ fn create_card_details( if let Some(auth_data) = &item.router_data.request.authentication_data { details.three_ds = Some(ThreeDSecureData::Authentication(ThreeDSData { - three_ds_transaction_id: Secret::new(auth_data.threeds_server_transaction_id.clone()), + three_ds_transaction_id: auth_data + .threeds_server_transaction_id + .clone() + .map(Secret::new), cavv: Secret::new(auth_data.cavv.clone()), eci: auth_data.eci.clone(), xid: auth_data.ds_trans_id.clone().map(Secret::new), - three_ds_version: auth_data.message_version.to_string(), + three_ds_version: auth_data + .message_version + .clone() + .map(|version| version.to_string()), authentication_response: "Y".to_string(), })); } else if item.router_data.is_three_ds() { @@ -493,6 +501,9 @@ impl From for enums::AttemptStatus { TransactionType::Payment => match item.status { TransactionStatus::Authorized => Self::Authorized, TransactionStatus::Settled | TransactionStatus::Transmitted => Self::Charged, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::AuthenticationPending + } TransactionStatus::Canceled => Self::Voided, TransactionStatus::Failed => Self::Failure, TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, @@ -501,6 +512,9 @@ impl From for enums::AttemptStatus { TransactionStatus::Settled | TransactionStatus::Transmitted | TransactionStatus::Authorized => Self::Charged, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::AuthenticationPending + } TransactionStatus::Canceled => Self::Voided, TransactionStatus::Failed => Self::Failure, TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, @@ -515,6 +529,9 @@ impl From for enums::RefundStatus { match item.res_type { TransactionType::Credit => match item.status { TransactionStatus::Settled | TransactionStatus::Transmitted => Self::Success, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::Pending + } TransactionStatus::Initialized | TransactionStatus::Authenticated | TransactionStatus::Authorized @@ -770,9 +787,12 @@ impl TryFrom> connector_transaction_id: None, }) } else { - let mandate_reference = - sync_response.card.as_ref().map(|card| MandateReference { - connector_mandate_id: Some(card.alias.clone()), + let mandate_reference = sync_response + .card + .as_ref() + .and_then(|card| card.alias.as_ref()) + .map(|alias| MandateReference { + connector_mandate_id: Some(alias.clone()), payment_method_id: None, mandate_metadata: None, connector_mandate_request_reference_id: None, diff --git a/crates/hyperswitch_connectors/src/connectors/moneris.rs b/crates/hyperswitch_connectors/src/connectors/moneris.rs index d543a0be72c..46b0ee9472e 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris.rs @@ -1,10 +1,11 @@ pub mod transformers; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -21,8 +22,8 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -33,23 +34,27 @@ use hyperswitch_interfaces::{ configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, RefreshTokenType, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, PeekInterface}; use transformers as moneris; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, RefundsRequestData}, +}; #[derive(Clone)] pub struct Moneris { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Moneris { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &MinorUnitForConnector, } } } @@ -82,12 +87,31 @@ where req: &RouterData, _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let auth = moneris::MonerisAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + moneris::auth_headers::API_VERSION.to_string(), + "2024-09-17".to_string().into(), + ), + ( + moneris::auth_headers::X_MERCHANT_ID.to_string(), + auth.merchant_id.expose().into_masked(), + ), + ]; + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.peek()).into_masked(), + ); + header.push(auth_header); Ok(header) } } @@ -117,7 +141,7 @@ impl ConnectorCommon for Moneris { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth.client_id.expose().into_masked(), )]) } @@ -134,11 +158,20 @@ impl ConnectorCommon for Moneris { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + let reason = match &response.errors { + Some(error_list) => error_list + .iter() + .map(|error| error.parameter_name.clone()) + .collect::>() + .join(" & "), + None => response.title.clone(), + }; + Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.category, + message: response.title, + reason: Some(reason), attempt_status: None, connector_transaction_id: None, }) @@ -146,16 +179,133 @@ impl ConnectorCommon for Moneris { } impl ConnectorValidation for Moneris { - //TODO: implement functions when support enabled + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled + | enums::CaptureMethod::SequentialAutomatic => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } } impl ConnectorIntegration for Moneris { //TODO: implement sessions flow } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/oauth2/token", self.base_url(connectors))) + } + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + fn get_headers( + &self, + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + RefreshTokenType::get_content_type(self).to_string().into(), + )]) + } + + fn get_request_body( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisAuthRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .url(&RefreshTokenType::get_url(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisAuthResponse = res + .response + .parse_struct("Moneris MonerisAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } -impl ConnectorIntegration for Moneris {} + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + // auth error have different structure than common error + let response: moneris::MonerisAuthErrorResponse = res + .response + .parse_struct("MonerisAuthErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.to_string(), + message: response.error.clone(), + reason: response.error_description, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorIntegration for Moneris { + // Not Implemented (R) + fn build_request( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Moneris".to_string()) + .into(), + ) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -173,9 +323,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payments", self.base_url(connectors))) } fn get_request_body( @@ -259,10 +409,18 @@ impl ConnectorIntegration for Mon fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/payments/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -323,18 +481,31 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/complete", + self.base_url(connectors) + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = moneris::MonerisRouterData::from((amount, req)); + let connector_req = + moneris::MonerisPaymentsCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -385,7 +556,77 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/cancel", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("Moneris PaymentsCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -403,9 +644,9 @@ impl ConnectorIntegration for Moneris fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds", self.base_url(connectors))) } fn get_request_body( @@ -486,10 +727,11 @@ impl ConnectorIntegration for Moneris { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req.request.get_connector_refund_id()?; + Ok(format!("{}/refunds/{refund_id}", self.base_url(connectors))) } fn build_request( diff --git a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs index 0cc255d85bf..b8fb3d0fc51 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs @@ -1,31 +1,38 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::MinorUnit; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{RefreshTokenRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + BrowserInformationData, CardData as _, PaymentsAuthorizeRequestData, + RouterData as OtherRouterData, + }, }; -//TODO: Fill the struct with respective fields +const CLIENT_CREDENTIALS: &str = "client_credentials"; + pub struct MonerisRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for MonerisRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(MinorUnit, T)> for MonerisRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +40,47 @@ impl From<(StringMinorUnit, T)> for MonerisRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] +pub mod auth_headers { + pub const X_MERCHANT_ID: &str = "X-Merchant-Id"; + pub const API_VERSION: &str = "Api-Version"; +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsRequest { - amount: StringMinorUnit, + idempotency_key: String, + amount: Amount, + payment_method: PaymentMethod, + automatic_capture: bool, + ipv4: Secret, +} +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + currency: enums::Currency, + amount: MinorUnit, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethod { + payment_method_source: PaymentMethodSource, card: MonerisCard, } +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaymentMethodSource { + Card, +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + card_number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + card_security_code: Secret, } impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest { @@ -54,18 +88,51 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen fn try_from( item: &MonerisRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "Moneris", + })? + }; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = MonerisCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, + PaymentMethodData::Card(ref req_card) => { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, }; + let payment_method = PaymentMethod { + payment_method_source: PaymentMethodSource::Card, + card: MonerisCard { + card_number: req_card.card_number.clone(), + expiry_month: Secret::new( + req_card + .card_exp_month + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + expiry_year: Secret::new( + req_card + .get_expiry_year_4_digit() + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + card_security_code: req_card.card_cvc.clone(), + }, + }; + let automatic_capture = item.router_data.request.is_auto_capture()?; + + let browser_info = item.router_data.request.get_browser_info()?; + let ipv4 = browser_info.get_ip_address()?; + Ok(Self { - amount: item.amount.clone(), - card, + idempotency_key, + amount, + payment_method, + automatic_capture, + ipv4, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -73,49 +140,106 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct MonerisAuthType { - pub(super) api_key: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, + pub(super) merchant_id: Secret, } impl TryFrom<&ConnectorAuthType> for MonerisAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + client_id: key1.to_owned(), + client_secret: api_key.to_owned(), + merchant_id: api_secret.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct MonerisAuthRequest { + client_id: Secret, + client_secret: Secret, + grant_type: String, +} + +impl TryFrom<&RefreshTokenRouterData> for MonerisAuthRequest { + type Error = error_stack::Report; + fn try_from(item: &RefreshTokenRouterData) -> Result { + let auth = MonerisAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + client_id: auth.client_id.clone(), + client_secret: auth.client_secret.clone(), + grant_type: CLIENT_CREDENTIALS.to_string(), + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MonerisAuthResponse { + access_token: Secret, + token_type: String, + expires_in: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(AccessToken { + token: item.response.access_token, + expires: item + .response + .expires_in + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, + }), + ..item.data + }) + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum MonerisPaymentStatus { Succeeded, - Failed, #[default] Processing, + Canceled, + Declined, + DeclinedRetry, + Authorized, } impl From for common_enums::AttemptStatus { fn from(item: MonerisPaymentStatus) -> Self { match item { MonerisPaymentStatus::Succeeded => Self::Charged, - MonerisPaymentStatus::Failed => Self::Failure, - MonerisPaymentStatus::Processing => Self::Authorizing, + MonerisPaymentStatus::Authorized => Self::Authorized, + MonerisPaymentStatus::Canceled => Self::Voided, + MonerisPaymentStatus::Declined | MonerisPaymentStatus::DeclinedRetry => Self::Failure, + MonerisPaymentStatus::Processing => Self::Pending, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsResponse { - status: MonerisPaymentStatus, - id: String, + payment_status: MonerisPaymentStatus, + payment_id: String, } impl TryFrom> @@ -126,14 +250,14 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: common_enums::AttemptStatus::from(item.response.payment_status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::ConnectorTransactionId(item.response.payment_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.payment_id), incremental_authorization_allowed: None, charges: None, }), @@ -142,50 +266,101 @@ impl TryFrom> for MonerisPaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &MonerisRouterData<&PaymentsCaptureRouterData>) -> Result { + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + Ok(Self { + amount, + idempotency_key, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MonerisCancelRequest { + idempotency_key: String, + reason: Option, +} + +impl TryFrom<&PaymentsCancelRouterData> for MonerisCancelRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelRouterData) -> Result { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.request.cancellation_reason.clone(); + Ok(Self { + idempotency_key, + reason, + }) + } +} + #[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct MonerisRefundRequest { - pub amount: StringMinorUnit, + pub refund_amount: Amount, + pub idempotency_key: String, + pub reason: Option, + pub payment_id: String, } impl TryFrom<&MonerisRouterData<&RefundsRouterData>> for MonerisRefundRequest { type Error = error_stack::Report; fn try_from(item: &MonerisRouterData<&RefundsRouterData>) -> Result { + let refund_amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.router_data.request.reason.clone(); + let payment_id = item.router_data.request.connector_transaction_id.clone(); Ok(Self { - amount: item.amount.to_owned(), + refund_amount, + idempotency_key, + reason, + payment_id, }) } } -// Type definition for Refund Response - #[allow(dead_code)] #[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RefundStatus { Succeeded, - Failed, #[default] Processing, + Declined, + DeclinedRetry, } impl From for enums::RefundStatus { fn from(item: RefundStatus) -> Self { match item { RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, + RefundStatus::Declined | RefundStatus::DeclinedRetry => Self::Failure, RefundStatus::Processing => Self::Pending, - //TODO: Review mapping } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + refund_id: String, + refund_status: RefundStatus, } impl TryFrom> for RefundsRouterData { @@ -195,8 +370,8 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) @@ -210,19 +385,32 @@ impl TryFrom> for RefundsRouter ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub status: u16, + pub category: String, + pub title: String, + pub errors: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MonerisError { + pub reason_code: String, + pub parameter_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MonerisAuthErrorResponse { + pub error: String, + pub error_description: Option, } diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs index 55f3528bbc4..ac634d58aea 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs @@ -13,19 +13,23 @@ use hyperswitch_domain_models::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, refunds::{Execute, RSync}, - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, + types::{ + UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData, + UasPreAuthenticationRouterData, + }, }; use hyperswitch_interfaces::{ api::{ @@ -71,6 +75,7 @@ impl api::PaymentToken for UnifiedAuthenticationService {} impl api::UnifiedAuthenticationService for UnifiedAuthenticationService {} impl api::UasPreAuthentication for UnifiedAuthenticationService {} impl api::UasPostAuthentication for UnifiedAuthenticationService {} +impl api::UasAuthenticationConfirmation for UnifiedAuthenticationService {} impl api::UasAuthentication for UnifiedAuthenticationService {} impl ConnectorIntegration @@ -168,6 +173,105 @@ impl ConnectorIntegration for UnifiedAuthenticationService +{ + fn get_headers( + &self, + req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}confirmation", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &UasAuthenticationConfirmationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.transaction_amount, + req.request.transaction_currency, + )?; + + let connector_router_data = + unified_authentication_service::UnifiedAuthenticationServiceRouterData::from(( + amount, req, + )); + let connector_req = + unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationRequest::try_from( + &connector_router_data, + )?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::UasAuthenticationConfirmationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::UasAuthenticationConfirmationType::get_headers( + self, req, connectors, + )?) + .set_body(types::UasAuthenticationConfirmationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &UasAuthenticationConfirmationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationResponse = + res.response + .parse_struct("UnifiedAuthenticationService UnifiedAuthenticationServiceAuthenticateConfirmationResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration< PreAuthenticate, 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 f2ca77d746f..c5d0968fd57 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs @@ -6,7 +6,10 @@ use hyperswitch_domain_models::{ DynamicData, PostAuthenticationDetails, PreAuthenticationDetails, TokenDetails, UasAuthenticationResponseData, }, - types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, + types::{ + UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData, + UasPreAuthenticationRouterData, + }, }; use hyperswitch_interfaces::errors; use masking::Secret; @@ -46,6 +49,56 @@ pub struct UnifiedAuthenticationServicePreAuthenticateRequest { pub transaction_details: Option, } +#[derive(Debug, Serialize, PartialEq)] +pub struct UnifiedAuthenticationServiceAuthenticateConfirmationRequest { + pub authenticate_by: String, + pub source_authentication_id: String, + pub auth_creds: AuthType, + pub x_src_flow_id: Option, + pub transaction_amount: Option, + pub transaction_currency: Option, + pub checkout_event_type: Option, + pub checkout_event_status: Option, + pub confirmation_status: Option, + pub confirmation_reason: Option, + pub confirmation_timestamp: Option, + pub network_authorization_code: Option, + pub network_transaction_identifier: Option, + pub correlation_id: Option, + pub merchant_transaction_id: Option, +} + +#[derive(Debug, Serialize, PartialEq, Deserialize)] +pub struct UnifiedAuthenticationServiceAuthenticateConfirmationResponse { + status: String, +} + +impl + TryFrom< + ResponseRouterData< + F, + UnifiedAuthenticationServiceAuthenticateConfirmationResponse, + T, + UasAuthenticationResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + UnifiedAuthenticationServiceAuthenticateConfirmationResponse, + T, + UasAuthenticationResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(UasAuthenticationResponseData::Confirmation {}), + ..item.data + }) + } +} + #[derive(Debug, Serialize, PartialEq)] #[serde(tag = "auth_type")] pub enum AuthType { @@ -443,3 +496,43 @@ impl pub struct UnifiedAuthenticationServiceErrorResponse { pub error: String, } + +impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>> + for UnifiedAuthenticationServiceAuthenticateConfirmationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>, + ) -> Result { + let auth_type = + UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?; + let authentication_id = item.router_data.authentication_id.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "authentication_id", + }, + )?; + Ok(Self { + authenticate_by: item.router_data.connector.clone(), + auth_creds: AuthType::HeaderKey { + api_key: auth_type.api_key, + }, + source_authentication_id: authentication_id, + x_src_flow_id: item.router_data.request.x_src_flow_id.clone(), + transaction_amount: Some(item.amount), + transaction_currency: Some(item.router_data.request.transaction_currency), + checkout_event_type: item.router_data.request.checkout_event_type.clone(), + checkout_event_status: item.router_data.request.checkout_event_status.clone(), + confirmation_status: item.router_data.request.confirmation_status.clone(), + confirmation_reason: item.router_data.request.confirmation_reason.clone(), + confirmation_timestamp: item.router_data.request.confirmation_timestamp, + network_authorization_code: item.router_data.request.network_authorization_code.clone(), + network_transaction_identifier: item + .router_data + .request + .network_transaction_identifier + .clone(), + correlation_id: item.router_data.request.correlation_id.clone(), + merchant_transaction_id: item.router_data.request.merchant_transaction_id.clone(), + }) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index f046b8de707..10c381fa857 100644 --- a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -941,13 +941,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); diff --git a/crates/hyperswitch_connectors/src/connectors/xendit.rs b/crates/hyperswitch_connectors/src/connectors/xendit.rs index dae6b8fd88f..c45f637f48a 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit.rs @@ -14,18 +14,22 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, SplitRefundsRequest, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -61,6 +65,7 @@ impl Xendit { } } impl api::Payment for Xendit {} +impl api::PaymentsPreProcessing for Xendit {} impl api::PaymentSession for Xendit {} impl api::ConnectorAccessToken for Xendit {} impl api::MandateSetup for Xendit {} @@ -121,6 +126,7 @@ impl ConnectorCommon for Xendit { let auth = xendit::XenditAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -203,7 +209,46 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + if let Ok(PaymentsResponseData::TransactionResponse { + charges: + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits( + xendit_response, + ), + )), + .. + }) = req.response.as_ref() + { + headers.push(( + xendit::auth_headers::WITH_SPLIT_RULE.to_string(), + xendit_response.split_rule_id.clone().into(), + )); + if let Some(for_user_id) = &xendit_response.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -280,13 +325,121 @@ impl ConnectorIntegration + for Xendit +{ + fn get_headers( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/split_rules", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = xendit::XenditSplitRequestData::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &PaymentsPreProcessingRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditSplitResponse = res + .response + .parse_struct("XenditSplitResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Xendit { fn get_headers( &self, req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -353,7 +506,31 @@ impl ConnectorIntegration fo req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -453,7 +630,17 @@ impl ConnectorIntegration for Xendit req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -539,7 +726,17 @@ impl ConnectorIntegration for Xendit { req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs index 7a7789e73ab..79ac7312350 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -7,13 +7,15 @@ use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::{PaymentsAuthorizeData, PaymentsCaptureData, ResponseId}, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCaptureData, PaymentsPreProcessingData, ResponseId, + }, router_response_types::{ MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -89,6 +91,38 @@ pub struct XenditPaymentsRequest { pub channel_properties: Option, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRoute { + #[serde(skip_serializing_if = "Option::is_none")] + pub flat_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub percent_amount: Option, + pub currency: enums::Currency, + pub destination_account_id: String, + pub reference_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRequest { + pub name: String, + pub description: String, + pub routes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitRequestData { + #[serde(flatten)] + pub split_data: XenditSplitRequest, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitResponse { + id: String, + name: String, + description: String, + routes: Vec, +} + #[derive(Serialize, Deserialize, Debug)] pub struct CardInfo { pub channel_properties: ChannelProperties, @@ -104,6 +138,11 @@ pub struct CardInformation { pub cardholder_email: pii::Email, pub cardholder_phone_number: Secret, } +pub mod auth_headers { + pub const WITH_SPLIT_RULE: &str = "with-split-rule"; + pub const FOR_USER_ID: &str = "for-user-id"; +} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionType { @@ -288,6 +327,35 @@ impl status_code: item.http_code, }) } else { + let charges = match item.data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => item + .data + .response + .as_ref() + .ok() + .and_then(|response| match response { + PaymentsResponseData::TransactionResponse { charges, .. } => { + charges.clone() + } + _ => None, + }), + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(ref split_data), + )) => { + let charges = common_types::domain::XenditSplitSubMerchantData { + for_user_id: split_data.for_user_id.clone(), + }; + Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::SingleSplit(charges), + ), + ) + } + _ => None, + }; + Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: match item.response.actions { @@ -320,7 +388,7 @@ impl item.response.reference_id.peek().to_string(), ), incremental_authorization_allowed: None, - charges: None, + charges, }) }; Ok(Self { @@ -388,6 +456,90 @@ impl }) } } + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: ResponseRouterData< + F, + XenditSplitResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + ) -> Result { + let for_user_id = match item.data.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) => split_data.for_user_id.clone(), + _ => None, + }; + + let routes: Vec = item + .response + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert_back( + &required_conversion_type, + amount, + item.data.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| common_types::payments::XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let charges = common_types::payments::XenditMultipleSplitResponse { + split_rule_id: item.response.id, + for_user_id, + name: item.response.name, + description: item.response.description, + routes, + }; + + let response = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits(charges), + ), + ), + }; + + Ok(Self { + response: Ok(response), + ..item.data + }) + } +} + impl TryFrom> for PaymentsSyncRouterData { type Error = error_stack::Report; fn try_from( @@ -461,6 +613,59 @@ impl TryFrom<&ConnectorAuthType> for XenditAuthType { } } +impl TryFrom<&PaymentsPreProcessingRouterData> for XenditSplitRequestData { + type Error = error_stack::Report; + fn try_from(item: &PaymentsPreProcessingRouterData) -> Result { + if let Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) = item.request.split_payments.clone() + { + let routes: Vec = split_data + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert( + &required_conversion_type, + amount, + item.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let split_data = XenditSplitRequest { + name: split_data.name.clone(), + description: split_data.description.clone(), + routes, + }; + + Ok(Self { split_data }) + } else { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Xendit"), + ) + .into()) + } + } +} + //TODO: Fill the struct with respective fields // REFUND : // Type definition for RefundRequest @@ -538,3 +743,8 @@ impl TryFrom> for RefundsRouter }) } } + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct XenditMetadata { + pub for_user_id: String, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index a88de8331c3..b2df2d15f71 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -33,12 +33,13 @@ use hyperswitch_domain_models::{ PreProcessing, Reject, SdkSessionUpdate, }, webhooks::VerifyWebhookSource, - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, @@ -75,7 +76,8 @@ use hyperswitch_interfaces::{ PaymentsPreProcessing, TaxCalculation, }, ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, UasAuthentication, - UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, + UasAuthenticationConfirmation, UasPostAuthentication, UasPreAuthentication, + UnifiedAuthenticationService, }, errors::ConnectorError, }; @@ -814,7 +816,6 @@ default_imp_for_pre_processing_steps!( connectors::Worldpay, connectors::Wellsfargo, connectors::Volt, - connectors::Xendit, connectors::Zen, connectors::Zsl, connectors::CtpMastercard @@ -3099,3 +3100,89 @@ default_imp_for_uas_authentication!( connectors::Zen, connectors::Zsl ); + +macro_rules! default_imp_for_uas_authentication_confirmation { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthenticationConfirmation for $path::$connector {} + impl + ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData + + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_authentication_confirmation!( + connectors::Aci, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Billwerk, + connectors::Bluesnap, + connectors::Bitpay, + connectors::Braintree, + connectors::Boku, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Cybersource, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Getnet, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Klarna, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Nuvei, + connectors::Payeezy, + connectors::Payu, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Multisafepay, + connectors::Paybox, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, + connectors::Taxjar, + connectors::Thunes, + connectors::Tsys, + connectors::Worldline, + connectors::Worldpay, + connectors::Wellsfargo, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl +); diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 4ed91f5ffdb..05657216d9d 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -8,18 +8,21 @@ use common_enums::{ enums, enums::{ AlbaniaStatesAbbreviation, AndorraStatesAbbreviation, AttemptStatus, - AustriaStatesAbbreviation, BelarusStatesAbbreviation, + AustriaStatesAbbreviation, BelarusStatesAbbreviation, BelgiumStatesAbbreviation, BosniaAndHerzegovinaStatesAbbreviation, BulgariaStatesAbbreviation, CanadaStatesAbbreviation, CroatiaStatesAbbreviation, CzechRepublicStatesAbbreviation, DenmarkStatesAbbreviation, FinlandStatesAbbreviation, FranceStatesAbbreviation, FutureUsage, GermanyStatesAbbreviation, GreeceStatesAbbreviation, HungaryStatesAbbreviation, IcelandStatesAbbreviation, IrelandStatesAbbreviation, ItalyStatesAbbreviation, LatviaStatesAbbreviation, LiechtensteinStatesAbbreviation, - LithuaniaStatesAbbreviation, MaltaStatesAbbreviation, MoldovaStatesAbbreviation, - MonacoStatesAbbreviation, MontenegroStatesAbbreviation, NetherlandsStatesAbbreviation, - NorthMacedoniaStatesAbbreviation, NorwayStatesAbbreviation, PolandStatesAbbreviation, - PortugalStatesAbbreviation, RomaniaStatesAbbreviation, SpainStatesAbbreviation, - SwitzerlandStatesAbbreviation, UnitedKingdomStatesAbbreviation, UsStatesAbbreviation, + LithuaniaStatesAbbreviation, LuxembourgStatesAbbreviation, MaltaStatesAbbreviation, + MoldovaStatesAbbreviation, MonacoStatesAbbreviation, MontenegroStatesAbbreviation, + NetherlandsStatesAbbreviation, NorthMacedoniaStatesAbbreviation, NorwayStatesAbbreviation, + PolandStatesAbbreviation, PortugalStatesAbbreviation, RomaniaStatesAbbreviation, + RussiaStatesAbbreviation, SanMarinoStatesAbbreviation, SerbiaStatesAbbreviation, + SlovakiaStatesAbbreviation, SloveniaStatesAbbreviation, SpainStatesAbbreviation, + SwedenStatesAbbreviation, SwitzerlandStatesAbbreviation, UkraineStatesAbbreviation, + UnitedKingdomStatesAbbreviation, UsStatesAbbreviation, }, }; use common_utils::{ @@ -2462,36 +2465,31 @@ impl ForeignTryFrom for PolandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "PolandStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "PolandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "greater poland voivodeship" => Ok(Self::GreaterPolandVoivodeship), - "kielce" => Ok(Self::Kielce), - "kuyavian pomeranian voivodeship" => Ok(Self::KuyavianPomeranianVoivodeship), - "lesser poland voivodeship" => Ok(Self::LesserPolandVoivodeship), - "lower silesian voivodeship" => Ok(Self::LowerSilesianVoivodeship), - "lublin voivodeship" => Ok(Self::LublinVoivodeship), - "lubusz voivodeship" => Ok(Self::LubuszVoivodeship), - "masovian voivodeship" => Ok(Self::MasovianVoivodeship), - "opole voivodeship" => Ok(Self::OpoleVoivodeship), - "podkarpackie voivodeship" => Ok(Self::PodkarpackieVoivodeship), - "podlaskie voivodeship" => Ok(Self::PodlaskieVoivodeship), - "pomeranian voivodeship" => Ok(Self::PomeranianVoivodeship), - "silesian voivodeship" => Ok(Self::SilesianVoivodeship), - "warmian masurian voivodeship" => Ok(Self::WarmianMasurianVoivodeship), - "west pomeranian voivodeship" => Ok(Self::WestPomeranianVoivodeship), - "lodz voivodeship" => Ok(Self::LodzVoivodeship), - "swietokrzyskie voivodeship" => Ok(Self::SwietokrzyskieVoivodeship), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Greater Poland" => Ok(Self::GreaterPoland), + "Holy Cross" => Ok(Self::HolyCross), + "Kuyavia-Pomerania" => Ok(Self::KuyaviaPomerania), + "Lesser Poland" => Ok(Self::LesserPoland), + "Lower Silesia" => Ok(Self::LowerSilesia), + "Lublin" => Ok(Self::Lublin), + "Lubusz" => Ok(Self::Lubusz), + "Łódź" => Ok(Self::Łódź), + "Mazovia" => Ok(Self::Mazovia), + "Podlaskie" => Ok(Self::Podlaskie), + "Pomerania" => Ok(Self::Pomerania), + "Silesia" => Ok(Self::Silesia), + "Subcarpathia" => Ok(Self::Subcarpathia), + "Upper Silesia" => Ok(Self::UpperSilesia), + "Warmia-Masuria" => Ok(Self::WarmiaMasuria), + "West Pomerania" => Ok(Self::WestPomerania), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2500,49 +2498,138 @@ impl ForeignTryFrom for FranceStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "FranceStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "FranceStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "alo" => Ok(Self::Alo), - "alsace" => Ok(Self::Alsace), - "aquitaine" => Ok(Self::Aquitaine), - "auvergne" => Ok(Self::Auvergne), - "auvergne rhone alpes" => Ok(Self::AuvergneRhoneAlpes), - "bourgogne franche comte" => Ok(Self::BourgogneFrancheComte), - "brittany" => Ok(Self::Brittany), - "burgundy" => Ok(Self::Burgundy), - "centre val de loire" => Ok(Self::CentreValDeLoire), - "champagne ardenne" => Ok(Self::ChampagneArdenne), - "corsica" => Ok(Self::Corsica), - "franche comte" => Ok(Self::FrancheComte), - "french guiana" => Ok(Self::FrenchGuiana), - "french polynesia" => Ok(Self::FrenchPolynesia), - "grand est" => Ok(Self::GrandEst), - "guadeloupe" => Ok(Self::Guadeloupe), - "hauts de france" => Ok(Self::HautsDeFrance), - "ile de france" => Ok(Self::IleDeFrance), - "normandy" => Ok(Self::Normandy), - "nouvelle aquitaine" => Ok(Self::NouvelleAquitaine), - "occitania" => Ok(Self::Occitania), - "paris" => Ok(Self::Paris), - "pays de la loire" => Ok(Self::PaysDeLaLoire), - "provence alpes cote d azur" => Ok(Self::ProvenceAlpesCoteDAzur), - "reunion" => Ok(Self::Reunion), - "saint barthelemy" => Ok(Self::SaintBarthelemy), - "saint martin" => Ok(Self::SaintMartin), - "saint pierre and miquelon" => Ok(Self::SaintPierreAndMiquelon), - "upper normandy" => Ok(Self::UpperNormandy), - "wallis and futuna" => Ok(Self::WallisAndFutuna), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Ain" => Ok(Self::Ain), + "Aisne" => Ok(Self::Aisne), + "Allier" => Ok(Self::Allier), + "Alpes-de-Haute-Provence" => Ok(Self::AlpesDeHauteProvence), + "Alpes-Maritimes" => Ok(Self::AlpesMaritimes), + "Alsace" => Ok(Self::Alsace), + "Ardèche" => Ok(Self::Ardeche), + "Ardennes" => Ok(Self::Ardennes), + "Ariège" => Ok(Self::Ariege), + "Aube" => Ok(Self::Aube), + "Aude" => Ok(Self::Aude), + "Auvergne-Rhône-Alpes" => Ok(Self::AuvergneRhoneAlpes), + "Aveyron" => Ok(Self::Aveyron), + "Bas-Rhin" => Ok(Self::BasRhin), + "Bouches-du-Rhône" => Ok(Self::BouchesDuRhone), + "Bourgogne-Franche-Comté" => Ok(Self::BourgogneFrancheComte), + "Bretagne" => Ok(Self::Bretagne), + "Calvados" => Ok(Self::Calvados), + "Cantal" => Ok(Self::Cantal), + "Centre-Val de Loire" => Ok(Self::CentreValDeLoire), + "Charente" => Ok(Self::Charente), + "Charente-Maritime" => Ok(Self::CharenteMaritime), + "Cher" => Ok(Self::Cher), + "Clipperton" => Ok(Self::Clipperton), + "Corrèze" => Ok(Self::Correze), + "Corse" => Ok(Self::Corse), + "Corse-du-Sud" => Ok(Self::CorseDuSud), + "Côte-d'Or" => Ok(Self::CoteDor), + "Côtes-d'Armor" => Ok(Self::CotesDarmor), + "Creuse" => Ok(Self::Creuse), + "Deux-Sèvres" => Ok(Self::DeuxSevres), + "Dordogne" => Ok(Self::Dordogne), + "Doubs" => Ok(Self::Doubs), + "Drôme" => Ok(Self::Drome), + "Essonne" => Ok(Self::Essonne), + "Eure" => Ok(Self::Eure), + "Eure-et-Loir" => Ok(Self::EureEtLoir), + "Finistère" => Ok(Self::Finistere), + "French Guiana" => Ok(Self::FrenchGuiana), + "French Polynesia" => Ok(Self::FrenchPolynesia), + "French Southern and Antarctic Lands" => Ok(Self::FrenchSouthernAndAntarcticLands), + "Gard" => Ok(Self::Gard), + "Gers" => Ok(Self::Gers), + "Gironde" => Ok(Self::Gironde), + "Grand-Est" => Ok(Self::GrandEst), + "Guadeloupe" => Ok(Self::Guadeloupe), + "Haut-Rhin" => Ok(Self::HautRhin), + "Haute-Corse" => Ok(Self::HauteCorse), + "Haute-Garonne" => Ok(Self::HauteGaronne), + "Haute-Loire" => Ok(Self::HauteLoire), + "Haute-Marne" => Ok(Self::HauteMarne), + "Haute-Saône" => Ok(Self::HauteSaone), + "Haute-Savoie" => Ok(Self::HauteSavoie), + "Haute-Vienne" => Ok(Self::HauteVienne), + "Hautes-Alpes" => Ok(Self::HautesAlpes), + "Hautes-Pyrénées" => Ok(Self::HautesPyrenees), + "Hauts-de-France" => Ok(Self::HautsDeFrance), + "Hauts-de-Seine" => Ok(Self::HautsDeSeine), + "Hérault" => Ok(Self::Herault), + "Île-de-France" => Ok(Self::IleDeFrance), + "Ille-et-Vilaine" => Ok(Self::IlleEtVilaine), + "Indre" => Ok(Self::Indre), + "Indre-et-Loire" => Ok(Self::IndreEtLoire), + "Isère" => Ok(Self::Isere), + "Jura" => Ok(Self::Jura), + "La Réunion" => Ok(Self::LaReunion), + "Landes" => Ok(Self::Landes), + "Loir-et-Cher" => Ok(Self::LoirEtCher), + "Loire" => Ok(Self::Loire), + "Loire-Atlantique" => Ok(Self::LoireAtlantique), + "Loiret" => Ok(Self::Loiret), + "Lot" => Ok(Self::Lot), + "Lot-et-Garonne" => Ok(Self::LotEtGaronne), + "Lozère" => Ok(Self::Lozere), + "Maine-et-Loire" => Ok(Self::MaineEtLoire), + "Manche" => Ok(Self::Manche), + "Marne" => Ok(Self::Marne), + "Martinique" => Ok(Self::Martinique), + "Mayenne" => Ok(Self::Mayenne), + "Mayotte" => Ok(Self::Mayotte), + "Métropole de Lyon" => Ok(Self::MetropoleDeLyon), + "Meurthe-et-Moselle" => Ok(Self::MeurtheEtMoselle), + "Meuse" => Ok(Self::Meuse), + "Morbihan" => Ok(Self::Morbihan), + "Moselle" => Ok(Self::Moselle), + "Nièvre" => Ok(Self::Nievre), + "Nord" => Ok(Self::Nord), + "Normandie" => Ok(Self::Normandie), + "Nouvelle-Aquitaine" => Ok(Self::NouvelleAquitaine), + "Occitanie" => Ok(Self::Occitanie), + "Oise" => Ok(Self::Oise), + "Orne" => Ok(Self::Orne), + "Paris" => Ok(Self::Paris), + "Pas-de-Calais" => Ok(Self::PasDeCalais), + "Pays-de-la-Loire" => Ok(Self::PaysDeLaLoire), + "Provence-Alpes-Côte-d'Azur" => Ok(Self::ProvenceAlpesCoteDazur), + "Puy-de-Dôme" => Ok(Self::PuyDeDome), + "Pyrénées-Atlantiques" => Ok(Self::PyreneesAtlantiques), + "Pyrénées-Orientales" => Ok(Self::PyreneesOrientales), + "Rhône" => Ok(Self::Rhone), + "Saint Pierre and Miquelon" => Ok(Self::SaintPierreAndMiquelon), + "Saint-Barthélemy" => Ok(Self::SaintBarthelemy), + "Saint-Martin" => Ok(Self::SaintMartin), + "Saône-et-Loire" => Ok(Self::SaoneEtLoire), + "Sarthe" => Ok(Self::Sarthe), + "Savoie" => Ok(Self::Savoie), + "Seine-et-Marne" => Ok(Self::SeineEtMarne), + "Seine-Maritime" => Ok(Self::SeineMaritime), + "Seine-Saint-Denis" => Ok(Self::SeineSaintDenis), + "Somme" => Ok(Self::Somme), + "Tarn" => Ok(Self::Tarn), + "Tarn-et-Garonne" => Ok(Self::TarnEtGaronne), + "Territoire de Belfort" => Ok(Self::TerritoireDeBelfort), + "Val-d'Oise" => Ok(Self::ValDoise), + "Val-de-Marne" => Ok(Self::ValDeMarne), + "Var" => Ok(Self::Var), + "Vaucluse" => Ok(Self::Vaucluse), + "Vendée" => Ok(Self::Vendee), + "Vienne" => Ok(Self::Vienne), + "Vosges" => Ok(Self::Vosges), + "Wallis and Futuna" => Ok(Self::WallisAndFutuna), + "Yonne" => Ok(Self::Yonne), + "Yvelines" => Ok(Self::Yvelines), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2550,38 +2637,32 @@ impl ForeignTryFrom for FranceStatesAbbreviation { impl ForeignTryFrom for GermanyStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "GermanyStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "GermanyStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "baden wurttemberg" => Ok(Self::BadenWurttemberg), - "bavaria" => Ok(Self::Bavaria), - "berlin" => Ok(Self::Berlin), - "brandenburg" => Ok(Self::Brandenburg), - "bremen" => Ok(Self::Bremen), - "hamburg" => Ok(Self::Hamburg), - "hesse" => Ok(Self::Hesse), - "lower saxony" => Ok(Self::LowerSaxony), - "mecklenburg vorpommern" => Ok(Self::MecklenburgVorpommern), - "north rhine westphalia" => Ok(Self::NorthRhineWestphalia), - "rhineland palatinate" => Ok(Self::RhinelandPalatinate), - "saarland" => Ok(Self::Saarland), - "saxony" => Ok(Self::Saxony), - "saxony anhalt" => Ok(Self::SaxonyAnhalt), - "schleswig holstein" => Ok(Self::SchleswigHolstein), - "thuringia" => Ok(Self::Thuringia), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Baden-Württemberg" => Ok(Self::BW), + "Bavaria" => Ok(Self::BY), + "Berlin" => Ok(Self::BE), + "Brandenburg" => Ok(Self::BB), + "Bremen" => Ok(Self::HB), + "Hamburg" => Ok(Self::HH), + "Hessen" => Ok(Self::HE), + "Lower Saxony" => Ok(Self::NI), + "Mecklenburg-Vorpommern" => Ok(Self::MV), + "North Rhine-Westphalia" => Ok(Self::NW), + "Rhineland-Palatinate" => Ok(Self::RP), + "Saarland" => Ok(Self::SL), + "Saxony" => Ok(Self::SN), + "Saxony-Anhalt" => Ok(Self::ST), + "Schleswig-Holstein" => Ok(Self::SH), + "Thuringia" => Ok(Self::TH), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2590,83 +2671,79 @@ impl ForeignTryFrom for SpainStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "SpainStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "SpainStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "a coruna province" => Ok(Self::ACorunaProvince), - "albacete province" => Ok(Self::AlbaceteProvince), - "alicante province" => Ok(Self::AlicanteProvince), - "almeria province" => Ok(Self::AlmeriaProvince), - "andalusia" => Ok(Self::Andalusia), - "araba alava" => Ok(Self::ArabaAlava), - "aragon" => Ok(Self::Aragon), - "badajoz province" => Ok(Self::BadajozProvince), - "balearic islands" => Ok(Self::BalearicIslands), - "barcelona province" => Ok(Self::BarcelonaProvince), - "basque country" => Ok(Self::BasqueCountry), - "biscay" => Ok(Self::Biscay), - "burgos province" => Ok(Self::BurgosProvince), - "canary islands" => Ok(Self::CanaryIslands), - "cantabria" => Ok(Self::Cantabria), - "castellon province" => Ok(Self::CastellonProvince), - "castile and leon" => Ok(Self::CastileAndLeon), - "castile la mancha" => Ok(Self::CastileLaMancha), - "catalonia" => Ok(Self::Catalonia), - "ceuta" => Ok(Self::Ceuta), - "ciudad real province" => Ok(Self::CiudadRealProvince), - "community of madrid" => Ok(Self::CommunityOfMadrid), - "cuenca province" => Ok(Self::CuencaProvince), - "caceres province" => Ok(Self::CaceresProvince), - "cadiz province" => Ok(Self::CadizProvince), - "cordoba province" => Ok(Self::CordobaProvince), - "extremadura" => Ok(Self::Extremadura), - "galicia" => Ok(Self::Galicia), - "gipuzkoa" => Ok(Self::Gipuzkoa), - "girona province" => Ok(Self::GironaProvince), - "granada province" => Ok(Self::GranadaProvince), - "guadalajara province" => Ok(Self::GuadalajaraProvince), - "huelva province" => Ok(Self::HuelvaProvince), - "huesca province" => Ok(Self::HuescaProvince), - "jaen province" => Ok(Self::JaenProvince), - "la rioja" => Ok(Self::LaRioja), - "las palmas province" => Ok(Self::LasPalmasProvince), - "leon province" => Ok(Self::LeonProvince), - "lleida province" => Ok(Self::LleidaProvince), - "lugo province" => Ok(Self::LugoProvince), - "madrid province" => Ok(Self::MadridProvince), - "melilla" => Ok(Self::Melilla), - "murcia province" => Ok(Self::MurciaProvince), - "malaga province" => Ok(Self::MalagaProvince), - "navarre" => Ok(Self::Navarre), - "ourense province" => Ok(Self::OurenseProvince), - "palencia province" => Ok(Self::PalenciaProvince), - "pontevedra province" => Ok(Self::PontevedraProvince), - "province of asturias" => Ok(Self::ProvinceOfAsturias), - "province of avila" => Ok(Self::ProvinceOfAvila), - "region of murcia" => Ok(Self::RegionOfMurcia), - "salamanca province" => Ok(Self::SalamancaProvince), - "santa cruz de tenerife province" => Ok(Self::SantaCruzDeTenerifeProvince), - "segovia province" => Ok(Self::SegoviaProvince), - "seville province" => Ok(Self::SevilleProvince), - "soria province" => Ok(Self::SoriaProvince), - "tarragona province" => Ok(Self::TarragonaProvince), - "teruel province" => Ok(Self::TeruelProvince), - "toledo province" => Ok(Self::ToledoProvince), - "valencia province" => Ok(Self::ValenciaProvince), - "valencian community" => Ok(Self::ValencianCommunity), - "valladolid province" => Ok(Self::ValladolidProvince), - "zamora province" => Ok(Self::ZamoraProvince), - "zaragoza province" => Ok(Self::ZaragozaProvince), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "A Coruña Province" => Ok(Self::ACorunaProvince), + "Albacete Province" => Ok(Self::AlbaceteProvince), + "Alicante Province" => Ok(Self::AlicanteProvince), + "Almería Province" => Ok(Self::AlmeriaProvince), + "Andalusia" => Ok(Self::Andalusia), + "Araba / Álava" => Ok(Self::ArabaAlava), + "Aragon" => Ok(Self::Aragon), + "Badajoz Province" => Ok(Self::BadajozProvince), + "Balearic Islands" => Ok(Self::BalearicIslands), + "Barcelona Province" => Ok(Self::BarcelonaProvince), + "Basque Country" => Ok(Self::BasqueCountry), + "Biscay" => Ok(Self::Biscay), + "Burgos Province" => Ok(Self::BurgosProvince), + "Canary Islands" => Ok(Self::CanaryIslands), + "Cantabria" => Ok(Self::Cantabria), + "Castellón Province" => Ok(Self::CastellonProvince), + "Castile and León" => Ok(Self::CastileAndLeon), + "Castilla-La Mancha" => Ok(Self::CastileLaMancha), + "Catalonia" => Ok(Self::Catalonia), + "Ceuta" => Ok(Self::Ceuta), + "Ciudad Real Province" => Ok(Self::CiudadRealProvince), + "Community of Madrid" => Ok(Self::CommunityOfMadrid), + "Cuenca Province" => Ok(Self::CuencaProvince), + "Cáceres Province" => Ok(Self::CaceresProvince), + "Cádiz Province" => Ok(Self::CadizProvince), + "Córdoba Province" => Ok(Self::CordobaProvince), + "Extremadura" => Ok(Self::Extremadura), + "Galicia" => Ok(Self::Galicia), + "Gipuzkoa" => Ok(Self::Gipuzkoa), + "Girona Province" => Ok(Self::GironaProvince), + "Granada Province" => Ok(Self::GranadaProvince), + "Guadalajara Province" => Ok(Self::GuadalajaraProvince), + "Huelva Province" => Ok(Self::HuelvaProvince), + "Huesca Province" => Ok(Self::HuescaProvince), + "Jaén Province" => Ok(Self::JaenProvince), + "La Rioja" => Ok(Self::LaRioja), + "Las Palmas Province" => Ok(Self::LasPalmasProvince), + "León Province" => Ok(Self::LeonProvince), + "Lleida Province" => Ok(Self::LleidaProvince), + "Lugo Province" => Ok(Self::LugoProvince), + "Madrid Province" => Ok(Self::MadridProvince), + "Melilla" => Ok(Self::Melilla), + "Murcia Province" => Ok(Self::MurciaProvince), + "Málaga Province" => Ok(Self::MalagaProvince), + "Navarre" => Ok(Self::Navarre), + "Ourense Province" => Ok(Self::OurenseProvince), + "Palencia Province" => Ok(Self::PalenciaProvince), + "Pontevedra Province" => Ok(Self::PontevedraProvince), + "Province of Asturias" => Ok(Self::ProvinceOfAsturias), + "Province of Ávila" => Ok(Self::ProvinceOfAvila), + "Region of Murcia" => Ok(Self::RegionOfMurcia), + "Salamanca Province" => Ok(Self::SalamancaProvince), + "Santa Cruz de Tenerife Province" => Ok(Self::SantaCruzDeTenerifeProvince), + "Segovia Province" => Ok(Self::SegoviaProvince), + "Seville Province" => Ok(Self::SevilleProvince), + "Soria Province" => Ok(Self::SoriaProvince), + "Tarragona Province" => Ok(Self::TarragonaProvince), + "Teruel Province" => Ok(Self::TeruelProvince), + "Toledo Province" => Ok(Self::ToledoProvince), + "Valencia Province" => Ok(Self::ValenciaProvince), + "Valencian Community" => Ok(Self::ValencianCommunity), + "Valladolid Province" => Ok(Self::ValladolidProvince), + "Zamora Province" => Ok(Self::ZamoraProvince), + "Zaragoza Province" => Ok(Self::ZaragozaProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2675,60 +2752,56 @@ impl ForeignTryFrom for ItalyStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "ItalyStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "ItalyStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "abruzzo" => Ok(Self::Abruzzo), - "aosta valley" => Ok(Self::AostaValley), - "apulia" => Ok(Self::Apulia), - "basilicata" => Ok(Self::Basilicata), - "benevento province" => Ok(Self::BeneventoProvince), - "calabria" => Ok(Self::Calabria), - "campania" => Ok(Self::Campania), - "emilia romagna" => Ok(Self::EmiliaRomagna), - "friuli venezia giulia" => Ok(Self::FriuliVeneziaGiulia), - "lazio" => Ok(Self::Lazio), - "liguria" => Ok(Self::Liguria), - "lombardy" => Ok(Self::Lombardy), - "marche" => Ok(Self::Marche), - "molise" => Ok(Self::Molise), - "piedmont" => Ok(Self::Piedmont), - "sardinia" => Ok(Self::Sardinia), - "sicily" => Ok(Self::Sicily), - "trentino south tyrol" => Ok(Self::TrentinoSouthTyrol), - "tuscany" => Ok(Self::Tuscany), - "umbria" => Ok(Self::Umbria), - "veneto" => Ok(Self::Veneto), - "agrigento" => Ok(Self::Agrigento), - "caltanissetta" => Ok(Self::Caltanissetta), - "enna" => Ok(Self::Enna), - "ragusa" => Ok(Self::Ragusa), - "siracusa" => Ok(Self::Siracusa), - "trapani" => Ok(Self::Trapani), - "bari" => Ok(Self::Bari), - "bologna" => Ok(Self::Bologna), - "cagliari" => Ok(Self::Cagliari), - "catania" => Ok(Self::Catania), - "florence" => Ok(Self::Florence), - "genoa" => Ok(Self::Genoa), - "messina" => Ok(Self::Messina), - "milan" => Ok(Self::Milan), - "naples" => Ok(Self::Naples), - "palermo" => Ok(Self::Palermo), - "reggio calabria" => Ok(Self::ReggioCalabria), - "rome" => Ok(Self::Rome), - "turin" => Ok(Self::Turin), - "venice" => Ok(Self::Venice), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Abruzzo" => Ok(Self::Abruzzo), + "Aosta Valley" => Ok(Self::AostaValley), + "Apulia" => Ok(Self::Apulia), + "Basilicata" => Ok(Self::Basilicata), + "Benevento Province" => Ok(Self::BeneventoProvince), + "Calabria" => Ok(Self::Calabria), + "Campania" => Ok(Self::Campania), + "Emilia-Romagna" => Ok(Self::EmiliaRomagna), + "Friuli–Venezia Giulia" => Ok(Self::FriuliVeneziaGiulia), + "Lazio" => Ok(Self::Lazio), + "Liguria" => Ok(Self::Liguria), + "Lombardy" => Ok(Self::Lombardy), + "Marche" => Ok(Self::Marche), + "Molise" => Ok(Self::Molise), + "Piedmont" => Ok(Self::Piedmont), + "Sardinia" => Ok(Self::Sardinia), + "Sicily" => Ok(Self::Sicily), + "Trentino-South Tyrol" => Ok(Self::TrentinoSouthTyrol), + "Tuscany" => Ok(Self::Tuscany), + "Umbria" => Ok(Self::Umbria), + "Veneto" => Ok(Self::Veneto), + "Libero consorzio comunale di Agrigento" => Ok(Self::Agrigento), + "Libero consorzio comunale di Caltanissetta" => Ok(Self::Caltanissetta), + "Libero consorzio comunale di Enna" => Ok(Self::Enna), + "Libero consorzio comunale di Ragusa" => Ok(Self::Ragusa), + "Libero consorzio comunale di Siracusa" => Ok(Self::Siracusa), + "Libero consorzio comunale di Trapani" => Ok(Self::Trapani), + "Metropolitan City of Bari" => Ok(Self::Bari), + "Metropolitan City of Bologna" => Ok(Self::Bologna), + "Metropolitan City of Cagliari" => Ok(Self::Cagliari), + "Metropolitan City of Catania" => Ok(Self::Catania), + "Metropolitan City of Florence" => Ok(Self::Florence), + "Metropolitan City of Genoa" => Ok(Self::Genoa), + "Metropolitan City of Messina" => Ok(Self::Messina), + "Metropolitan City of Milan" => Ok(Self::Milan), + "Metropolitan City of Naples" => Ok(Self::Naples), + "Metropolitan City of Palermo" => Ok(Self::Palermo), + "Metropolitan City of Reggio Calabria" => Ok(Self::ReggioCalabria), + "Metropolitan City of Rome" => Ok(Self::Rome), + "Metropolitan City of Turin" => Ok(Self::Turin), + "Metropolitan City of Venice" => Ok(Self::Venice), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2737,39 +2810,36 @@ impl ForeignTryFrom for NorwayStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "NorwayStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "NorwayStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "akershus" => Ok(Self::Akershus), - "buskerud" => Ok(Self::Buskerud), - "finnmark" => Ok(Self::Finnmark), - "hedmark" => Ok(Self::Hedmark), - "hordaland" => Ok(Self::Hordaland), - "janmayen" => Ok(Self::JanMayen), - "nordtrondelag" => Ok(Self::NordTrondelag), - "nordland" => Ok(Self::Nordland), - "oppland" => Ok(Self::Oppland), - "oslo" => Ok(Self::Oslo), - "rogaland" => Ok(Self::Rogaland), - "sognogfjordane" => Ok(Self::SognOgFjordane), - "svalbard" => Ok(Self::Svalbard), - "sortrondelag" => Ok(Self::SorTrondelag), - "telemark" => Ok(Self::Telemark), - "troms" => Ok(Self::Troms), - "trondelag" => Ok(Self::Trondelag), - "vestagder" => Ok(Self::VestAgder), - "vestfold" => Ok(Self::Vestfold), - "ostfold" => Ok(Self::Ostfold), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Akershus" => Ok(Self::Akershus), + "Buskerud" => Ok(Self::Buskerud), + "Finnmark" => Ok(Self::Finnmark), + "Hedmark" => Ok(Self::Hedmark), + "Hordaland" => Ok(Self::Hordaland), + "Jan Mayen" => Ok(Self::JanMayen), + "Møre og Romsdal" => Ok(Self::MoreOgRomsdal), + "Nord-Trøndelag" => Ok(Self::NordTrondelag), + "Nordland" => Ok(Self::Nordland), + "Oppland" => Ok(Self::Oppland), + "Oslo" => Ok(Self::Oslo), + "Rogaland" => Ok(Self::Rogaland), + "Sogn og Fjordane" => Ok(Self::SognOgFjordane), + "Svalbard" => Ok(Self::Svalbard), + "Sør-Trøndelag" => Ok(Self::SorTrondelag), + "Telemark" => Ok(Self::Telemark), + "Troms" => Ok(Self::Troms), + "Trøndelag" => Ok(Self::Trondelag), + "Vest-Agder" => Ok(Self::VestAgder), + "Vestfold" => Ok(Self::Vestfold), + "Østfold" => Ok(Self::Ostfold), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2777,34 +2847,28 @@ impl ForeignTryFrom for NorwayStatesAbbreviation { impl ForeignTryFrom for AlbaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AlbaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AlbaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "berat" => Ok(Self::Berat), - "diber" => Ok(Self::Diber), - "durres" => Ok(Self::Durres), - "elbasan" => Ok(Self::Elbasan), - "fier" => Ok(Self::Fier), - "gjirokaster" => Ok(Self::Gjirokaster), - "korce" => Ok(Self::Korce), - "kukes" => Ok(Self::Kukes), - "lezhe" => Ok(Self::Lezhe), - "shkoder" => Ok(Self::Shkoder), - "tirane" => Ok(Self::Tirane), - "vlore" => Ok(Self::Vlore), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Berat" => Ok(Self::Berat), + "Dibër" => Ok(Self::Diber), + "Durrës" => Ok(Self::Durres), + "Elbasan" => Ok(Self::Elbasan), + "Fier" => Ok(Self::Fier), + "Gjirokastër" => Ok(Self::Gjirokaster), + "Korçë" => Ok(Self::Korce), + "Kukës" => Ok(Self::Kukes), + "Lezhë" => Ok(Self::Lezhe), + "Shkodër" => Ok(Self::Shkoder), + "Tiranë" => Ok(Self::Tirane), + "Vlorë" => Ok(Self::Vlore), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2812,29 +2876,23 @@ impl ForeignTryFrom for AlbaniaStatesAbbreviation { impl ForeignTryFrom for AndorraStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AndorraStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AndorraStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "andorra la vella" => Ok(Self::AndorraLaVella), - "canillo" => Ok(Self::Canillo), - "encamp" => Ok(Self::Encamp), - "escaldes engordany" => Ok(Self::EscaldesEngordany), - "la massana" => Ok(Self::LaMassana), - "ordino" => Ok(Self::Ordino), - "sant julia de loria" => Ok(Self::SantJuliaDeLoria), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Andorra la Vella" => Ok(Self::AndorraLaVella), + "Canillo" => Ok(Self::Canillo), + "Encamp" => Ok(Self::Encamp), + "Escaldes-Engordany" => Ok(Self::EscaldesEngordany), + "La Massana" => Ok(Self::LaMassana), + "Ordino" => Ok(Self::Ordino), + "Sant Julià de Lòria" => Ok(Self::SantJuliaDeLoria), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2842,31 +2900,25 @@ impl ForeignTryFrom for AndorraStatesAbbreviation { impl ForeignTryFrom for AustriaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AustriaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AustriaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "burgenland" => Ok(Self::Burgenland), - "carinthia" => Ok(Self::Carinthia), - "lower austria" => Ok(Self::LowerAustria), - "salzburg" => Ok(Self::Salzburg), - "styria" => Ok(Self::Styria), - "tyrol" => Ok(Self::Tyrol), - "upper austria" => Ok(Self::UpperAustria), - "vienna" => Ok(Self::Vienna), - "vorarlberg" => Ok(Self::Vorarlberg), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Burgenland" => Ok(Self::Burgenland), + "Carinthia" => Ok(Self::Carinthia), + "Lower Austria" => Ok(Self::LowerAustria), + "Salzburg" => Ok(Self::Salzburg), + "Styria" => Ok(Self::Styria), + "Tyrol" => Ok(Self::Tyrol), + "Upper Austria" => Ok(Self::UpperAustria), + "Vienna" => Ok(Self::Vienna), + "Vorarlberg" => Ok(Self::Vorarlberg), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2874,63 +2926,57 @@ impl ForeignTryFrom for AustriaStatesAbbreviation { impl ForeignTryFrom for RomaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "RomaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "RomaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "alba" => Ok(Self::Alba), - "arad county" => Ok(Self::AradCounty), - "arges" => Ok(Self::Arges), - "bacau county" => Ok(Self::BacauCounty), - "bihor county" => Ok(Self::BihorCounty), - "bistrita nasaud county" => Ok(Self::BistritaNasaudCounty), - "botosani county" => Ok(Self::BotosaniCounty), - "braila" => Ok(Self::Braila), - "brasov county" => Ok(Self::BrasovCounty), - "bucharest" => Ok(Self::Bucharest), - "buzau county" => Ok(Self::BuzauCounty), - "caras severin county" => Ok(Self::CarasSeverinCounty), - "cluj county" => Ok(Self::ClujCounty), - "constanta county" => Ok(Self::ConstantaCounty), - "covasna county" => Ok(Self::CovasnaCounty), - "calarasi county" => Ok(Self::CalarasiCounty), - "dolj county" => Ok(Self::DoljCounty), - "dambovita county" => Ok(Self::DambovitaCounty), - "galati county" => Ok(Self::GalatiCounty), - "giurgiu county" => Ok(Self::GiurgiuCounty), - "gorj county" => Ok(Self::GorjCounty), - "harghita county" => Ok(Self::HarghitaCounty), - "hunedoara county" => Ok(Self::HunedoaraCounty), - "ialomita county" => Ok(Self::IalomitaCounty), - "iasi county" => Ok(Self::IasiCounty), - "ilfov county" => Ok(Self::IlfovCounty), - "mehedinti county" => Ok(Self::MehedintiCounty), - "mures county" => Ok(Self::MuresCounty), - "neamt county" => Ok(Self::NeamtCounty), - "olt county" => Ok(Self::OltCounty), - "prahova county" => Ok(Self::PrahovaCounty), - "satu mare county" => Ok(Self::SatuMareCounty), - "sibiu county" => Ok(Self::SibiuCounty), - "suceava county" => Ok(Self::SuceavaCounty), - "salaj county" => Ok(Self::SalajCounty), - "teleorman county" => Ok(Self::TeleormanCounty), - "timis county" => Ok(Self::TimisCounty), - "tulcea county" => Ok(Self::TulceaCounty), - "vaslui county" => Ok(Self::VasluiCounty), - "vrancea county" => Ok(Self::VranceaCounty), - "valcea county" => Ok(Self::ValceaCounty), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Alba" => Ok(Self::Alba), + "Arad County" => Ok(Self::AradCounty), + "Argeș" => Ok(Self::Arges), + "Bacău County" => Ok(Self::BacauCounty), + "Bihor County" => Ok(Self::BihorCounty), + "Bistrița-Năsăud County" => Ok(Self::BistritaNasaudCounty), + "Botoșani County" => Ok(Self::BotosaniCounty), + "Brăila" => Ok(Self::Braila), + "Brașov County" => Ok(Self::BrasovCounty), + "Bucharest" => Ok(Self::Bucharest), + "Buzău County" => Ok(Self::BuzauCounty), + "Caraș-Severin County" => Ok(Self::CarasSeverinCounty), + "Cluj County" => Ok(Self::ClujCounty), + "Constanța County" => Ok(Self::ConstantaCounty), + "Covasna County" => Ok(Self::CovasnaCounty), + "Călărași County" => Ok(Self::CalarasiCounty), + "Dolj County" => Ok(Self::DoljCounty), + "Dâmbovița County" => Ok(Self::DambovitaCounty), + "Galați County" => Ok(Self::GalatiCounty), + "Giurgiu County" => Ok(Self::GiurgiuCounty), + "Gorj County" => Ok(Self::GorjCounty), + "Harghita County" => Ok(Self::HarghitaCounty), + "Hunedoara County" => Ok(Self::HunedoaraCounty), + "Ialomița County" => Ok(Self::IalomitaCounty), + "Iași County" => Ok(Self::IasiCounty), + "Ilfov County" => Ok(Self::IlfovCounty), + "Mehedinți County" => Ok(Self::MehedintiCounty), + "Mureș County" => Ok(Self::MuresCounty), + "Neamț County" => Ok(Self::NeamtCounty), + "Olt County" => Ok(Self::OltCounty), + "Prahova County" => Ok(Self::PrahovaCounty), + "Satu Mare County" => Ok(Self::SatuMareCounty), + "Sibiu County" => Ok(Self::SibiuCounty), + "Suceava County" => Ok(Self::SuceavaCounty), + "Sălaj County" => Ok(Self::SalajCounty), + "Teleorman County" => Ok(Self::TeleormanCounty), + "Timiș County" => Ok(Self::TimisCounty), + "Tulcea County" => Ok(Self::TulceaCounty), + "Vaslui County" => Ok(Self::VasluiCounty), + "Vrancea County" => Ok(Self::VranceaCounty), + "Vâlcea County" => Ok(Self::ValceaCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2938,42 +2984,36 @@ impl ForeignTryFrom for RomaniaStatesAbbreviation { impl ForeignTryFrom for PortugalStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "PortugalStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "PortugalStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aveiro district" => Ok(Self::AveiroDistrict), - "azores" => Ok(Self::Azores), - "beja district" => Ok(Self::BejaDistrict), - "braga district" => Ok(Self::BragaDistrict), - "braganca district" => Ok(Self::BragancaDistrict), - "castelo branco district" => Ok(Self::CasteloBrancoDistrict), - "coimbra district" => Ok(Self::CoimbraDistrict), - "faro district" => Ok(Self::FaroDistrict), - "guarda district" => Ok(Self::GuardaDistrict), - "leiria district" => Ok(Self::LeiriaDistrict), - "lisbon district" => Ok(Self::LisbonDistrict), - "madeira" => Ok(Self::Madeira), - "portalegre district" => Ok(Self::PortalegreDistrict), - "porto district" => Ok(Self::PortoDistrict), - "santarem district" => Ok(Self::SantaremDistrict), - "setubal district" => Ok(Self::SetubalDistrict), - "viana do castelo district" => Ok(Self::VianaDoCasteloDistrict), - "vila real district" => Ok(Self::VilaRealDistrict), - "viseu district" => Ok(Self::ViseuDistrict), - "evora district" => Ok(Self::EvoraDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aveiro District" => Ok(Self::AveiroDistrict), + "Azores" => Ok(Self::Azores), + "Beja District" => Ok(Self::BejaDistrict), + "Braga District" => Ok(Self::BragaDistrict), + "Bragança District" => Ok(Self::BragancaDistrict), + "Castelo Branco District" => Ok(Self::CasteloBrancoDistrict), + "Coimbra District" => Ok(Self::CoimbraDistrict), + "Faro District" => Ok(Self::FaroDistrict), + "Guarda District" => Ok(Self::GuardaDistrict), + "Leiria District" => Ok(Self::LeiriaDistrict), + "Lisbon District" => Ok(Self::LisbonDistrict), + "Madeira" => Ok(Self::Madeira), + "Portalegre District" => Ok(Self::PortalegreDistrict), + "Porto District" => Ok(Self::PortoDistrict), + "Santarém District" => Ok(Self::SantaremDistrict), + "Setúbal District" => Ok(Self::SetubalDistrict), + "Viana do Castelo District" => Ok(Self::VianaDoCasteloDistrict), + "Vila Real District" => Ok(Self::VilaRealDistrict), + "Viseu District" => Ok(Self::ViseuDistrict), + "Évora District" => Ok(Self::EvoraDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2981,47 +3021,41 @@ impl ForeignTryFrom for PortugalStatesAbbreviation { impl ForeignTryFrom for SwitzerlandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "SwitzerlandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SwitzerlandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aargau" => Ok(Self::Aargau), - "appenzell ausserrhoden" => Ok(Self::AppenzellAusserrhoden), - "appenzell innerrhoden" => Ok(Self::AppenzellInnerrhoden), - "basel landschaft" => Ok(Self::BaselLandschaft), - "canton of fribourg" => Ok(Self::CantonOfFribourg), - "canton of geneva" => Ok(Self::CantonOfGeneva), - "canton of jura" => Ok(Self::CantonOfJura), - "canton of lucerne" => Ok(Self::CantonOfLucerne), - "canton of neuchatel" => Ok(Self::CantonOfNeuchatel), - "canton of schaffhausen" => Ok(Self::CantonOfSchaffhausen), - "canton of solothurn" => Ok(Self::CantonOfSolothurn), - "canton of st gallen" => Ok(Self::CantonOfStGallen), - "canton of valais" => Ok(Self::CantonOfValais), - "canton of vaud" => Ok(Self::CantonOfVaud), - "canton of zug" => Ok(Self::CantonOfZug), - "glarus" => Ok(Self::Glarus), - "graubunden" => Ok(Self::Graubunden), - "nidwalden" => Ok(Self::Nidwalden), - "obwalden" => Ok(Self::Obwalden), - "schwyz" => Ok(Self::Schwyz), - "thurgau" => Ok(Self::Thurgau), - "ticino" => Ok(Self::Ticino), - "uri" => Ok(Self::Uri), - "canton of bern" => Ok(Self::CantonOfBern), - "canton of zurich" => Ok(Self::CantonOfZurich), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aargau" => Ok(Self::Aargau), + "Appenzell Ausserrhoden" => Ok(Self::AppenzellAusserrhoden), + "Appenzell Innerrhoden" => Ok(Self::AppenzellInnerrhoden), + "Basel-Landschaft" => Ok(Self::BaselLandschaft), + "Canton of Fribourg" => Ok(Self::CantonOfFribourg), + "Canton of Geneva" => Ok(Self::CantonOfGeneva), + "Canton of Jura" => Ok(Self::CantonOfJura), + "Canton of Lucerne" => Ok(Self::CantonOfLucerne), + "Canton of Neuchâtel" => Ok(Self::CantonOfNeuchatel), + "Canton of Schaffhausen" => Ok(Self::CantonOfSchaffhausen), + "Canton of Solothurn" => Ok(Self::CantonOfSolothurn), + "Canton of St. Gallen" => Ok(Self::CantonOfStGallen), + "Canton of Valais" => Ok(Self::CantonOfValais), + "Canton of Vaud" => Ok(Self::CantonOfVaud), + "Canton of Zug" => Ok(Self::CantonOfZug), + "Glarus" => Ok(Self::Glarus), + "Graubünden" => Ok(Self::Graubunden), + "Nidwalden" => Ok(Self::Nidwalden), + "Obwalden" => Ok(Self::Obwalden), + "Schwyz" => Ok(Self::Schwyz), + "Thurgau" => Ok(Self::Thurgau), + "Ticino" => Ok(Self::Ticino), + "Uri" => Ok(Self::Uri), + "canton of Bern" => Ok(Self::CantonOfBern), + "canton of Zürich" => Ok(Self::CantonOfZurich), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3029,54 +3063,100 @@ impl ForeignTryFrom for SwitzerlandStatesAbbreviation { impl ForeignTryFrom for NorthMacedoniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "NorthMacedoniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "NorthMacedoniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aerodrom municipality" => Ok(Self::AerodromMunicipality), - "aracinovo municipality" => Ok(Self::AracinovoMunicipality), - "berovo municipality" => Ok(Self::BerovoMunicipality), - "bitola municipality" => Ok(Self::BitolaMunicipality), - "bogdanci municipality" => Ok(Self::BogdanciMunicipality), - "bogovinje municipality" => Ok(Self::BogovinjeMunicipality), - "bosilovo municipality" => Ok(Self::BosilovoMunicipality), - "brvenica municipality" => Ok(Self::BrvenicaMunicipality), - "butel municipality" => Ok(Self::ButelMunicipality), - "centar municipality" => Ok(Self::CentarMunicipality), - "centar zupa municipality" => Ok(Self::CentarZupaMunicipality), - "debarca municipality" => Ok(Self::DebarcaMunicipality), - "delcevo municipality" => Ok(Self::DelcevoMunicipality), - "demir hisar municipality" => Ok(Self::DemirHisarMunicipality), - "demir kapija municipality" => Ok(Self::DemirKapijaMunicipality), - "dojran municipality" => Ok(Self::DojranMunicipality), - "dolneni municipality" => Ok(Self::DolneniMunicipality), - "drugovo municipality" => Ok(Self::DrugovoMunicipality), - "gazi baba municipality" => Ok(Self::GaziBabaMunicipality), - "gevgelija municipality" => Ok(Self::GevgelijaMunicipality), - "gjorce petrov municipality" => Ok(Self::GjorcePetrovMunicipality), - "gostivar municipality" => Ok(Self::GostivarMunicipality), - "gradsko municipality" => Ok(Self::GradskoMunicipality), - "greater skopje" => Ok(Self::GreaterSkopje), - "ilinden municipality" => Ok(Self::IlindenMunicipality), - "jegunovce municipality" => Ok(Self::JegunovceMunicipality), - "karbinci" => Ok(Self::Karbinci), - "karpos municipality" => Ok(Self::KarposMunicipality), - "kavadarci municipality" => Ok(Self::KavadarciMunicipality), - "kisela voda municipality" => Ok(Self::KiselaVodaMunicipality), - "kicevo municipality" => Ok(Self::KicevoMunicipality), - "konce municipality" => Ok(Self::KonceMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aerodrom Municipality" => Ok(Self::AerodromMunicipality), + "Aračinovo Municipality" => Ok(Self::AracinovoMunicipality), + "Berovo Municipality" => Ok(Self::BerovoMunicipality), + "Bitola Municipality" => Ok(Self::BitolaMunicipality), + "Bogdanci Municipality" => Ok(Self::BogdanciMunicipality), + "Bogovinje Municipality" => Ok(Self::BogovinjeMunicipality), + "Bosilovo Municipality" => Ok(Self::BosilovoMunicipality), + "Brvenica Municipality" => Ok(Self::BrvenicaMunicipality), + "Butel Municipality" => Ok(Self::ButelMunicipality), + "Centar Municipality" => Ok(Self::CentarMunicipality), + "Centar Župa Municipality" => Ok(Self::CentarZupaMunicipality), + "Debarca Municipality" => Ok(Self::DebarcaMunicipality), + "Delčevo Municipality" => Ok(Self::DelcevoMunicipality), + "Demir Hisar Municipality" => Ok(Self::DemirHisarMunicipality), + "Demir Kapija Municipality" => Ok(Self::DemirKapijaMunicipality), + "Dojran Municipality" => Ok(Self::DojranMunicipality), + "Dolneni Municipality" => Ok(Self::DolneniMunicipality), + "Drugovo Municipality" => Ok(Self::DrugovoMunicipality), + "Gazi Baba Municipality" => Ok(Self::GaziBabaMunicipality), + "Gevgelija Municipality" => Ok(Self::GevgelijaMunicipality), + "Gjorče Petrov Municipality" => Ok(Self::GjorcePetrovMunicipality), + "Gostivar Municipality" => Ok(Self::GostivarMunicipality), + "Gradsko Municipality" => Ok(Self::GradskoMunicipality), + "Greater Skopje" => Ok(Self::GreaterSkopje), + "Ilinden Municipality" => Ok(Self::IlindenMunicipality), + "Jegunovce Municipality" => Ok(Self::JegunovceMunicipality), + "Karbinci" => Ok(Self::Karbinci), + "Karpoš Municipality" => Ok(Self::KarposMunicipality), + "Kavadarci Municipality" => Ok(Self::KavadarciMunicipality), + "Kisela Voda Municipality" => Ok(Self::KiselaVodaMunicipality), + "Kičevo Municipality" => Ok(Self::KicevoMunicipality), + "Konče Municipality" => Ok(Self::KonceMunicipality), + "Kočani Municipality" => Ok(Self::KocaniMunicipality), + "Kratovo Municipality" => Ok(Self::KratovoMunicipality), + "Kriva Palanka Municipality" => Ok(Self::KrivaPalankaMunicipality), + "Krivogaštani Municipality" => Ok(Self::KrivogastaniMunicipality), + "Kruševo Municipality" => Ok(Self::KrusevoMunicipality), + "Kumanovo Municipality" => Ok(Self::KumanovoMunicipality), + "Lipkovo Municipality" => Ok(Self::LipkovoMunicipality), + "Lozovo Municipality" => Ok(Self::LozovoMunicipality), + "Makedonska Kamenica Municipality" => Ok(Self::MakedonskaKamenicaMunicipality), + "Makedonski Brod Municipality" => Ok(Self::MakedonskiBrodMunicipality), + "Mavrovo and Rostuša Municipality" => Ok(Self::MavrovoAndRostusaMunicipality), + "Mogila Municipality" => Ok(Self::MogilaMunicipality), + "Negotino Municipality" => Ok(Self::NegotinoMunicipality), + "Novaci Municipality" => Ok(Self::NovaciMunicipality), + "Novo Selo Municipality" => Ok(Self::NovoSeloMunicipality), + "Ohrid Municipality" => Ok(Self::OhridMunicipality), + "Oslomej Municipality" => Ok(Self::OslomejMunicipality), + "Pehčevo Municipality" => Ok(Self::PehcevoMunicipality), + "Petrovec Municipality" => Ok(Self::PetrovecMunicipality), + "Plasnica Municipality" => Ok(Self::PlasnicaMunicipality), + "Prilep Municipality" => Ok(Self::PrilepMunicipality), + "Probištip Municipality" => Ok(Self::ProbishtipMunicipality), + "Radoviš Municipality" => Ok(Self::RadovisMunicipality), + "Rankovce Municipality" => Ok(Self::RankovceMunicipality), + "Resen Municipality" => Ok(Self::ResenMunicipality), + "Rosoman Municipality" => Ok(Self::RosomanMunicipality), + "Saraj Municipality" => Ok(Self::SarajMunicipality), + "Sopište Municipality" => Ok(Self::SopisteMunicipality), + "Staro Nagoričane Municipality" => Ok(Self::StaroNagoricaneMunicipality), + "Struga Municipality" => Ok(Self::StrugaMunicipality), + "Strumica Municipality" => Ok(Self::StrumicaMunicipality), + "Studeničani Municipality" => Ok(Self::StudenicaniMunicipality), + "Sveti Nikole Municipality" => Ok(Self::SvetiNikoleMunicipality), + "Tearce Municipality" => Ok(Self::TearceMunicipality), + "Tetovo Municipality" => Ok(Self::TetovoMunicipality), + "Valandovo Municipality" => Ok(Self::ValandovoMunicipality), + "Vasilevo Municipality" => Ok(Self::VasilevoMunicipality), + "Veles Municipality" => Ok(Self::VelesMunicipality), + "Vevčani Municipality" => Ok(Self::VevcaniMunicipality), + "Vinica Municipality" => Ok(Self::VinicaMunicipality), + "Vraneštica Municipality" => Ok(Self::VranesticaMunicipality), + "Vrapčište Municipality" => Ok(Self::VrapcisteMunicipality), + "Zajas Municipality" => Ok(Self::ZajasMunicipality), + "Zelenikovo Municipality" => Ok(Self::ZelenikovoMunicipality), + "Zrnovci Municipality" => Ok(Self::ZrnovciMunicipality), + "Čair Municipality" => Ok(Self::CairMunicipality), + "Čaška Municipality" => Ok(Self::CaskaMunicipality), + "Češinovo-Obleševo Municipality" => Ok(Self::CesinovoOblesevoMunicipality), + "Čučer-Sandevo Municipality" => Ok(Self::CucerSandevoMunicipality), + "Štip Municipality" => Ok(Self::StipMunicipality), + "Šuto Orizari Municipality" => Ok(Self::ShutoOrizariMunicipality), + "Želino Municipality" => Ok(Self::ZelinoMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3084,44 +3164,36 @@ impl ForeignTryFrom for NorthMacedoniaStatesAbbreviation { impl ForeignTryFrom for MontenegroStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "MontenegroStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "MontenegroStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "andrijevica municipality" => Ok(Self::AndrijevicaMunicipality), - "bar municipality" => Ok(Self::BarMunicipality), - "berane municipality" => Ok(Self::BeraneMunicipality), - "bijelo polje municipality" => Ok(Self::BijeloPoljeMunicipality), - "budva municipality" => Ok(Self::BudvaMunicipality), - "danilovgrad municipality" => Ok(Self::DanilovgradMunicipality), - "gusinje municipality" => Ok(Self::GusinjeMunicipality), - "kolasin municipality" => Ok(Self::KolasinMunicipality), - "kotor municipality" => Ok(Self::KotorMunicipality), - "mojkovac municipality" => Ok(Self::MojkovacMunicipality), - "niksic municipality" => Ok(Self::NiksicMunicipality), - "old royal capital cetinje" => Ok(Self::OldRoyalCapitalCetinje), - "petnjica municipality" => Ok(Self::PetnjicaMunicipality), - "plav municipality" => Ok(Self::PlavMunicipality), - "pljevlja municipality" => Ok(Self::PljevljaMunicipality), - "pluzine municipality" => Ok(Self::PluzineMunicipality), - "podgorica municipality" => Ok(Self::PodgoricaMunicipality), - "rozaje municipality" => Ok(Self::RozajeMunicipality), - "tivat municipality" => Ok(Self::TivatMunicipality), - "ulcinj municipality" => Ok(Self::UlcinjMunicipality), - "savnik municipality" => Ok(Self::SavnikMunicipality), - "zabljak municipality" => Ok(Self::ZabljakMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Andrijevica Municipality" => Ok(Self::AndrijevicaMunicipality), + "Bar Municipality" => Ok(Self::BarMunicipality), + "Berane Municipality" => Ok(Self::BeraneMunicipality), + "Bijelo Polje Municipality" => Ok(Self::BijeloPoljeMunicipality), + "Budva Municipality" => Ok(Self::BudvaMunicipality), + "Danilovgrad Municipality" => Ok(Self::DanilovgradMunicipality), + "Gusinje Municipality" => Ok(Self::GusinjeMunicipality), + "Kolašin Municipality" => Ok(Self::KolasinMunicipality), + "Kotor Municipality" => Ok(Self::KotorMunicipality), + "Mojkovac Municipality" => Ok(Self::MojkovacMunicipality), + "Nikšić Municipality" => Ok(Self::NiksicMunicipality), + "Petnjica Municipality" => Ok(Self::PetnjicaMunicipality), + "Plav Municipality" => Ok(Self::PlavMunicipality), + "Pljevlja Municipality" => Ok(Self::PljevljaMunicipality), + "Plužine Municipality" => Ok(Self::PlužineMunicipality), + "Podgorica Municipality" => Ok(Self::PodgoricaMunicipality), + "Rožaje Municipality" => Ok(Self::RožajeMunicipality), + "Tivat Municipality" => Ok(Self::TivatMunicipality), + "Ulcinj Municipality" => Ok(Self::UlcinjMunicipality), + "Žabljak Municipality" => Ok(Self::ŽabljakMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3130,20 +3202,16 @@ impl ForeignTryFrom for MonacoStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "MonacoStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "MonacoStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "monaco" => Ok(Self::Monaco), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Monaco" => Ok(Self::Monaco), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3151,37 +3219,31 @@ impl ForeignTryFrom for MonacoStatesAbbreviation { impl ForeignTryFrom for NetherlandsStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "NetherlandsStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "NetherlandsStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bonaire" => Ok(Self::Bonaire), - "drenthe" => Ok(Self::Drenthe), - "flevoland" => Ok(Self::Flevoland), - "friesland" => Ok(Self::Friesland), - "gelderland" => Ok(Self::Gelderland), - "groningen" => Ok(Self::Groningen), - "limburg" => Ok(Self::Limburg), - "north brabant" => Ok(Self::NorthBrabant), - "north holland" => Ok(Self::NorthHolland), - "overijssel" => Ok(Self::Overijssel), - "saba" => Ok(Self::Saba), - "sint eustatius" => Ok(Self::SintEustatius), - "south holland" => Ok(Self::SouthHolland), - "utrecht" => Ok(Self::Utrecht), - "zeeland" => Ok(Self::Zeeland), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bonaire" => Ok(Self::Bonaire), + "Drenthe" => Ok(Self::Drenthe), + "Flevoland" => Ok(Self::Flevoland), + "Friesland" => Ok(Self::Friesland), + "Gelderland" => Ok(Self::Gelderland), + "Groningen" => Ok(Self::Groningen), + "Limburg" => Ok(Self::Limburg), + "North Brabant" => Ok(Self::NorthBrabant), + "North Holland" => Ok(Self::NorthHolland), + "Overijssel" => Ok(Self::Overijssel), + "Saba" => Ok(Self::Saba), + "Sint Eustatius" => Ok(Self::SintEustatius), + "South Holland" => Ok(Self::SouthHolland), + "Utrecht" => Ok(Self::Utrecht), + "Zeeland" => Ok(Self::Zeeland), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3189,60 +3251,54 @@ impl ForeignTryFrom for NetherlandsStatesAbbreviation { impl ForeignTryFrom for MoldovaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "MoldovaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "MoldovaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "anenii noi district" => Ok(Self::AneniiNoiDistrict), - "basarabeasca district" => Ok(Self::BasarabeascaDistrict), - "bender municipality" => Ok(Self::BenderMunicipality), - "briceni district" => Ok(Self::BriceniDistrict), - "balti municipality" => Ok(Self::BaltiMunicipality), - "cahul district" => Ok(Self::CahulDistrict), - "cantemir district" => Ok(Self::CantemirDistrict), - "chisinau municipality" => Ok(Self::ChisinauMunicipality), - "cimislia district" => Ok(Self::CimisliaDistrict), - "criuleni district" => Ok(Self::CriuleniDistrict), - "calarasi district" => Ok(Self::CalarasiDistrict), - "causeni district" => Ok(Self::CauseniDistrict), - "donduseni district" => Ok(Self::DonduseniDistrict), - "drochia district" => Ok(Self::DrochiaDistrict), - "dubasari district" => Ok(Self::DubasariDistrict), - "edinet district" => Ok(Self::EdinetDistrict), - "floresti district" => Ok(Self::FlorestiDistrict), - "falesti district" => Ok(Self::FalestiDistrict), - "gagauzia" => Ok(Self::Gagauzia), - "glodeni district" => Ok(Self::GlodeniDistrict), - "hincesti district" => Ok(Self::HincestiDistrict), - "ialoveni district" => Ok(Self::IaloveniDistrict), - "nisporeni district" => Ok(Self::NisporeniDistrict), - "ocnita district" => Ok(Self::OcnitaDistrict), - "orhei district" => Ok(Self::OrheiDistrict), - "rezina district" => Ok(Self::RezinaDistrict), - "riscani district" => Ok(Self::RiscaniDistrict), - "soroca district" => Ok(Self::SorocaDistrict), - "straseni district" => Ok(Self::StraseniDistrict), - "singerei district" => Ok(Self::SingereiDistrict), - "taraclia district" => Ok(Self::TaracliaDistrict), - "telenesti district" => Ok(Self::TelenestiDistrict), - "transnistria autonomous territorial unit" => { - Ok(Self::TransnistriaAutonomousTerritorialUnit) - } - "ungheni district" => Ok(Self::UngheniDistrict), - "soldanesti district" => Ok(Self::SoldanestiDistrict), - "stefan voda district" => Ok(Self::StefanVodaDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Anenii Noi District" => Ok(Self::AneniiNoiDistrict), + "Basarabeasca District" => Ok(Self::BasarabeascaDistrict), + "Bender Municipality" => Ok(Self::BenderMunicipality), + "Briceni District" => Ok(Self::BriceniDistrict), + "Bălți Municipality" => Ok(Self::BălțiMunicipality), + "Cahul District" => Ok(Self::CahulDistrict), + "Cantemir District" => Ok(Self::CantemirDistrict), + "Chișinău Municipality" => Ok(Self::ChișinăuMunicipality), + "Cimișlia District" => Ok(Self::CimișliaDistrict), + "Criuleni District" => Ok(Self::CriuleniDistrict), + "Călărași District" => Ok(Self::CălărașiDistrict), + "Căușeni District" => Ok(Self::CăușeniDistrict), + "Dondușeni District" => Ok(Self::DondușeniDistrict), + "Drochia District" => Ok(Self::DrochiaDistrict), + "Dubăsari District" => Ok(Self::DubăsariDistrict), + "Edineț District" => Ok(Self::EdinețDistrict), + "Florești District" => Ok(Self::FloreștiDistrict), + "Fălești District" => Ok(Self::FăleștiDistrict), + "Găgăuzia" => Ok(Self::Găgăuzia), + "Glodeni District" => Ok(Self::GlodeniDistrict), + "Hîncești District" => Ok(Self::HînceștiDistrict), + "Ialoveni District" => Ok(Self::IaloveniDistrict), + "Nisporeni District" => Ok(Self::NisporeniDistrict), + "Ocnița District" => Ok(Self::OcnițaDistrict), + "Orhei District" => Ok(Self::OrheiDistrict), + "Rezina District" => Ok(Self::RezinaDistrict), + "Rîșcani District" => Ok(Self::RîșcaniDistrict), + "Soroca District" => Ok(Self::SorocaDistrict), + "Strășeni District" => Ok(Self::StrășeniDistrict), + "Sîngerei District" => Ok(Self::SîngereiDistrict), + "Taraclia District" => Ok(Self::TaracliaDistrict), + "Telenești District" => Ok(Self::TeleneștiDistrict), + "Transnistria Autonomous Territorial Unit" => { + Ok(Self::TransnistriaAutonomousTerritorialUnit) } - } + "Ungheni District" => Ok(Self::UngheniDistrict), + "Șoldănești District" => Ok(Self::ȘoldăneștiDistrict), + "Ștefan Vodă District" => Ok(Self::ȘtefanVodăDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -3250,99 +3306,85 @@ impl ForeignTryFrom for MoldovaStatesAbbreviation { impl ForeignTryFrom for LithuaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "LithuaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LithuaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "akmene district municipality" => Ok(Self::AkmeneDistrictMunicipality), - "alytus city municipality" => Ok(Self::AlytusCityMunicipality), - "alytus county" => Ok(Self::AlytusCounty), - "alytus district municipality" => Ok(Self::AlytusDistrictMunicipality), - "birstonas municipality" => Ok(Self::BirstonasMunicipality), - "birzai district municipality" => Ok(Self::BirzaiDistrictMunicipality), - "druskininkai municipality" => Ok(Self::DruskininkaiMunicipality), - "elektrenai municipality" => Ok(Self::ElektrenaiMunicipality), - "ignalina district municipality" => Ok(Self::IgnalinaDistrictMunicipality), - "jonava district municipality" => Ok(Self::JonavaDistrictMunicipality), - "joniskis district municipality" => Ok(Self::JoniskisDistrictMunicipality), - "jurbarkas district municipality" => Ok(Self::JurbarkasDistrictMunicipality), - "kaisiadorys district municipality" => { - Ok(Self::KaisiadorysDistrictMunicipality) - } - "kalvarija municipality" => Ok(Self::KalvarijaMunicipality), - "kaunas city municipality" => Ok(Self::KaunasCityMunicipality), - "kaunas county" => Ok(Self::KaunasCounty), - "kaunas district municipality" => Ok(Self::KaunasDistrictMunicipality), - "kazlu ruda municipality" => Ok(Self::KazluRudaMunicipality), - "kelme district municipality" => Ok(Self::KelmeDistrictMunicipality), - "klaipeda city municipality" => Ok(Self::KlaipedaCityMunicipality), - "klaipeda county" => Ok(Self::KlaipedaCounty), - "klaipeda district municipality" => Ok(Self::KlaipedaDistrictMunicipality), - "kretinga district municipality" => Ok(Self::KretingaDistrictMunicipality), - "kupiskis district municipality" => Ok(Self::KupiskisDistrictMunicipality), - "kedainiai district municipality" => Ok(Self::KedainiaiDistrictMunicipality), - "lazdijai district municipality" => Ok(Self::LazdijaiDistrictMunicipality), - "marijampole county" => Ok(Self::MarijampoleCounty), - "marijampole municipality" => Ok(Self::MarijampoleMunicipality), - "mazeikiai district municipality" => Ok(Self::MazeikiaiDistrictMunicipality), - "moletai district municipality" => Ok(Self::MoletaiDistrictMunicipality), - "neringa municipality" => Ok(Self::NeringaMunicipality), - "pagegiai municipality" => Ok(Self::PagegiaiMunicipality), - "pakruojis district municipality" => Ok(Self::PakruojisDistrictMunicipality), - "palanga city municipality" => Ok(Self::PalangaCityMunicipality), - "panevezys city municipality" => Ok(Self::PanevezysCityMunicipality), - "panevezys county" => Ok(Self::PanevezysCounty), - "panevezys district municipality" => Ok(Self::PanevezysDistrictMunicipality), - "pasvalys district municipality" => Ok(Self::PasvalysDistrictMunicipality), - "plunge district municipality" => Ok(Self::PlungeDistrictMunicipality), - "prienai district municipality" => Ok(Self::PrienaiDistrictMunicipality), - "radviliskis district municipality" => { - Ok(Self::RadviliskisDistrictMunicipality) - } - "raseiniai district municipality" => Ok(Self::RaseiniaiDistrictMunicipality), - "rietavas municipality" => Ok(Self::RietavasMunicipality), - "rokiskis district municipality" => Ok(Self::RokiskisDistrictMunicipality), - "skuodas district municipality" => Ok(Self::SkuodasDistrictMunicipality), - "taurage county" => Ok(Self::TaurageCounty), - "taurage district municipality" => Ok(Self::TaurageDistrictMunicipality), - "telsiai county" => Ok(Self::TelsiaiCounty), - "telsiai district municipality" => Ok(Self::TelsiaiDistrictMunicipality), - "trakai district municipality" => Ok(Self::TrakaiDistrictMunicipality), - "ukmerge district municipality" => Ok(Self::UkmergeDistrictMunicipality), - "utena county" => Ok(Self::UtenaCounty), - "utena district municipality" => Ok(Self::UtenaDistrictMunicipality), - "varena district municipality" => Ok(Self::VarenaDistrictMunicipality), - "vilkaviskis district municipality" => { - Ok(Self::VilkaviskisDistrictMunicipality) - } - "vilnius city municipality" => Ok(Self::VilniusCityMunicipality), - "vilnius county" => Ok(Self::VilniusCounty), - "vilnius district municipality" => Ok(Self::VilniusDistrictMunicipality), - "visaginas municipality" => Ok(Self::VisaginasMunicipality), - "zarasai district municipality" => Ok(Self::ZarasaiDistrictMunicipality), - "sakiai district municipality" => Ok(Self::SakiaiDistrictMunicipality), - "salcininkai district municipality" => { - Ok(Self::SalcininkaiDistrictMunicipality) - } - "siauliai city municipality" => Ok(Self::SiauliaiCityMunicipality), - "siauliai county" => Ok(Self::SiauliaiCounty), - "siauliai district municipality" => Ok(Self::SiauliaiDistrictMunicipality), - "silale district municipality" => Ok(Self::SilaleDistrictMunicipality), - "silute district municipality" => Ok(Self::SiluteDistrictMunicipality), - "sirvintos district municipality" => Ok(Self::SirvintosDistrictMunicipality), - "svencionys district municipality" => Ok(Self::SvencionysDistrictMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Akmenė District Municipality" => Ok(Self::AkmeneDistrictMunicipality), + "Alytus City Municipality" => Ok(Self::AlytusCityMunicipality), + "Alytus County" => Ok(Self::AlytusCounty), + "Alytus District Municipality" => Ok(Self::AlytusDistrictMunicipality), + "Birštonas Municipality" => Ok(Self::BirstonasMunicipality), + "Biržai District Municipality" => Ok(Self::BirzaiDistrictMunicipality), + "Druskininkai municipality" => Ok(Self::DruskininkaiMunicipality), + "Elektrėnai municipality" => Ok(Self::ElektrenaiMunicipality), + "Ignalina District Municipality" => Ok(Self::IgnalinaDistrictMunicipality), + "Jonava District Municipality" => Ok(Self::JonavaDistrictMunicipality), + "Joniškis District Municipality" => Ok(Self::JoniskisDistrictMunicipality), + "Jurbarkas District Municipality" => Ok(Self::JurbarkasDistrictMunicipality), + "Kaišiadorys District Municipality" => Ok(Self::KaisiadorysDistrictMunicipality), + "Kalvarija municipality" => Ok(Self::KalvarijaMunicipality), + "Kaunas City Municipality" => Ok(Self::KaunasCityMunicipality), + "Kaunas County" => Ok(Self::KaunasCounty), + "Kaunas District Municipality" => Ok(Self::KaunasDistrictMunicipality), + "Kazlų Rūda municipality" => Ok(Self::KazluRudaMunicipality), + "Kelmė District Municipality" => Ok(Self::KelmeDistrictMunicipality), + "Klaipeda City Municipality" => Ok(Self::KlaipedaCityMunicipality), + "Klaipėda County" => Ok(Self::KlaipedaCounty), + "Klaipėda District Municipality" => Ok(Self::KlaipedaDistrictMunicipality), + "Kretinga District Municipality" => Ok(Self::KretingaDistrictMunicipality), + "Kupiškis District Municipality" => Ok(Self::KupiskisDistrictMunicipality), + "Kėdainiai District Municipality" => Ok(Self::KedainiaiDistrictMunicipality), + "Lazdijai District Municipality" => Ok(Self::LazdijaiDistrictMunicipality), + "Marijampolė County" => Ok(Self::MarijampoleCounty), + "Marijampolė Municipality" => Ok(Self::MarijampoleMunicipality), + "Mažeikiai District Municipality" => Ok(Self::MazeikiaiDistrictMunicipality), + "Molėtai District Municipality" => Ok(Self::MoletaiDistrictMunicipality), + "Neringa Municipality" => Ok(Self::NeringaMunicipality), + "Pagėgiai municipality" => Ok(Self::PagegiaiMunicipality), + "Pakruojis District Municipality" => Ok(Self::PakruojisDistrictMunicipality), + "Palanga City Municipality" => Ok(Self::PalangaCityMunicipality), + "Panevėžys City Municipality" => Ok(Self::PanevezysCityMunicipality), + "Panevėžys County" => Ok(Self::PanevezysCounty), + "Panevėžys District Municipality" => Ok(Self::PanevezysDistrictMunicipality), + "Pasvalys District Municipality" => Ok(Self::PasvalysDistrictMunicipality), + "Plungė District Municipality" => Ok(Self::PlungeDistrictMunicipality), + "Prienai District Municipality" => Ok(Self::PrienaiDistrictMunicipality), + "Radviliškis District Municipality" => Ok(Self::RadviliskisDistrictMunicipality), + "Raseiniai District Municipality" => Ok(Self::RaseiniaiDistrictMunicipality), + "Rietavas municipality" => Ok(Self::RietavasMunicipality), + "Rokiškis District Municipality" => Ok(Self::RokiskisDistrictMunicipality), + "Skuodas District Municipality" => Ok(Self::SkuodasDistrictMunicipality), + "Tauragė County" => Ok(Self::TaurageCounty), + "Tauragė District Municipality" => Ok(Self::TaurageDistrictMunicipality), + "Telšiai County" => Ok(Self::TelsiaiCounty), + "Telšiai District Municipality" => Ok(Self::TelsiaiDistrictMunicipality), + "Trakai District Municipality" => Ok(Self::TrakaiDistrictMunicipality), + "Ukmergė District Municipality" => Ok(Self::UkmergeDistrictMunicipality), + "Utena County" => Ok(Self::UtenaCounty), + "Utena District Municipality" => Ok(Self::UtenaDistrictMunicipality), + "Varėna District Municipality" => Ok(Self::VarenaDistrictMunicipality), + "Vilkaviškis District Municipality" => Ok(Self::VilkaviskisDistrictMunicipality), + "Vilnius City Municipality" => Ok(Self::VilniusCityMunicipality), + "Vilnius County" => Ok(Self::VilniusCounty), + "Vilnius District Municipality" => Ok(Self::VilniusDistrictMunicipality), + "Visaginas Municipality" => Ok(Self::VisaginasMunicipality), + "Zarasai District Municipality" => Ok(Self::ZarasaiDistrictMunicipality), + "Šakiai District Municipality" => Ok(Self::SakiaiDistrictMunicipality), + "Šalčininkai District Municipality" => Ok(Self::SalcininkaiDistrictMunicipality), + "Šiauliai City Municipality" => Ok(Self::SiauliaiCityMunicipality), + "Šiauliai County" => Ok(Self::SiauliaiCounty), + "Šiauliai District Municipality" => Ok(Self::SiauliaiDistrictMunicipality), + "Šilalė District Municipality" => Ok(Self::SilaleDistrictMunicipality), + "Šilutė District Municipality" => Ok(Self::SiluteDistrictMunicipality), + "Širvintos District Municipality" => Ok(Self::SirvintosDistrictMunicipality), + "Švenčionys District Municipality" => Ok(Self::SvencionysDistrictMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3350,33 +3392,27 @@ impl ForeignTryFrom for LithuaniaStatesAbbreviation { impl ForeignTryFrom for LiechtensteinStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "LiechtensteinStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LiechtensteinStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "balzers" => Ok(Self::Balzers), - "eschen" => Ok(Self::Eschen), - "gamprin" => Ok(Self::Gamprin), - "mauren" => Ok(Self::Mauren), - "planken" => Ok(Self::Planken), - "ruggell" => Ok(Self::Ruggell), - "schaan" => Ok(Self::Schaan), - "schellenberg" => Ok(Self::Schellenberg), - "triesen" => Ok(Self::Triesen), - "triesenberg" => Ok(Self::Triesenberg), - "vaduz" => Ok(Self::Vaduz), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Balzers" => Ok(Self::Balzers), + "Eschen" => Ok(Self::Eschen), + "Gamprin" => Ok(Self::Gamprin), + "Mauren" => Ok(Self::Mauren), + "Planken" => Ok(Self::Planken), + "Ruggell" => Ok(Self::Ruggell), + "Schaan" => Ok(Self::Schaan), + "Schellenberg" => Ok(Self::Schellenberg), + "Triesen" => Ok(Self::Triesen), + "Triesenberg" => Ok(Self::Triesenberg), + "Vaduz" => Ok(Self::Vaduz), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3385,118 +3421,114 @@ impl ForeignTryFrom for LatviaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "LatviaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "LatviaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aglona municipality" => Ok(Self::AglonaMunicipality), - "aizkraukle municipality" => Ok(Self::AizkraukleMunicipality), - "aizpute municipality" => Ok(Self::AizputeMunicipality), - "akniiste municipality" => Ok(Self::AknīsteMunicipality), - "aloja municipality" => Ok(Self::AlojaMunicipality), - "alsunga municipality" => Ok(Self::AlsungaMunicipality), - "aluksne municipality" => Ok(Self::AlūksneMunicipality), - "amata municipality" => Ok(Self::AmataMunicipality), - "ape municipality" => Ok(Self::ApeMunicipality), - "auce municipality" => Ok(Self::AuceMunicipality), - "babite municipality" => Ok(Self::BabīteMunicipality), - "baldone municipality" => Ok(Self::BaldoneMunicipality), - "baltinava municipality" => Ok(Self::BaltinavaMunicipality), - "balvi municipality" => Ok(Self::BalviMunicipality), - "bauska municipality" => Ok(Self::BauskaMunicipality), - "beverina municipality" => Ok(Self::BeverīnaMunicipality), - "broceni municipality" => Ok(Self::BrocēniMunicipality), - "burtnieki municipality" => Ok(Self::BurtniekiMunicipality), - "carnikava municipality" => Ok(Self::CarnikavaMunicipality), - "cesvaine municipality" => Ok(Self::CesvaineMunicipality), - "cibla municipality" => Ok(Self::CiblaMunicipality), - "cesis municipality" => Ok(Self::CēsisMunicipality), - "dagda municipality" => Ok(Self::DagdaMunicipality), - "daugavpils" => Ok(Self::Daugavpils), - "daugavpils municipality" => Ok(Self::DaugavpilsMunicipality), - "dobele municipality" => Ok(Self::DobeleMunicipality), - "dundaga municipality" => Ok(Self::DundagaMunicipality), - "durbe municipality" => Ok(Self::DurbeMunicipality), - "engure municipality" => Ok(Self::EngureMunicipality), - "garkalne municipality" => Ok(Self::GarkalneMunicipality), - "grobina municipality" => Ok(Self::GrobiņaMunicipality), - "gulbene municipality" => Ok(Self::GulbeneMunicipality), - "iecava municipality" => Ok(Self::IecavaMunicipality), - "ikskile municipality" => Ok(Self::IkšķileMunicipality), - "ilukste municipality" => Ok(Self::IlūksteMunicipality), - "incukalns municipality" => Ok(Self::InčukalnsMunicipality), - "jaunjelgava municipality" => Ok(Self::JaunjelgavaMunicipality), - "jaunpiebalga municipality" => Ok(Self::JaunpiebalgaMunicipality), - "jaunpils municipality" => Ok(Self::JaunpilsMunicipality), - "jelgava" => Ok(Self::Jelgava), - "jelgava municipality" => Ok(Self::JelgavaMunicipality), - "jekabpils" => Ok(Self::Jēkabpils), - "jekabpils municipality" => Ok(Self::JēkabpilsMunicipality), - "jurmala" => Ok(Self::Jūrmala), - "kandava municipality" => Ok(Self::KandavaMunicipality), - "koceni municipality" => Ok(Self::KocēniMunicipality), - "koknese municipality" => Ok(Self::KokneseMunicipality), - "krimulda municipality" => Ok(Self::KrimuldaMunicipality), - "kustpils municipality" => Ok(Self::KrustpilsMunicipality), - "kraslava municipality" => Ok(Self::KrāslavaMunicipality), - "kuldiga municipality" => Ok(Self::KuldīgaMunicipality), - "karsava municipality" => Ok(Self::KārsavaMunicipality), - "lielvarde municipality" => Ok(Self::LielvārdeMunicipality), - "liepaja" => Ok(Self::Liepāja), - "limbazi municipality" => Ok(Self::LimbažiMunicipality), - "lubana municipality" => Ok(Self::LubānaMunicipality), - "ludza municipality" => Ok(Self::LudzaMunicipality), - "ligatne municipality" => Ok(Self::LīgatneMunicipality), - "livani municipality" => Ok(Self::LīvāniMunicipality), - "madona municipality" => Ok(Self::MadonaMunicipality), - "mazsalaca municipality" => Ok(Self::MazsalacaMunicipality), - "malpils municipality" => Ok(Self::MālpilsMunicipality), - "marupe municipality" => Ok(Self::MārupeMunicipality), - "mersrags municipality" => Ok(Self::MērsragsMunicipality), - "naukseni municipality" => Ok(Self::NaukšēniMunicipality), - "nereta municipality" => Ok(Self::NeretaMunicipality), - "nica municipality" => Ok(Self::NīcaMunicipality), - "ogre municipality" => Ok(Self::OgreMunicipality), - "olaine municipality" => Ok(Self::OlaineMunicipality), - "ozolnieki municipality" => Ok(Self::OzolniekiMunicipality), - "preili municipality" => Ok(Self::PreiļiMunicipality), - "priekule municipality" => Ok(Self::PriekuleMunicipality), - "priekuli municipality" => Ok(Self::PriekuļiMunicipality), - "pargauja municipality" => Ok(Self::PārgaujaMunicipality), - "pavilosta municipality" => Ok(Self::PāvilostaMunicipality), - "plavinas municipality" => Ok(Self::PļaviņasMunicipality), - "rauna municipality" => Ok(Self::RaunaMunicipality), - "riebini municipality" => Ok(Self::RiebiņiMunicipality), - "riga" => Ok(Self::Riga), - "roja municipality" => Ok(Self::RojaMunicipality), - "ropazi municipality" => Ok(Self::RopažiMunicipality), - "rucava municipality" => Ok(Self::RucavaMunicipality), - "rugaji municipality" => Ok(Self::RugājiMunicipality), - "rundale municipality" => Ok(Self::RundāleMunicipality), - "rezekne" => Ok(Self::Rēzekne), - "rezekne municipality" => Ok(Self::RēzekneMunicipality), - "rujiena municipality" => Ok(Self::RūjienaMunicipality), - "sala municipality" => Ok(Self::SalaMunicipality), - "salacgriva municipality" => Ok(Self::SalacgrīvaMunicipality), - "salaspils municipality" => Ok(Self::SalaspilsMunicipality), - "saldus municipality" => Ok(Self::SaldusMunicipality), - "saulkrasti municipality" => Ok(Self::SaulkrastiMunicipality), - "sigulda municipality" => Ok(Self::SiguldaMunicipality), - "skrunda municipality" => Ok(Self::SkrundaMunicipality), - "skriveri municipality" => Ok(Self::SkrīveriMunicipality), - "smiltene municipality" => Ok(Self::SmilteneMunicipality), - "stopini municipality" => Ok(Self::StopiņiMunicipality), - "strenci municipality" => Ok(Self::StrenčiMunicipality), - "seja municipality" => Ok(Self::SējaMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aglona Municipality" => Ok(Self::AglonaMunicipality), + "Aizkraukle Municipality" => Ok(Self::AizkraukleMunicipality), + "Aizpute Municipality" => Ok(Self::AizputeMunicipality), + "Aknīste Municipality" => Ok(Self::AknīsteMunicipality), + "Aloja Municipality" => Ok(Self::AlojaMunicipality), + "Alsunga Municipality" => Ok(Self::AlsungaMunicipality), + "Alūksne Municipality" => Ok(Self::AlūksneMunicipality), + "Amata Municipality" => Ok(Self::AmataMunicipality), + "Ape Municipality" => Ok(Self::ApeMunicipality), + "Auce Municipality" => Ok(Self::AuceMunicipality), + "Babīte Municipality" => Ok(Self::BabīteMunicipality), + "Baldone Municipality" => Ok(Self::BaldoneMunicipality), + "Baltinava Municipality" => Ok(Self::BaltinavaMunicipality), + "Balvi Municipality" => Ok(Self::BalviMunicipality), + "Bauska Municipality" => Ok(Self::BauskaMunicipality), + "Beverīna Municipality" => Ok(Self::BeverīnaMunicipality), + "Brocēni Municipality" => Ok(Self::BrocēniMunicipality), + "Burtnieki Municipality" => Ok(Self::BurtniekiMunicipality), + "Carnikava Municipality" => Ok(Self::CarnikavaMunicipality), + "Cesvaine Municipality" => Ok(Self::CesvaineMunicipality), + "Cibla Municipality" => Ok(Self::CiblaMunicipality), + "Cēsis Municipality" => Ok(Self::CēsisMunicipality), + "Dagda Municipality" => Ok(Self::DagdaMunicipality), + "Daugavpils" => Ok(Self::Daugavpils), + "Daugavpils Municipality" => Ok(Self::DaugavpilsMunicipality), + "Dobele Municipality" => Ok(Self::DobeleMunicipality), + "Dundaga Municipality" => Ok(Self::DundagaMunicipality), + "Durbe Municipality" => Ok(Self::DurbeMunicipality), + "Engure Municipality" => Ok(Self::EngureMunicipality), + "Garkalne Municipality" => Ok(Self::GarkalneMunicipality), + "Grobiņa Municipality" => Ok(Self::GrobiņaMunicipality), + "Gulbene Municipality" => Ok(Self::GulbeneMunicipality), + "Iecava Municipality" => Ok(Self::IecavaMunicipality), + "Ikšķile Municipality" => Ok(Self::IkšķileMunicipality), + "Ilūkste Municipalityy" => Ok(Self::IlūksteMunicipality), + "Inčukalns Municipality" => Ok(Self::InčukalnsMunicipality), + "Jaunjelgava Municipality" => Ok(Self::JaunjelgavaMunicipality), + "Jaunpiebalga Municipality" => Ok(Self::JaunpiebalgaMunicipality), + "Jaunpils Municipality" => Ok(Self::JaunpilsMunicipality), + "Jelgava" => Ok(Self::Jelgava), + "Jelgava Municipality" => Ok(Self::JelgavaMunicipality), + "Jēkabpils" => Ok(Self::Jēkabpils), + "Jēkabpils Municipality" => Ok(Self::JēkabpilsMunicipality), + "Jūrmala" => Ok(Self::Jūrmala), + "Kandava Municipality" => Ok(Self::KandavaMunicipality), + "Kocēni Municipality" => Ok(Self::KocēniMunicipality), + "Koknese Municipality" => Ok(Self::KokneseMunicipality), + "Krimulda Municipality" => Ok(Self::KrimuldaMunicipality), + "Krustpils Municipality" => Ok(Self::KrustpilsMunicipality), + "Krāslava Municipality" => Ok(Self::KrāslavaMunicipality), + "Kuldīga Municipality" => Ok(Self::KuldīgaMunicipality), + "Kārsava Municipality" => Ok(Self::KārsavaMunicipality), + "Lielvārde Municipality" => Ok(Self::LielvārdeMunicipality), + "Liepāja" => Ok(Self::Liepāja), + "Limbaži Municipality" => Ok(Self::LimbažiMunicipality), + "Lubāna Municipality" => Ok(Self::LubānaMunicipality), + "Ludza Municipality" => Ok(Self::LudzaMunicipality), + "Līgatne Municipality" => Ok(Self::LīgatneMunicipality), + "Līvāni Municipality" => Ok(Self::LīvāniMunicipality), + "Madona Municipality" => Ok(Self::MadonaMunicipality), + "Mazsalaca Municipality" => Ok(Self::MazsalacaMunicipality), + "Mālpils Municipality" => Ok(Self::MālpilsMunicipality), + "Mārupe Municipality" => Ok(Self::MārupeMunicipality), + "Mērsrags Municipality" => Ok(Self::MērsragsMunicipality), + "Naukšēni Municipality" => Ok(Self::NaukšēniMunicipality), + "Nereta Municipality" => Ok(Self::NeretaMunicipality), + "Nīca Municipality" => Ok(Self::NīcaMunicipality), + "Ogre Municipality" => Ok(Self::OgreMunicipality), + "Olaine Municipality" => Ok(Self::OlaineMunicipality), + "Ozolnieki Municipality" => Ok(Self::OzolniekiMunicipality), + "Preiļi Municipality" => Ok(Self::PreiļiMunicipality), + "Priekule Municipality" => Ok(Self::PriekuleMunicipality), + "Priekuļi Municipality" => Ok(Self::PriekuļiMunicipality), + "Pārgauja Municipality" => Ok(Self::PārgaujaMunicipality), + "Pāvilosta Municipality" => Ok(Self::PāvilostaMunicipality), + "Pļaviņas Municipality" => Ok(Self::PļaviņasMunicipality), + "Rauna Municipality" => Ok(Self::RaunaMunicipality), + "Riebiņi Municipality" => Ok(Self::RiebiņiMunicipality), + "Riga" => Ok(Self::Riga), + "Roja Municipality" => Ok(Self::RojaMunicipality), + "Ropaži Municipality" => Ok(Self::RopažiMunicipality), + "Rucava Municipality" => Ok(Self::RucavaMunicipality), + "Rugāji Municipality" => Ok(Self::RugājiMunicipality), + "Rundāle Municipality" => Ok(Self::RundāleMunicipality), + "Rēzekne" => Ok(Self::Rēzekne), + "Rēzekne Municipality" => Ok(Self::RēzekneMunicipality), + "Rūjiena Municipality" => Ok(Self::RūjienaMunicipality), + "Sala Municipality" => Ok(Self::SalaMunicipality), + "Salacgrīva Municipality" => Ok(Self::SalacgrīvaMunicipality), + "Salaspils Municipality" => Ok(Self::SalaspilsMunicipality), + "Saldus Municipality" => Ok(Self::SaldusMunicipality), + "Saulkrasti Municipality" => Ok(Self::SaulkrastiMunicipality), + "Sigulda Municipality" => Ok(Self::SiguldaMunicipality), + "Skrunda Municipality" => Ok(Self::SkrundaMunicipality), + "Skrīveri Municipality" => Ok(Self::SkrīveriMunicipality), + "Smiltene Municipality" => Ok(Self::SmilteneMunicipality), + "Stopiņi Municipality" => Ok(Self::StopiņiMunicipality), + "Strenči Municipality" => Ok(Self::StrenčiMunicipality), + "Sēja Municipality" => Ok(Self::SējaMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3505,87 +3537,83 @@ impl ForeignTryFrom for MaltaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "MaltaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "MaltaStatesAbbreviation"); match state_abbreviation_check { - Ok(municipality) => Ok(municipality), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let municipality = binding.as_str(); - match municipality { - "attard" => Ok(Self::Attard), - "balzan" => Ok(Self::Balzan), - "birgu" => Ok(Self::Birgu), - "birkirkara" => Ok(Self::Birkirkara), - "birzebbuga" => Ok(Self::Birzebbuga), - "cospicua" => Ok(Self::Cospicua), - "dingli" => Ok(Self::Dingli), - "fgura" => Ok(Self::Fgura), - "floriana" => Ok(Self::Floriana), - "fontana" => Ok(Self::Fontana), - "gudja" => Ok(Self::Gudja), - "gzira" => Ok(Self::Gzira), - "ghajnsielem" => Ok(Self::Ghajnsielem), - "gharb" => Ok(Self::Gharb), - "gharghur" => Ok(Self::Gharghur), - "ghasri" => Ok(Self::Ghasri), - "ghaxaq" => Ok(Self::Ghaxaq), - "hamrun" => Ok(Self::Hamrun), - "iklin" => Ok(Self::Iklin), - "senglea" => Ok(Self::Senglea), - "kalkara" => Ok(Self::Kalkara), - "kercem" => Ok(Self::Kercem), - "kirkop" => Ok(Self::Kirkop), - "lija" => Ok(Self::Lija), - "luqa" => Ok(Self::Luqa), - "marsa" => Ok(Self::Marsa), - "marsaskala" => Ok(Self::Marsaskala), - "marsaxlokk" => Ok(Self::Marsaxlokk), - "mdina" => Ok(Self::Mdina), - "mellieha" => Ok(Self::Mellieha), - "mgarr" => Ok(Self::Mgarr), - "mosta" => Ok(Self::Mosta), - "mqabba" => Ok(Self::Mqabba), - "msida" => Ok(Self::Msida), - "mtarfa" => Ok(Self::Mtarfa), - "munxar" => Ok(Self::Munxar), - "nadur" => Ok(Self::Nadur), - "naxxar" => Ok(Self::Naxxar), - "paola" => Ok(Self::Paola), - "pembroke" => Ok(Self::Pembroke), - "pieta" => Ok(Self::Pieta), - "qala" => Ok(Self::Qala), - "qormi" => Ok(Self::Qormi), - "qrendi" => Ok(Self::Qrendi), - "victoria" => Ok(Self::Victoria), - "rabat" => Ok(Self::Rabat), - "st julians" => Ok(Self::StJulians), - "san gwann" => Ok(Self::SanGwann), - "saint lawrence" => Ok(Self::SaintLawrence), - "st pauls bay" => Ok(Self::StPaulsBay), - "sannat" => Ok(Self::Sannat), - "santa lucija" => Ok(Self::SantaLucija), - "santa venera" => Ok(Self::SantaVenera), - "siggiewi" => Ok(Self::Siggiewi), - "sliema" => Ok(Self::Sliema), - "swieqi" => Ok(Self::Swieqi), - "ta xbiex" => Ok(Self::TaXbiex), - "tarxien" => Ok(Self::Tarxien), - "valletta" => Ok(Self::Valletta), - "xaghra" => Ok(Self::Xaghra), - "xewkija" => Ok(Self::Xewkija), - "xghajra" => Ok(Self::Xghajra), - "zabbar" => Ok(Self::Zabbar), - "zebbug gozo" => Ok(Self::ZebbugGozo), - "zebbug malta" => Ok(Self::ZebbugMalta), - "zejtun" => Ok(Self::Zejtun), - "zurrieq" => Ok(Self::Zurrieq), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Attard" => Ok(Self::Attard), + "Balzan" => Ok(Self::Balzan), + "Birgu" => Ok(Self::Birgu), + "Birkirkara" => Ok(Self::Birkirkara), + "Birżebbuġa" => Ok(Self::Birżebbuġa), + "Cospicua" => Ok(Self::Cospicua), + "Dingli" => Ok(Self::Dingli), + "Fgura" => Ok(Self::Fgura), + "Floriana" => Ok(Self::Floriana), + "Fontana" => Ok(Self::Fontana), + "Gudja" => Ok(Self::Gudja), + "Gżira" => Ok(Self::Gżira), + "Għajnsielem" => Ok(Self::Għajnsielem), + "Għarb" => Ok(Self::Għarb), + "Għargħur" => Ok(Self::Għargħur), + "Għasri" => Ok(Self::Għasri), + "Għaxaq" => Ok(Self::Għaxaq), + "Ħamrun" => Ok(Self::Ħamrun), + "Iklin" => Ok(Self::Iklin), + "Senglea" => Ok(Self::Senglea), + "Kalkara" => Ok(Self::Kalkara), + "Kerċem" => Ok(Self::Kerċem), + "Kirkop" => Ok(Self::Kirkop), + "Lija" => Ok(Self::Lija), + "Luqa" => Ok(Self::Luqa), + "Marsa" => Ok(Self::Marsa), + "Marsaskala" => Ok(Self::Marsaskala), + "Marsaxlokk" => Ok(Self::Marsaxlokk), + "Mdina" => Ok(Self::Mdina), + "Mellieħa" => Ok(Self::Mellieħa), + "Mosta" => Ok(Self::Mosta), + "Mqabba" => Ok(Self::Mqabba), + "Msida" => Ok(Self::Msida), + "Mtarfa" => Ok(Self::Mtarfa), + "Munxar" => Ok(Self::Munxar), + "Mġarr" => Ok(Self::Mġarr), + "Nadur" => Ok(Self::Nadur), + "Naxxar" => Ok(Self::Naxxar), + "Paola" => Ok(Self::Paola), + "Pembroke" => Ok(Self::Pembroke), + "Pietà" => Ok(Self::Pietà), + "Qala" => Ok(Self::Qala), + "Qormi" => Ok(Self::Qormi), + "Qrendi" => Ok(Self::Qrendi), + "Rabat" => Ok(Self::Rabat), + "Saint Lawrence" => Ok(Self::SaintLawrence), + "San Ġwann" => Ok(Self::SanĠwann), + "Sannat" => Ok(Self::Sannat), + "Santa Luċija" => Ok(Self::SantaLuċija), + "Santa Venera" => Ok(Self::SantaVenera), + "Siġġiewi" => Ok(Self::Siġġiewi), + "Sliema" => Ok(Self::Sliema), + "St. Julian's" => Ok(Self::StJulians), + "St. Paul's Bay" => Ok(Self::StPaulsBay), + "Swieqi" => Ok(Self::Swieqi), + "Ta' Xbiex" => Ok(Self::TaXbiex), + "Tarxien" => Ok(Self::Tarxien), + "Valletta" => Ok(Self::Valletta), + "Victoria" => Ok(Self::Victoria), + "Xagħra" => Ok(Self::Xagħra), + "Xewkija" => Ok(Self::Xewkija), + "Xgħajra" => Ok(Self::Xgħajra), + "Żabbar" => Ok(Self::Żabbar), + "Żebbuġ Gozo" => Ok(Self::ŻebbuġGozo), + "Żebbuġ Malta" => Ok(Self::ŻebbuġMalta), + "Żejtun" => Ok(Self::Żejtun), + "Żurrieq" => Ok(Self::Żurrieq), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3593,29 +3621,23 @@ impl ForeignTryFrom for MaltaStatesAbbreviation { impl ForeignTryFrom for BelarusStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BelarusStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BelarusStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "brest region" => Ok(Self::BrestRegion), - "gomel region" => Ok(Self::GomelRegion), - "grodno region" => Ok(Self::GrodnoRegion), - "minsk" => Ok(Self::Minsk), - "minsk region" => Ok(Self::MinskRegion), - "mogilev region" => Ok(Self::MogilevRegion), - "vitebsk region" => Ok(Self::VitebskRegion), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Brest Region" => Ok(Self::BrestRegion), + "Gomel Region" => Ok(Self::GomelRegion), + "Grodno Region" => Ok(Self::GrodnoRegion), + "Minsk" => Ok(Self::Minsk), + "Minsk Region" => Ok(Self::MinskRegion), + "Mogilev Region" => Ok(Self::MogilevRegion), + "Vitebsk Region" => Ok(Self::VitebskRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3623,51 +3645,45 @@ impl ForeignTryFrom for BelarusStatesAbbreviation { impl ForeignTryFrom for IrelandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "IrelandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "IrelandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "connacht" => Ok(Self::Connacht), - "county carlow" => Ok(Self::CountyCarlow), - "county cavan" => Ok(Self::CountyCavan), - "county clare" => Ok(Self::CountyClare), - "county cork" => Ok(Self::CountyCork), - "county donegal" => Ok(Self::CountyDonegal), - "county dublin" => Ok(Self::CountyDublin), - "county galway" => Ok(Self::CountyGalway), - "county kerry" => Ok(Self::CountyKerry), - "county kildare" => Ok(Self::CountyKildare), - "county kilkenny" => Ok(Self::CountyKilkenny), - "county laois" => Ok(Self::CountyLaois), - "county limerick" => Ok(Self::CountyLimerick), - "county longford" => Ok(Self::CountyLongford), - "county louth" => Ok(Self::CountyLouth), - "county mayo" => Ok(Self::CountyMayo), - "county meath" => Ok(Self::CountyMeath), - "county monaghan" => Ok(Self::CountyMonaghan), - "county offaly" => Ok(Self::CountyOffaly), - "county roscommon" => Ok(Self::CountyRoscommon), - "county sligo" => Ok(Self::CountySligo), - "county tipperary" => Ok(Self::CountyTipperary), - "county waterford" => Ok(Self::CountyWaterford), - "county westmeath" => Ok(Self::CountyWestmeath), - "county wexford" => Ok(Self::CountyWexford), - "county wicklow" => Ok(Self::CountyWicklow), - "leinster" => Ok(Self::Leinster), - "munster" => Ok(Self::Munster), - "ulster" => Ok(Self::Ulster), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Connacht" => Ok(Self::Connacht), + "County Carlow" => Ok(Self::CountyCarlow), + "County Cavan" => Ok(Self::CountyCavan), + "County Clare" => Ok(Self::CountyClare), + "County Cork" => Ok(Self::CountyCork), + "County Donegal" => Ok(Self::CountyDonegal), + "County Dublin" => Ok(Self::CountyDublin), + "County Galway" => Ok(Self::CountyGalway), + "County Kerry" => Ok(Self::CountyKerry), + "County Kildare" => Ok(Self::CountyKildare), + "County Kilkenny" => Ok(Self::CountyKilkenny), + "County Laois" => Ok(Self::CountyLaois), + "County Limerick" => Ok(Self::CountyLimerick), + "County Longford" => Ok(Self::CountyLongford), + "County Louth" => Ok(Self::CountyLouth), + "County Mayo" => Ok(Self::CountyMayo), + "County Meath" => Ok(Self::CountyMeath), + "County Monaghan" => Ok(Self::CountyMonaghan), + "County Offaly" => Ok(Self::CountyOffaly), + "County Roscommon" => Ok(Self::CountyRoscommon), + "County Sligo" => Ok(Self::CountySligo), + "County Tipperary" => Ok(Self::CountyTipperary), + "County Waterford" => Ok(Self::CountyWaterford), + "County Westmeath" => Ok(Self::CountyWestmeath), + "County Wexford" => Ok(Self::CountyWexford), + "County Wicklow" => Ok(Self::CountyWicklow), + "Leinster" => Ok(Self::Leinster), + "Munster" => Ok(Self::Munster), + "Ulster" => Ok(Self::Ulster), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3675,30 +3691,24 @@ impl ForeignTryFrom for IrelandStatesAbbreviation { impl ForeignTryFrom for IcelandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "IcelandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "IcelandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "capital region" => Ok(Self::CapitalRegion), - "eastern region" => Ok(Self::EasternRegion), - "northeastern region" => Ok(Self::NortheasternRegion), - "northwestern region" => Ok(Self::NorthwesternRegion), - "southern peninsula region" => Ok(Self::SouthernPeninsulaRegion), - "southern region" => Ok(Self::SouthernRegion), - "western region" => Ok(Self::WesternRegion), - "westfjords" => Ok(Self::Westfjords), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Capital Region" => Ok(Self::CapitalRegion), + "Eastern Region" => Ok(Self::EasternRegion), + "Northeastern Region" => Ok(Self::NortheasternRegion), + "Northwestern Region" => Ok(Self::NorthwesternRegion), + "Southern Peninsula Region" => Ok(Self::SouthernPeninsulaRegion), + "Southern Region" => Ok(Self::SouthernRegion), + "Western Region" => Ok(Self::WesternRegion), + "Westfjords" => Ok(Self::Westfjords), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3706,64 +3716,58 @@ impl ForeignTryFrom for IcelandStatesAbbreviation { impl ForeignTryFrom for HungaryStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "HungaryStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "HungaryStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "baranya county" => Ok(Self::BaranyaCounty), - "borsod abauj zemplen county" => Ok(Self::BorsodAbaujZemplenCounty), - "budapest" => Ok(Self::Budapest), - "bacs kiskun county" => Ok(Self::BacsKiskunCounty), - "bekes county" => Ok(Self::BekesCounty), - "bekescsaba" => Ok(Self::Bekescsaba), - "csongrad county" => Ok(Self::CsongradCounty), - "debrecen" => Ok(Self::Debrecen), - "dunaujvaros" => Ok(Self::Dunaujvaros), - "eger" => Ok(Self::Eger), - "fejer county" => Ok(Self::FejerCounty), - "gyor" => Ok(Self::Gyor), - "gyor moson sopron county" => Ok(Self::GyorMosonSopronCounty), - "hajdu bihar county" => Ok(Self::HajduBiharCounty), - "heves county" => Ok(Self::HevesCounty), - "hodmezovasarhely" => Ok(Self::Hodmezovasarhely), - "jasz nagykun szolnok county" => Ok(Self::JaszNagykunSzolnokCounty), - "kaposvar" => Ok(Self::Kaposvar), - "kecskemet" => Ok(Self::Kecskemet), - "miskolc" => Ok(Self::Miskolc), - "nagykanizsa" => Ok(Self::Nagykanizsa), - "nyiregyhaza" => Ok(Self::Nyiregyhaza), - "nograd county" => Ok(Self::NogradCounty), - "pest county" => Ok(Self::PestCounty), - "pecs" => Ok(Self::Pecs), - "salgotarjan" => Ok(Self::Salgotarjan), - "somogy county" => Ok(Self::SomogyCounty), - "sopron" => Ok(Self::Sopron), - "szabolcs szatmar bereg county" => Ok(Self::SzabolcsSzatmarBeregCounty), - "szeged" => Ok(Self::Szeged), - "szekszard" => Ok(Self::Szekszard), - "szolnok" => Ok(Self::Szolnok), - "szombathely" => Ok(Self::Szombathely), - "szekesfehervar" => Ok(Self::Szekesfehervar), - "tatabanya" => Ok(Self::Tatabanya), - "tolna county" => Ok(Self::TolnaCounty), - "vas county" => Ok(Self::VasCounty), - "veszprem" => Ok(Self::Veszprem), - "veszprem county" => Ok(Self::VeszpremCounty), - "zala county" => Ok(Self::ZalaCounty), - "zalaegerszeg" => Ok(Self::Zalaegerszeg), - "erd" => Ok(Self::Erd), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Baranya County" => Ok(Self::BaranyaCounty), + "Borsod-Abaúj-Zemplén County" => Ok(Self::BorsodAbaujZemplenCounty), + "Budapest" => Ok(Self::Budapest), + "Bács-Kiskun County" => Ok(Self::BacsKiskunCounty), + "Békés County" => Ok(Self::BekesCounty), + "Békéscsaba" => Ok(Self::Bekescsaba), + "Csongrád County" => Ok(Self::CsongradCounty), + "Debrecen" => Ok(Self::Debrecen), + "Dunaújváros" => Ok(Self::Dunaujvaros), + "Eger" => Ok(Self::Eger), + "Fejér County" => Ok(Self::FejerCounty), + "Győr" => Ok(Self::Gyor), + "Győr-Moson-Sopron County" => Ok(Self::GyorMosonSopronCounty), + "Hajdú-Bihar County" => Ok(Self::HajduBiharCounty), + "Heves County" => Ok(Self::HevesCounty), + "Hódmezővásárhely" => Ok(Self::Hodmezovasarhely), + "Jász-Nagykun-Szolnok County" => Ok(Self::JaszNagykunSzolnokCounty), + "Kaposvár" => Ok(Self::Kaposvar), + "Kecskemét" => Ok(Self::Kecskemet), + "Miskolc" => Ok(Self::Miskolc), + "Nagykanizsa" => Ok(Self::Nagykanizsa), + "Nyíregyháza" => Ok(Self::Nyiregyhaza), + "Nógrád County" => Ok(Self::NogradCounty), + "Pest County" => Ok(Self::PestCounty), + "Pécs" => Ok(Self::Pecs), + "Salgótarján" => Ok(Self::Salgotarjan), + "Somogy County" => Ok(Self::SomogyCounty), + "Sopron" => Ok(Self::Sopron), + "Szabolcs-Szatmár-Bereg County" => Ok(Self::SzabolcsSzatmarBeregCounty), + "Szeged" => Ok(Self::Szeged), + "Szekszárd" => Ok(Self::Szekszard), + "Szolnok" => Ok(Self::Szolnok), + "Szombathely" => Ok(Self::Szombathely), + "Székesfehérvár" => Ok(Self::Szekesfehervar), + "Tatabánya" => Ok(Self::Tatabanya), + "Tolna County" => Ok(Self::TolnaCounty), + "Vas County" => Ok(Self::VasCounty), + "Veszprém" => Ok(Self::Veszprem), + "Veszprém County" => Ok(Self::VeszpremCounty), + "Zala County" => Ok(Self::ZalaCounty), + "Zalaegerszeg" => Ok(Self::Zalaegerszeg), + "Érd" => Ok(Self::Erd), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3772,57 +3776,53 @@ impl ForeignTryFrom for GreeceStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "GreeceStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "GreeceStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "achaea regional unit" => Ok(Self::AchaeaRegionalUnit), - "aetolia acarnania regional unit" => Ok(Self::AetoliaAcarnaniaRegionalUnit), - "arcadia prefecture" => Ok(Self::ArcadiaPrefecture), - "argolis regional unit" => Ok(Self::ArgolisRegionalUnit), - "attica region" => Ok(Self::AtticaRegion), - "boeotia regional unit" => Ok(Self::BoeotiaRegionalUnit), - "central greece region" => Ok(Self::CentralGreeceRegion), - "central macedonia" => Ok(Self::CentralMacedonia), - "chania regional unit" => Ok(Self::ChaniaRegionalUnit), - "corfu prefecture" => Ok(Self::CorfuPrefecture), - "corinthia regional unit" => Ok(Self::CorinthiaRegionalUnit), - "crete region" => Ok(Self::CreteRegion), - "drama regional unit" => Ok(Self::DramaRegionalUnit), - "east attica regional unit" => Ok(Self::EastAtticaRegionalUnit), - "east macedonia and thrace" => Ok(Self::EastMacedoniaAndThrace), - "epirus region" => Ok(Self::EpirusRegion), - "euboea" => Ok(Self::Euboea), - "grevena prefecture" => Ok(Self::GrevenaPrefecture), - "imathia regional unit" => Ok(Self::ImathiaRegionalUnit), - "ioannina regional unit" => Ok(Self::IoanninaRegionalUnit), - "ionian islands region" => Ok(Self::IonianIslandsRegion), - "karditsa regional unit" => Ok(Self::KarditsaRegionalUnit), - "kastoria regional unit" => Ok(Self::KastoriaRegionalUnit), - "kefalonia prefecture" => Ok(Self::KefaloniaPrefecture), - "kilkis regional unit" => Ok(Self::KilkisRegionalUnit), - "kozani prefecture" => Ok(Self::KozaniPrefecture), - "laconia" => Ok(Self::Laconia), - "larissa prefecture" => Ok(Self::LarissaPrefecture), - "lefkada regional unit" => Ok(Self::LefkadaRegionalUnit), - "pella regional unit" => Ok(Self::PellaRegionalUnit), - "peloponnese region" => Ok(Self::PeloponneseRegion), - "phthiotis prefecture" => Ok(Self::PhthiotisPrefecture), - "preveza prefecture" => Ok(Self::PrevezaPrefecture), - "serres prefecture" => Ok(Self::SerresPrefecture), - "south aegean" => Ok(Self::SouthAegean), - "thessaloniki regional unit" => Ok(Self::ThessalonikiRegionalUnit), - "west greece region" => Ok(Self::WestGreeceRegion), - "west macedonia region" => Ok(Self::WestMacedoniaRegion), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Achaea Regional Unit" => Ok(Self::AchaeaRegionalUnit), + "Aetolia-Acarnania Regional Unit" => Ok(Self::AetoliaAcarnaniaRegionalUnit), + "Arcadia Prefecture" => Ok(Self::ArcadiaPrefecture), + "Argolis Regional Unit" => Ok(Self::ArgolisRegionalUnit), + "Attica Region" => Ok(Self::AtticaRegion), + "Boeotia Regional Unit" => Ok(Self::BoeotiaRegionalUnit), + "Central Greece Region" => Ok(Self::CentralGreeceRegion), + "Central Macedonia" => Ok(Self::CentralMacedonia), + "Chania Regional Unit" => Ok(Self::ChaniaRegionalUnit), + "Corfu Prefecture" => Ok(Self::CorfuPrefecture), + "Corinthia Regional Unit" => Ok(Self::CorinthiaRegionalUnit), + "Crete Region" => Ok(Self::CreteRegion), + "Drama Regional Unit" => Ok(Self::DramaRegionalUnit), + "East Attica Regional Unit" => Ok(Self::EastAtticaRegionalUnit), + "East Macedonia and Thrace" => Ok(Self::EastMacedoniaAndThrace), + "Epirus Region" => Ok(Self::EpirusRegion), + "Euboea" => Ok(Self::Euboea), + "Grevena Prefecture" => Ok(Self::GrevenaPrefecture), + "Imathia Regional Unit" => Ok(Self::ImathiaRegionalUnit), + "Ioannina Regional Unit" => Ok(Self::IoanninaRegionalUnit), + "Ionian Islands Region" => Ok(Self::IonianIslandsRegion), + "Karditsa Regional Unit" => Ok(Self::KarditsaRegionalUnit), + "Kastoria Regional Unit" => Ok(Self::KastoriaRegionalUnit), + "Kefalonia Prefecture" => Ok(Self::KefaloniaPrefecture), + "Kilkis Regional Unit" => Ok(Self::KilkisRegionalUnit), + "Kozani Prefecture" => Ok(Self::KozaniPrefecture), + "Laconia" => Ok(Self::Laconia), + "Larissa Prefecture" => Ok(Self::LarissaPrefecture), + "Lefkada Regional Unit" => Ok(Self::LefkadaRegionalUnit), + "Pella Regional Unit" => Ok(Self::PellaRegionalUnit), + "Peloponnese Region" => Ok(Self::PeloponneseRegion), + "Phthiotis Prefecture" => Ok(Self::PhthiotisPrefecture), + "Preveza Prefecture" => Ok(Self::PrevezaPrefecture), + "Serres Prefecture" => Ok(Self::SerresPrefecture), + "South Aegean" => Ok(Self::SouthAegean), + "Thessaloniki Regional Unit" => Ok(Self::ThessalonikiRegionalUnit), + "West Greece Region" => Ok(Self::WestGreeceRegion), + "West Macedonia Region" => Ok(Self::WestMacedoniaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3830,43 +3830,37 @@ impl ForeignTryFrom for GreeceStatesAbbreviation { impl ForeignTryFrom for FinlandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "FinlandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "FinlandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "central finland" => Ok(Self::CentralFinland), - "central ostrobothnia" => Ok(Self::CentralOstrobothnia), - "eastern finland province" => Ok(Self::EasternFinlandProvince), - "finland proper" => Ok(Self::FinlandProper), - "kainuu" => Ok(Self::Kainuu), - "kymenlaakso" => Ok(Self::Kymenlaakso), - "lapland" => Ok(Self::Lapland), - "north karelia" => Ok(Self::NorthKarelia), - "northern ostrobothnia" => Ok(Self::NorthernOstrobothnia), - "northern savonia" => Ok(Self::NorthernSavonia), - "ostrobothnia" => Ok(Self::Ostrobothnia), - "oulu province" => Ok(Self::OuluProvince), - "pirkanmaa" => Ok(Self::Pirkanmaa), - "paijanne tavastia" => Ok(Self::PaijanneTavastia), - "satakunta" => Ok(Self::Satakunta), - "south karelia" => Ok(Self::SouthKarelia), - "southern ostrobothnia" => Ok(Self::SouthernOstrobothnia), - "southern savonia" => Ok(Self::SouthernSavonia), - "tavastia proper" => Ok(Self::TavastiaProper), - "uusimaa" => Ok(Self::Uusimaa), - "aland islands" => Ok(Self::AlandIslands), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Central Finland" => Ok(Self::CentralFinland), + "Central Ostrobothnia" => Ok(Self::CentralOstrobothnia), + "Eastern Finland Province" => Ok(Self::EasternFinlandProvince), + "Finland Proper" => Ok(Self::FinlandProper), + "Kainuu" => Ok(Self::Kainuu), + "Kymenlaakso" => Ok(Self::Kymenlaakso), + "Lapland" => Ok(Self::Lapland), + "North Karelia" => Ok(Self::NorthKarelia), + "Northern Ostrobothnia" => Ok(Self::NorthernOstrobothnia), + "Northern Savonia" => Ok(Self::NorthernSavonia), + "Ostrobothnia" => Ok(Self::Ostrobothnia), + "Oulu Province" => Ok(Self::OuluProvince), + "Pirkanmaa" => Ok(Self::Pirkanmaa), + "Päijänne Tavastia" => Ok(Self::PaijanneTavastia), + "Satakunta" => Ok(Self::Satakunta), + "South Karelia" => Ok(Self::SouthKarelia), + "Southern Ostrobothnia" => Ok(Self::SouthernOstrobothnia), + "Southern Savonia" => Ok(Self::SouthernSavonia), + "Tavastia Proper" => Ok(Self::TavastiaProper), + "Uusimaa" => Ok(Self::Uusimaa), + "Åland Islands" => Ok(Self::AlandIslands), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3874,27 +3868,21 @@ impl ForeignTryFrom for FinlandStatesAbbreviation { impl ForeignTryFrom for DenmarkStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "DenmarkStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "DenmarkStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "capital region of denmark" => Ok(Self::CapitalRegionOfDenmark), - "central denmark region" => Ok(Self::CentralDenmarkRegion), - "north denmark region" => Ok(Self::NorthDenmarkRegion), - "region zealand" => Ok(Self::RegionZealand), - "region of southern denmark" => Ok(Self::RegionOfSouthernDenmark), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Capital Region of Denmark" => Ok(Self::CapitalRegionOfDenmark), + "Central Denmark Region" => Ok(Self::CentralDenmarkRegion), + "North Denmark Region" => Ok(Self::NorthDenmarkRegion), + "Region Zealand" => Ok(Self::RegionZealand), + "Region of Southern Denmark" => Ok(Self::RegionOfSouthernDenmark), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3902,129 +3890,123 @@ impl ForeignTryFrom for DenmarkStatesAbbreviation { impl ForeignTryFrom for CzechRepublicStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "CzechRepublicStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "CzechRepublicStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "benesov district" => Ok(Self::BenesovDistrict), - "beroun district" => Ok(Self::BerounDistrict), - "blansko district" => Ok(Self::BlanskoDistrict), - "brno city district" => Ok(Self::BrnoCityDistrict), - "brno country district" => Ok(Self::BrnoCountryDistrict), - "bruntal district" => Ok(Self::BruntalDistrict), - "breclav district" => Ok(Self::BreclavDistrict), - "central bohemian region" => Ok(Self::CentralBohemianRegion), - "cheb district" => Ok(Self::ChebDistrict), - "chomutov district" => Ok(Self::ChomutovDistrict), - "chrudim district" => Ok(Self::ChrudimDistrict), - "domazlice district" => Ok(Self::DomazliceDistrict), - "decin district" => Ok(Self::DecinDistrict), - "frydek mistek district" => Ok(Self::FrydekMistekDistrict), - "havlickuv brod district" => Ok(Self::HavlickuvBrodDistrict), - "hodonin district" => Ok(Self::HodoninDistrict), - "horni pocernice" => Ok(Self::HorniPocernice), - "hradec kralove district" => Ok(Self::HradecKraloveDistrict), - "hradec kralove region" => Ok(Self::HradecKraloveRegion), - "jablonec nad nisou district" => Ok(Self::JablonecNadNisouDistrict), - "jesenik district" => Ok(Self::JesenikDistrict), - "jihlava district" => Ok(Self::JihlavaDistrict), - "jindrichuv hradec district" => Ok(Self::JindrichuvHradecDistrict), - "jicin district" => Ok(Self::JicinDistrict), - "karlovy vary district" => Ok(Self::KarlovyVaryDistrict), - "karlovy vary region" => Ok(Self::KarlovyVaryRegion), - "karvina district" => Ok(Self::KarvinaDistrict), - "kladno district" => Ok(Self::KladnoDistrict), - "klatovy district" => Ok(Self::KlatovyDistrict), - "kolin district" => Ok(Self::KolinDistrict), - "kromeriz district" => Ok(Self::KromerizDistrict), - "liberec district" => Ok(Self::LiberecDistrict), - "liberec region" => Ok(Self::LiberecRegion), - "litomerice district" => Ok(Self::LitomericeDistrict), - "louny district" => Ok(Self::LounyDistrict), - "mlada boleslav district" => Ok(Self::MladaBoleslavDistrict), - "moravian silesian region" => Ok(Self::MoravianSilesianRegion), - "most district" => Ok(Self::MostDistrict), - "melnik district" => Ok(Self::MelnikDistrict), - "novy jicin district" => Ok(Self::NovyJicinDistrict), - "nymburk district" => Ok(Self::NymburkDistrict), - "nachod district" => Ok(Self::NachodDistrict), - "olomouc district" => Ok(Self::OlomoucDistrict), - "olomouc region" => Ok(Self::OlomoucRegion), - "opava district" => Ok(Self::OpavaDistrict), - "ostrava city district" => Ok(Self::OstravaCityDistrict), - "pardubice district" => Ok(Self::PardubiceDistrict), - "pardubice region" => Ok(Self::PardubiceRegion), - "pelhrimov district" => Ok(Self::PelhrimovDistrict), - "plzen region" => Ok(Self::PlzenRegion), - "plzen city district" => Ok(Self::PlzenCityDistrict), - "plzen north district" => Ok(Self::PlzenNorthDistrict), - "plzen south district" => Ok(Self::PlzenSouthDistrict), - "prachatice district" => Ok(Self::PrachaticeDistrict), - "prague" => Ok(Self::Prague), - "prague1" => Ok(Self::Prague1), - "prague10" => Ok(Self::Prague10), - "prague11" => Ok(Self::Prague11), - "prague12" => Ok(Self::Prague12), - "prague13" => Ok(Self::Prague13), - "prague14" => Ok(Self::Prague14), - "prague15" => Ok(Self::Prague15), - "prague16" => Ok(Self::Prague16), - "prague2" => Ok(Self::Prague2), - "prague21" => Ok(Self::Prague21), - "prague3" => Ok(Self::Prague3), - "prague4" => Ok(Self::Prague4), - "prague5" => Ok(Self::Prague5), - "prague6" => Ok(Self::Prague6), - "prague7" => Ok(Self::Prague7), - "prague8" => Ok(Self::Prague8), - "prague9" => Ok(Self::Prague9), - "prague east district" => Ok(Self::PragueEastDistrict), - "prague west district" => Ok(Self::PragueWestDistrict), - "prostejov district" => Ok(Self::ProstejovDistrict), - "pisek district" => Ok(Self::PisekDistrict), - "prerov district" => Ok(Self::PrerovDistrict), - "pribram district" => Ok(Self::PribramDistrict), - "rakovnik district" => Ok(Self::RakovnikDistrict), - "rokycany district" => Ok(Self::RokycanyDistrict), - "rychnov nad kneznou district" => Ok(Self::RychnovNadKneznouDistrict), - "semily district" => Ok(Self::SemilyDistrict), - "sokolov district" => Ok(Self::SokolovDistrict), - "south bohemian region" => Ok(Self::SouthBohemianRegion), - "south moravian region" => Ok(Self::SouthMoravianRegion), - "strakonice district" => Ok(Self::StrakoniceDistrict), - "svitavy district" => Ok(Self::SvitavyDistrict), - "tachov district" => Ok(Self::TachovDistrict), - "teplice district" => Ok(Self::TepliceDistrict), - "trutnov district" => Ok(Self::TrutnovDistrict), - "tabor district" => Ok(Self::TaborDistrict), - "trebic district" => Ok(Self::TrebicDistrict), - "uherske hradiste district" => Ok(Self::UherskeHradisteDistrict), - "vsetin district" => Ok(Self::VsetinDistrict), - "vysocina region" => Ok(Self::VysocinaRegion), - "vyskov district" => Ok(Self::VyskovDistrict), - "zlin district" => Ok(Self::ZlinDistrict), - "zlin region" => Ok(Self::ZlinRegion), - "znojmo district" => Ok(Self::ZnojmoDistrict), - "usti nad labem district" => Ok(Self::UstiNadLabemDistrict), - "usti nad labem region" => Ok(Self::UstiNadLabemRegion), - "usti nad orlici district" => Ok(Self::UstiNadOrliciDistrict), - "ceska lipa district" => Ok(Self::CeskaLipaDistrict), - "ceske budejovice district" => Ok(Self::CeskeBudejoviceDistrict), - "cesky krumlov district" => Ok(Self::CeskyKrumlovDistrict), - "sumperk district" => Ok(Self::SumperkDistrict), - "zdar nad sazavou district" => Ok(Self::ZdarNadSazavouDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Benešov District" => Ok(Self::BenesovDistrict), + "Beroun District" => Ok(Self::BerounDistrict), + "Blansko District" => Ok(Self::BlanskoDistrict), + "Brno-City District" => Ok(Self::BrnoCityDistrict), + "Brno-Country District" => Ok(Self::BrnoCountryDistrict), + "Bruntál District" => Ok(Self::BruntalDistrict), + "Břeclav District" => Ok(Self::BreclavDistrict), + "Central Bohemian Region" => Ok(Self::CentralBohemianRegion), + "Cheb District" => Ok(Self::ChebDistrict), + "Chomutov District" => Ok(Self::ChomutovDistrict), + "Chrudim District" => Ok(Self::ChrudimDistrict), + "Domažlice Distric" => Ok(Self::DomazliceDistrict), + "Děčín District" => Ok(Self::DecinDistrict), + "Frýdek-Místek District" => Ok(Self::FrydekMistekDistrict), + "Havlíčkův Brod District" => Ok(Self::HavlickuvBrodDistrict), + "Hodonín District" => Ok(Self::HodoninDistrict), + "Horní Počernice" => Ok(Self::HorniPocernice), + "Hradec Králové District" => Ok(Self::HradecKraloveDistrict), + "Hradec Králové Region" => Ok(Self::HradecKraloveRegion), + "Jablonec nad Nisou District" => Ok(Self::JablonecNadNisouDistrict), + "Jeseník District" => Ok(Self::JesenikDistrict), + "Jihlava District" => Ok(Self::JihlavaDistrict), + "Jindřichův Hradec District" => Ok(Self::JindrichuvHradecDistrict), + "Jičín District" => Ok(Self::JicinDistrict), + "Karlovy Vary District" => Ok(Self::KarlovyVaryDistrict), + "Karlovy Vary Region" => Ok(Self::KarlovyVaryRegion), + "Karviná District" => Ok(Self::KarvinaDistrict), + "Kladno District" => Ok(Self::KladnoDistrict), + "Klatovy District" => Ok(Self::KlatovyDistrict), + "Kolín District" => Ok(Self::KolinDistrict), + "Kroměříž District" => Ok(Self::KromerizDistrict), + "Liberec District" => Ok(Self::LiberecDistrict), + "Liberec Region" => Ok(Self::LiberecRegion), + "Litoměřice District" => Ok(Self::LitomericeDistrict), + "Louny District" => Ok(Self::LounyDistrict), + "Mladá Boleslav District" => Ok(Self::MladaBoleslavDistrict), + "Moravian-Silesian Region" => Ok(Self::MoravianSilesianRegion), + "Most District" => Ok(Self::MostDistrict), + "Mělník District" => Ok(Self::MelnikDistrict), + "Nový Jičín District" => Ok(Self::NovyJicinDistrict), + "Nymburk District" => Ok(Self::NymburkDistrict), + "Náchod District" => Ok(Self::NachodDistrict), + "Olomouc District" => Ok(Self::OlomoucDistrict), + "Olomouc Region" => Ok(Self::OlomoucRegion), + "Opava District" => Ok(Self::OpavaDistrict), + "Ostrava-City District" => Ok(Self::OstravaCityDistrict), + "Pardubice District" => Ok(Self::PardubiceDistrict), + "Pardubice Region" => Ok(Self::PardubiceRegion), + "Pelhřimov District" => Ok(Self::PelhrimovDistrict), + "Plzeň Region" => Ok(Self::PlzenRegion), + "Plzeň-City District" => Ok(Self::PlzenCityDistrict), + "Plzeň-North District" => Ok(Self::PlzenNorthDistrict), + "Plzeň-South District" => Ok(Self::PlzenSouthDistrict), + "Prachatice District" => Ok(Self::PrachaticeDistrict), + "Prague" => Ok(Self::Prague), + "Prague 1" => Ok(Self::Prague1), + "Prague 10" => Ok(Self::Prague10), + "Prague 11" => Ok(Self::Prague11), + "Prague 12" => Ok(Self::Prague12), + "Prague 13" => Ok(Self::Prague13), + "Prague 14" => Ok(Self::Prague14), + "Prague 15" => Ok(Self::Prague15), + "Prague 16" => Ok(Self::Prague16), + "Prague 2" => Ok(Self::Prague2), + "Prague 21" => Ok(Self::Prague21), + "Prague 3" => Ok(Self::Prague3), + "Prague 4" => Ok(Self::Prague4), + "Prague 5" => Ok(Self::Prague5), + "Prague 6" => Ok(Self::Prague6), + "Prague 7" => Ok(Self::Prague7), + "Prague 8" => Ok(Self::Prague8), + "Prague 9" => Ok(Self::Prague9), + "Prague-East District" => Ok(Self::PragueEastDistrict), + "Prague-West District" => Ok(Self::PragueWestDistrict), + "Prostějov District" => Ok(Self::ProstejovDistrict), + "Písek District" => Ok(Self::PisekDistrict), + "Přerov District" => Ok(Self::PrerovDistrict), + "Příbram District" => Ok(Self::PribramDistrict), + "Rakovník District" => Ok(Self::RakovnikDistrict), + "Rokycany District" => Ok(Self::RokycanyDistrict), + "Rychnov nad Kněžnou District" => Ok(Self::RychnovNadKneznouDistrict), + "Semily District" => Ok(Self::SemilyDistrict), + "Sokolov District" => Ok(Self::SokolovDistrict), + "South Bohemian Region" => Ok(Self::SouthBohemianRegion), + "South Moravian Region" => Ok(Self::SouthMoravianRegion), + "Strakonice District" => Ok(Self::StrakoniceDistrict), + "Svitavy District" => Ok(Self::SvitavyDistrict), + "Tachov District" => Ok(Self::TachovDistrict), + "Teplice District" => Ok(Self::TepliceDistrict), + "Trutnov District" => Ok(Self::TrutnovDistrict), + "Tábor District" => Ok(Self::TaborDistrict), + "Třebíč District" => Ok(Self::TrebicDistrict), + "Uherské Hradiště District" => Ok(Self::UherskeHradisteDistrict), + "Vsetín District" => Ok(Self::VsetinDistrict), + "Vysočina Region" => Ok(Self::VysocinaRegion), + "Vyškov District" => Ok(Self::VyskovDistrict), + "Zlín District" => Ok(Self::ZlinDistrict), + "Zlín Region" => Ok(Self::ZlinRegion), + "Znojmo District" => Ok(Self::ZnojmoDistrict), + "Ústí nad Labem District" => Ok(Self::UstiNadLabemDistrict), + "Ústí nad Labem Region" => Ok(Self::UstiNadLabemRegion), + "Ústí nad Orlicí District" => Ok(Self::UstiNadOrliciDistrict), + "Česká Lípa District" => Ok(Self::CeskaLipaDistrict), + "České Budějovice District" => Ok(Self::CeskeBudejoviceDistrict), + "Český Krumlov District" => Ok(Self::CeskyKrumlovDistrict), + "Šumperk District" => Ok(Self::SumperkDistrict), + "Žďár nad Sázavou District" => Ok(Self::ZdarNadSazavouDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4032,42 +4014,36 @@ impl ForeignTryFrom for CzechRepublicStatesAbbreviation { impl ForeignTryFrom for CroatiaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "CroatiaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "CroatiaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bjelovar bilogora county" => Ok(Self::BjelovarBilogoraCounty), - "brod posavina county" => Ok(Self::BrodPosavinaCounty), - "dubrovnik neretva county" => Ok(Self::DubrovnikNeretvaCounty), - "istria county" => Ok(Self::IstriaCounty), - "koprivnica krizevci county" => Ok(Self::KoprivnicaKrizevciCounty), - "krapina zagorje county" => Ok(Self::KrapinaZagorjeCounty), - "lika senj county" => Ok(Self::LikaSenjCounty), - "medimurje county" => Ok(Self::MedimurjeCounty), - "osijek baranja county" => Ok(Self::OsijekBaranjaCounty), - "pozega slavonian county" => Ok(Self::PozegaSlavoniaCounty), - "primorje gorski kotar county" => Ok(Self::PrimorjeGorskiKotarCounty), - "sisak moslavina county" => Ok(Self::SisakMoslavinaCounty), - "split dalmatia county" => Ok(Self::SplitDalmatiaCounty), - "varazdin county" => Ok(Self::VarazdinCounty), - "virovitica podravina county" => Ok(Self::ViroviticaPodravinaCounty), - "vukovar syrmia county" => Ok(Self::VukovarSyrmiaCounty), - "zadar county" => Ok(Self::ZadarCounty), - "zagreb" => Ok(Self::Zagreb), - "zagreb county" => Ok(Self::ZagrebCounty), - "sibenik knin county" => Ok(Self::SibenikKninCounty), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bjelovar-Bilogora County" => Ok(Self::BjelovarBilogoraCounty), + "Brod-Posavina County" => Ok(Self::BrodPosavinaCounty), + "Dubrovnik-Neretva County" => Ok(Self::DubrovnikNeretvaCounty), + "Istria County" => Ok(Self::IstriaCounty), + "Koprivnica-Križevci County" => Ok(Self::KoprivnicaKrizevciCounty), + "Krapina-Zagorje County" => Ok(Self::KrapinaZagorjeCounty), + "Lika-Senj County" => Ok(Self::LikaSenjCounty), + "Međimurje County" => Ok(Self::MedimurjeCounty), + "Osijek-Baranja County" => Ok(Self::OsijekBaranjaCounty), + "Požega-Slavonia County" => Ok(Self::PozegaSlavoniaCounty), + "Primorje-Gorski Kotar County" => Ok(Self::PrimorjeGorskiKotarCounty), + "Sisak-Moslavina County" => Ok(Self::SisakMoslavinaCounty), + "Split-Dalmatia County" => Ok(Self::SplitDalmatiaCounty), + "Varaždin County" => Ok(Self::VarazdinCounty), + "Virovitica-Podravina County" => Ok(Self::ViroviticaPodravinaCounty), + "Vukovar-Syrmia County" => Ok(Self::VukovarSyrmiaCounty), + "Zadar County" => Ok(Self::ZadarCounty), + "Zagreb" => Ok(Self::Zagreb), + "Zagreb County" => Ok(Self::ZagrebCounty), + "Šibenik-Knin County" => Ok(Self::SibenikKninCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4075,50 +4051,44 @@ impl ForeignTryFrom for CroatiaStatesAbbreviation { impl ForeignTryFrom for BulgariaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BulgariaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BulgariaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "blagoevgrad province" => Ok(Self::BlagoevgradProvince), - "burgas province" => Ok(Self::BurgasProvince), - "dobrich province" => Ok(Self::DobrichProvince), - "gabrovo province" => Ok(Self::GabrovoProvince), - "haskovo province" => Ok(Self::HaskovoProvince), - "kardzhali province" => Ok(Self::KardzhaliProvince), - "kyustendil province" => Ok(Self::KyustendilProvince), - "lovech province" => Ok(Self::LovechProvince), - "montana province" => Ok(Self::MontanaProvince), - "pazardzhik province" => Ok(Self::PazardzhikProvince), - "pernik province" => Ok(Self::PernikProvince), - "pleven province" => Ok(Self::PlevenProvince), - "plovdiv province" => Ok(Self::PlovdivProvince), - "razgrad province" => Ok(Self::RazgradProvince), - "ruse province" => Ok(Self::RuseProvince), - "shumen" => Ok(Self::Shumen), - "silistra province" => Ok(Self::SilistraProvince), - "sliven province" => Ok(Self::SlivenProvince), - "smolyan province" => Ok(Self::SmolyanProvince), - "sofia city province" => Ok(Self::SofiaCityProvince), - "sofia province" => Ok(Self::SofiaProvince), - "stara zagora province" => Ok(Self::StaraZagoraProvince), - "targovishte province" => Ok(Self::TargovishteProvince), - "varna province" => Ok(Self::VarnaProvince), - "veliko tarnovo province" => Ok(Self::VelikoTarnovoProvince), - "vidin province" => Ok(Self::VidinProvince), - "vratsa province" => Ok(Self::VratsaProvince), - "yambol province" => Ok(Self::YambolProvince), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Blagoevgrad Province" => Ok(Self::BlagoevgradProvince), + "Burgas Province" => Ok(Self::BurgasProvince), + "Dobrich Province" => Ok(Self::DobrichProvince), + "Gabrovo Province" => Ok(Self::GabrovoProvince), + "Haskovo Province" => Ok(Self::HaskovoProvince), + "Kardzhali Province" => Ok(Self::KardzhaliProvince), + "Kyustendil Province" => Ok(Self::KyustendilProvince), + "Lovech Province" => Ok(Self::LovechProvince), + "Montana Province" => Ok(Self::MontanaProvince), + "Pazardzhik Province" => Ok(Self::PazardzhikProvince), + "Pernik Province" => Ok(Self::PernikProvince), + "Pleven Province" => Ok(Self::PlevenProvince), + "Plovdiv Province" => Ok(Self::PlovdivProvince), + "Razgrad Province" => Ok(Self::RazgradProvince), + "Ruse Province" => Ok(Self::RuseProvince), + "Shumen" => Ok(Self::Shumen), + "Silistra Province" => Ok(Self::SilistraProvince), + "Sliven Province" => Ok(Self::SlivenProvince), + "Smolyan Province" => Ok(Self::SmolyanProvince), + "Sofia City Province" => Ok(Self::SofiaCityProvince), + "Sofia Province" => Ok(Self::SofiaProvince), + "Stara Zagora Province" => Ok(Self::StaraZagoraProvince), + "Targovishte Provinc" => Ok(Self::TargovishteProvince), + "Varna Province" => Ok(Self::VarnaProvince), + "Veliko Tarnovo Province" => Ok(Self::VelikoTarnovoProvince), + "Vidin Province" => Ok(Self::VidinProvince), + "Vratsa Province" => Ok(Self::VratsaProvince), + "Yambol Province" => Ok(Self::YambolProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4126,37 +4096,31 @@ impl ForeignTryFrom for BulgariaStatesAbbreviation { impl ForeignTryFrom for BosniaAndHerzegovinaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BosniaAndHerzegovinaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BosniaAndHerzegovinaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bosnian podrinje canton" => Ok(Self::BosnianPodrinjeCanton), - "brcko district" => Ok(Self::BrckoDistrict), - "canton 10" => Ok(Self::Canton10), - "central bosnia canton" => Ok(Self::CentralBosniaCanton), - "federation of bosnia and herzegovina" => { - Ok(Self::FederationOfBosniaAndHerzegovina) - } - "herzegovina neretva canton" => Ok(Self::HerzegovinaNeretvaCanton), - "posavina canton" => Ok(Self::PosavinaCanton), - "republika srpska" => Ok(Self::RepublikaSrpska), - "sarajevo canton" => Ok(Self::SarajevoCanton), - "tuzla canton" => Ok(Self::TuzlaCanton), - "una sana canton" => Ok(Self::UnaSanaCanton), - "west herzegovina canton" => Ok(Self::WestHerzegovinaCanton), - "zenica doboj canton" => Ok(Self::ZenicaDobojCanton), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bosnian Podrinje Canton" => Ok(Self::BosnianPodrinjeCanton), + "Brčko District" => Ok(Self::BrckoDistrict), + "Canton 10" => Ok(Self::Canton10), + "Central Bosnia Canton" => Ok(Self::CentralBosniaCanton), + "Federation of Bosnia and Herzegovina" => { + Ok(Self::FederationOfBosniaAndHerzegovina) } - } + "Herzegovina-Neretva Canton" => Ok(Self::HerzegovinaNeretvaCanton), + "Posavina Canton" => Ok(Self::PosavinaCanton), + "Republika Srpska" => Ok(Self::RepublikaSrpska), + "Sarajevo Canton" => Ok(Self::SarajevoCanton), + "Tuzla Canton" => Ok(Self::TuzlaCanton), + "Una-Sana Canton" => Ok(Self::UnaSanaCanton), + "West Herzegovina Canton" => Ok(Self::WestHerzegovinaCanton), + "Zenica-Doboj Canton" => Ok(Self::ZenicaDobojCanton), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -4164,242 +4128,842 @@ impl ForeignTryFrom for BosniaAndHerzegovinaStatesAbbreviation { impl ForeignTryFrom for UnitedKingdomStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "UnitedKingdomStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "UnitedKingdomStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aberdeen city" => Ok(Self::AberdeenCity), - "aberdeenshire" => Ok(Self::Aberdeenshire), - "angus" => Ok(Self::Angus), - "antrim and newtownabbey" => Ok(Self::AntrimAndNewtownabbey), - "ards and north down" => Ok(Self::ArdsAndNorthDown), - "argyll and bute" => Ok(Self::ArgyllAndBute), - "armagh city banbridge and craigavon" => { - Ok(Self::ArmaghCityBanbridgeAndCraigavon) - } - "barking and dagenham" => Ok(Self::BarkingAndDagenham), - "barnet" => Ok(Self::Barnet), - "barnsley" => Ok(Self::Barnsley), - "bath and north east somerset" => Ok(Self::BathAndNorthEastSomerset), - "bedford" => Ok(Self::Bedford), - "belfast city" => Ok(Self::BelfastCity), - "bexley" => Ok(Self::Bexley), - "birmingham" => Ok(Self::Birmingham), - "blackburn with darwen" => Ok(Self::BlackburnWithDarwen), - "blackpool" => Ok(Self::Blackpool), - "blaenau gwent" => Ok(Self::BlaenauGwent), - "bolton" => Ok(Self::Bolton), - "bournemouth christchurch and poole" => { - Ok(Self::BournemouthChristchurchAndPoole) - } - "bracknell forest" => Ok(Self::BracknellForest), - "bradford" => Ok(Self::Bradford), - "brent" => Ok(Self::Brent), - "bridgend" => Ok(Self::Bridgend), - "brighton and hove" => Ok(Self::BrightonAndHove), - "bristol city of" => Ok(Self::BristolCityOf), - "bromley" => Ok(Self::Bromley), - "buckinghamshire" => Ok(Self::Buckinghamshire), - "bury" => Ok(Self::Bury), - "caerphilly" => Ok(Self::Caerphilly), - "calderdale" => Ok(Self::Calderdale), - "cambridgeshire" => Ok(Self::Cambridgeshire), - "camden" => Ok(Self::Camden), - "cardiff" => Ok(Self::Cardiff), - "carmarthenshire" => Ok(Self::Carmarthenshire), - "causeway coast and glens" => Ok(Self::CausewayCoastAndGlens), - "central bedfordshire" => Ok(Self::CentralBedfordshire), - "ceredigion" => Ok(Self::Ceredigion), - "cheshire east" => Ok(Self::CheshireEast), - "cheshire west and chester" => Ok(Self::CheshireWestAndChester), - "clackmannanshire" => Ok(Self::Clackmannanshire), - "conwy" => Ok(Self::Conwy), - "cornwall" => Ok(Self::Cornwall), - "coventry" => Ok(Self::Coventry), - "croydon" => Ok(Self::Croydon), - "cumbria" => Ok(Self::Cumbria), - "darlington" => Ok(Self::Darlington), - "denbighshire" => Ok(Self::Denbighshire), - "derby" => Ok(Self::Derby), - "derbyshire" => Ok(Self::Derbyshire), - "derry and strabane" => Ok(Self::DerryAndStrabane), - "devon" => Ok(Self::Devon), - "doncaster" => Ok(Self::Doncaster), - "dorset" => Ok(Self::Dorset), - "dudley" => Ok(Self::Dudley), - "dumfries and galloway" => Ok(Self::DumfriesAndGalloway), - "dundee city" => Ok(Self::DundeeCity), - "durham county" => Ok(Self::DurhamCounty), - "ealing" => Ok(Self::Ealing), - "east ayrshire" => Ok(Self::EastAyrshire), - "east dunbartonshire" => Ok(Self::EastDunbartonshire), - "east lothian" => Ok(Self::EastLothian), - "east renfrewshire" => Ok(Self::EastRenfrewshire), - "east riding of yorkshire" => Ok(Self::EastRidingOfYorkshire), - "east sussex" => Ok(Self::EastSussex), - "edinburgh city of" => Ok(Self::EdinburghCityOf), - "eilean siar" => Ok(Self::EileanSiar), - "enfield" => Ok(Self::Enfield), - "essex" => Ok(Self::Essex), - "falkirk" => Ok(Self::Falkirk), - "fermanagh and omagh" => Ok(Self::FermanaghAndOmagh), - "fife" => Ok(Self::Fife), - "flintshire" => Ok(Self::Flintshire), - "gateshead" => Ok(Self::Gateshead), - "glasgow city" => Ok(Self::GlasgowCity), - "gloucestershire" => Ok(Self::Gloucestershire), - "greenwich" => Ok(Self::Greenwich), - "gwynedd" => Ok(Self::Gwynedd), - "hackney" => Ok(Self::Hackney), - "halton" => Ok(Self::Halton), - "hammersmith and fulham" => Ok(Self::HammersmithAndFulham), - "hampshire" => Ok(Self::Hampshire), - "haringey" => Ok(Self::Haringey), - "harrow" => Ok(Self::Harrow), - "hartlepool" => Ok(Self::Hartlepool), - "havering" => Ok(Self::Havering), - "herefordshire" => Ok(Self::Herefordshire), - "hertfordshire" => Ok(Self::Hertfordshire), - "highland" => Ok(Self::Highland), - "hillingdon" => Ok(Self::Hillingdon), - "hounslow" => Ok(Self::Hounslow), - "inverclyde" => Ok(Self::Inverclyde), - "isle of anglesey" => Ok(Self::IsleOfAnglesey), - "isle of wight" => Ok(Self::IsleOfWight), - "isles of scilly" => Ok(Self::IslesOfScilly), - "islington" => Ok(Self::Islington), - "kensington and chelsea" => Ok(Self::KensingtonAndChelsea), - "kent" => Ok(Self::Kent), - "kingston upon hull" => Ok(Self::KingstonUponHull), - "kingston upon thames" => Ok(Self::KingstonUponThames), - "kirklees" => Ok(Self::Kirklees), - "knowsley" => Ok(Self::Knowsley), - "lambeth" => Ok(Self::Lambeth), - "lancashire" => Ok(Self::Lancashire), - "leeds" => Ok(Self::Leeds), - "leicester" => Ok(Self::Leicester), - "leicestershire" => Ok(Self::Leicestershire), - "lewisham" => Ok(Self::Lewisham), - "lincolnshire" => Ok(Self::Lincolnshire), - "lisburn and castlereagh" => Ok(Self::LisburnAndCastlereagh), - "liverpool" => Ok(Self::Liverpool), - "london city of" => Ok(Self::LondonCityOf), - "luton" => Ok(Self::Luton), - "manchester" => Ok(Self::Manchester), - "medway" => Ok(Self::Medway), - "merthyr tydfil" => Ok(Self::MerthyrTydfil), - "merton" => Ok(Self::Merton), - "mid and east antrim" => Ok(Self::MidAndEastAntrim), - "mid ulster" => Ok(Self::MidUlster), - "middlesbrough" => Ok(Self::Middlesbrough), - "midlothian" => Ok(Self::Midlothian), - "milton keynes" => Ok(Self::MiltonKeynes), - "monmouthshire" => Ok(Self::Monmouthshire), - "moray" => Ok(Self::Moray), - "neath port talbot" => Ok(Self::NeathPortTalbot), - "newcastle upon tyne" => Ok(Self::NewcastleUponTyne), - "newham" => Ok(Self::Newham), - "newport" => Ok(Self::Newport), - "newry mourne and down" => Ok(Self::NewryMourneAndDown), - "norfolk" => Ok(Self::Norfolk), - "north ayrshire" => Ok(Self::NorthAyrshire), - "north east lincolnshire" => Ok(Self::NorthEastLincolnshire), - "north lanarkshire" => Ok(Self::NorthLanarkshire), - "north lincolnshire" => Ok(Self::NorthLincolnshire), - "north somerset" => Ok(Self::NorthSomerset), - "north tyneside" => Ok(Self::NorthTyneside), - "north yorkshire" => Ok(Self::NorthYorkshire), - "northamptonshire" => Ok(Self::Northamptonshire), - "northumberland" => Ok(Self::Northumberland), - "nottingham" => Ok(Self::Nottingham), - "nottinghamshire" => Ok(Self::Nottinghamshire), - "oldham" => Ok(Self::Oldham), - "orkney islands" => Ok(Self::OrkneyIslands), - "oxfordshire" => Ok(Self::Oxfordshire), - "pembrokeshire" => Ok(Self::Pembrokeshire), - "perth and kinross" => Ok(Self::PerthAndKinross), - "peterborough" => Ok(Self::Peterborough), - "plymouth" => Ok(Self::Plymouth), - "portsmouth" => Ok(Self::Portsmouth), - "powys" => Ok(Self::Powys), - "reading" => Ok(Self::Reading), - "redbridge" => Ok(Self::Redbridge), - "redcar and cleveland" => Ok(Self::RedcarAndCleveland), - "renfrewshire" => Ok(Self::Renfrewshire), - "rhondda cynon taff" => Ok(Self::RhonddaCynonTaff), - "richmond upon thames" => Ok(Self::RichmondUponThames), - "rochdale" => Ok(Self::Rochdale), - "rotherham" => Ok(Self::Rotherham), - "rutland" => Ok(Self::Rutland), - "salford" => Ok(Self::Salford), - "sandwell" => Ok(Self::Sandwell), - "scottish borders" => Ok(Self::ScottishBorders), - "sefton" => Ok(Self::Sefton), - "sheffield" => Ok(Self::Sheffield), - "shetland islands" => Ok(Self::ShetlandIslands), - "shropshire" => Ok(Self::Shropshire), - "slough" => Ok(Self::Slough), - "solihull" => Ok(Self::Solihull), - "somerset" => Ok(Self::Somerset), - "south ayrshire" => Ok(Self::SouthAyrshire), - "south gloucestershire" => Ok(Self::SouthGloucestershire), - "south lanarkshire" => Ok(Self::SouthLanarkshire), - "south tyneside" => Ok(Self::SouthTyneside), - "southampton" => Ok(Self::Southampton), - "southend on sea" => Ok(Self::SouthendOnSea), - "southwark" => Ok(Self::Southwark), - "st helens" => Ok(Self::StHelens), - "staffordshire" => Ok(Self::Staffordshire), - "stirling" => Ok(Self::Stirling), - "stockport" => Ok(Self::Stockport), - "stockton on tees" => Ok(Self::StocktonOnTees), - "stoke on trent" => Ok(Self::StokeOnTrent), - "suffolk" => Ok(Self::Suffolk), - "sunderland" => Ok(Self::Sunderland), - "surrey" => Ok(Self::Surrey), - "sutton" => Ok(Self::Sutton), - "swansea" => Ok(Self::Swansea), - "swindon" => Ok(Self::Swindon), - "tameside" => Ok(Self::Tameside), - "telford and wrekin" => Ok(Self::TelfordAndWrekin), - "thurrock" => Ok(Self::Thurrock), - "torbay" => Ok(Self::Torbay), - "torfaen" => Ok(Self::Torfaen), - "tower hamlets" => Ok(Self::TowerHamlets), - "trafford" => Ok(Self::Trafford), - "vale of glamorgan" => Ok(Self::ValeOfGlamorgan), - "wakefield" => Ok(Self::Wakefield), - "walsall" => Ok(Self::Walsall), - "waltham forest" => Ok(Self::WalthamForest), - "wandsworth" => Ok(Self::Wandsworth), - "warrington" => Ok(Self::Warrington), - "warwickshire" => Ok(Self::Warwickshire), - "west berkshire" => Ok(Self::WestBerkshire), - "west dunbartonshire" => Ok(Self::WestDunbartonshire), - "west lothian" => Ok(Self::WestLothian), - "west sussex" => Ok(Self::WestSussex), - "westminster" => Ok(Self::Westminster), - "wigan" => Ok(Self::Wigan), - "wiltshire" => Ok(Self::Wiltshire), - "windsor and maidenhead" => Ok(Self::WindsorAndMaidenhead), - "wirral" => Ok(Self::Wirral), - "wokingham" => Ok(Self::Wokingham), - "wolverhampton" => Ok(Self::Wolverhampton), - "worcestershire" => Ok(Self::Worcestershire), - "wrexham" => Ok(Self::Wrexham), - "york" => Ok(Self::York), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aberdeen" => Ok(Self::Aberdeen), + "Aberdeenshire" => Ok(Self::Aberdeenshire), + "Angus" => Ok(Self::Angus), + "Antrim" => Ok(Self::Antrim), + "Antrim and Newtownabbey" => Ok(Self::AntrimAndNewtownabbey), + "Ards" => Ok(Self::Ards), + "Ards and North Down" => Ok(Self::ArdsAndNorthDown), + "Argyll and Bute" => Ok(Self::ArgyllAndBute), + "Armagh City and District Council" => Ok(Self::ArmaghCityAndDistrictCouncil), + "Armagh, Banbridge and Craigavon" => Ok(Self::ArmaghBanbridgeAndCraigavon), + "Ascension Island" => Ok(Self::AscensionIsland), + "Ballymena Borough" => Ok(Self::BallymenaBorough), + "Ballymoney" => Ok(Self::Ballymoney), + "Banbridge" => Ok(Self::Banbridge), + "Barnsley" => Ok(Self::Barnsley), + "Bath and North East Somerset" => Ok(Self::BathAndNorthEastSomerset), + "Bedford" => Ok(Self::Bedford), + "Belfast district" => Ok(Self::BelfastDistrict), + "Birmingham" => Ok(Self::Birmingham), + "Blackburn with Darwen" => Ok(Self::BlackburnWithDarwen), + "Blackpool" => Ok(Self::Blackpool), + "Blaenau Gwent County Borough" => Ok(Self::BlaenauGwentCountyBorough), + "Bolton" => Ok(Self::Bolton), + "Bournemouth" => Ok(Self::Bournemouth), + "Bracknell Forest" => Ok(Self::BracknellForest), + "Bradford" => Ok(Self::Bradford), + "Bridgend County Borough" => Ok(Self::BridgendCountyBorough), + "Brighton and Hove" => Ok(Self::BrightonAndHove), + "Buckinghamshire" => Ok(Self::Buckinghamshire), + "Bury" => Ok(Self::Bury), + "Caerphilly County Borough" => Ok(Self::CaerphillyCountyBorough), + "Calderdale" => Ok(Self::Calderdale), + "Cambridgeshire" => Ok(Self::Cambridgeshire), + "Carmarthenshire" => Ok(Self::Carmarthenshire), + "Carrickfergus Borough Council" => Ok(Self::CarrickfergusBoroughCouncil), + "Castlereagh" => Ok(Self::Castlereagh), + "Causeway Coast and Glens" => Ok(Self::CausewayCoastAndGlens), + "Central Bedfordshire" => Ok(Self::CentralBedfordshire), + "Ceredigion" => Ok(Self::Ceredigion), + "Cheshire East" => Ok(Self::CheshireEast), + "Cheshire West and Chester" => Ok(Self::CheshireWestAndChester), + "City and County of Cardiff" => Ok(Self::CityAndCountyOfCardiff), + "City and County of Swansea" => Ok(Self::CityAndCountyOfSwansea), + "City of Bristol" => Ok(Self::CityOfBristol), + "City of Derby" => Ok(Self::CityOfDerby), + "City of Kingston upon Hull" => Ok(Self::CityOfKingstonUponHull), + "City of Leicester" => Ok(Self::CityOfLeicester), + "City of London" => Ok(Self::CityOfLondon), + "City of Nottingham" => Ok(Self::CityOfNottingham), + "City of Peterborough" => Ok(Self::CityOfPeterborough), + "City of Plymouth" => Ok(Self::CityOfPlymouth), + "City of Portsmouth" => Ok(Self::CityOfPortsmouth), + "City of Southampton" => Ok(Self::CityOfSouthampton), + "City of Stoke-on-Trent" => Ok(Self::CityOfStokeOnTrent), + "City of Sunderland" => Ok(Self::CityOfSunderland), + "City of Westminster" => Ok(Self::CityOfWestminster), + "City of Wolverhampton" => Ok(Self::CityOfWolverhampton), + "City of York" => Ok(Self::CityOfYork), + "Clackmannanshire" => Ok(Self::Clackmannanshire), + "Coleraine Borough Council" => Ok(Self::ColeraineBoroughCouncil), + "Conwy County Borough" => Ok(Self::ConwyCountyBorough), + "Cookstown District Council" => Ok(Self::CookstownDistrictCouncil), + "Cornwall" => Ok(Self::Cornwall), + "County Durham" => Ok(Self::CountyDurham), + "Coventry" => Ok(Self::Coventry), + "Craigavon Borough Council" => Ok(Self::CraigavonBoroughCouncil), + "Cumbria" => Ok(Self::Cumbria), + "Darlington" => Ok(Self::Darlington), + "Denbighshire" => Ok(Self::Denbighshire), + "Derbyshire" => Ok(Self::Derbyshire), + "Derry City and Strabane" => Ok(Self::DerryCityAndStrabane), + "Derry City Council" => Ok(Self::DerryCityCouncil), + "Devon" => Ok(Self::Devon), + "Doncaster" => Ok(Self::Doncaster), + "Dorset" => Ok(Self::Dorset), + "Down District Council" => Ok(Self::DownDistrictCouncil), + "Dudley" => Ok(Self::Dudley), + "Dumfries and Galloway" => Ok(Self::DumfriesAndGalloway), + "Dundee" => Ok(Self::Dundee), + "Dungannon and South Tyrone Borough Council" => { + Ok(Self::DungannonAndSouthTyroneBoroughCouncil) } - } + "East Ayrshire" => Ok(Self::EastAyrshire), + "East Dunbartonshire" => Ok(Self::EastDunbartonshire), + "East Lothian" => Ok(Self::EastLothian), + "East Renfrewshire" => Ok(Self::EastRenfrewshire), + "East Riding of Yorkshire" => Ok(Self::EastRidingOfYorkshire), + "East Sussex" => Ok(Self::EastSussex), + "Edinburgh" => Ok(Self::Edinburgh), + "England" => Ok(Self::England), + "Essex" => Ok(Self::Essex), + "Falkirk" => Ok(Self::Falkirk), + "Fermanagh and Omagh" => Ok(Self::FermanaghAndOmagh), + "Fermanagh District Council" => Ok(Self::FermanaghDistrictCouncil), + "Fife" => Ok(Self::Fife), + "Flintshire" => Ok(Self::Flintshire), + "Gateshead" => Ok(Self::Gateshead), + "Glasgow" => Ok(Self::Glasgow), + "Gloucestershire" => Ok(Self::Gloucestershire), + "Gwynedd" => Ok(Self::Gwynedd), + "Halton" => Ok(Self::Halton), + "Hampshire" => Ok(Self::Hampshire), + "Hartlepool" => Ok(Self::Hartlepool), + "Herefordshire" => Ok(Self::Herefordshire), + "Hertfordshire" => Ok(Self::Hertfordshire), + "Highland" => Ok(Self::Highland), + "Inverclyde" => Ok(Self::Inverclyde), + "Isle of Wight" => Ok(Self::IsleOfWight), + "Isles of Scilly" => Ok(Self::IslesOfScilly), + "Kent" => Ok(Self::Kent), + "Kirklees" => Ok(Self::Kirklees), + "Knowsley" => Ok(Self::Knowsley), + "Lancashire" => Ok(Self::Lancashire), + "Larne Borough Council" => Ok(Self::LarneBoroughCouncil), + "Leeds" => Ok(Self::Leeds), + "Leicestershire" => Ok(Self::Leicestershire), + "Limavady Borough Council" => Ok(Self::LimavadyBoroughCouncil), + "Lincolnshire" => Ok(Self::Lincolnshire), + "Lisburn and Castlereagh" => Ok(Self::LisburnAndCastlereagh), + "Lisburn City Council" => Ok(Self::LisburnCityCouncil), + "Liverpool" => Ok(Self::Liverpool), + "London Borough of Barking and Dagenham" => { + Ok(Self::LondonBoroughOfBarkingAndDagenham) + } + "London Borough of Barnet" => Ok(Self::LondonBoroughOfBarnet), + "London Borough of Bexley" => Ok(Self::LondonBoroughOfBexley), + "London Borough of Brent" => Ok(Self::LondonBoroughOfBrent), + "London Borough of Bromley" => Ok(Self::LondonBoroughOfBromley), + "London Borough of Camden" => Ok(Self::LondonBoroughOfCamden), + "London Borough of Croydon" => Ok(Self::LondonBoroughOfCroydon), + "London Borough of Ealing" => Ok(Self::LondonBoroughOfEaling), + "London Borough of Enfield" => Ok(Self::LondonBoroughOfEnfield), + "London Borough of Hackney" => Ok(Self::LondonBoroughOfHackney), + "London Borough of Hammersmith and Fulham" => { + Ok(Self::LondonBoroughOfHammersmithAndFulham) + } + "London Borough of Haringey" => Ok(Self::LondonBoroughOfHaringey), + "London Borough of Harrow" => Ok(Self::LondonBoroughOfHarrow), + "London Borough of Havering" => Ok(Self::LondonBoroughOfHavering), + "London Borough of Hillingdon" => Ok(Self::LondonBoroughOfHillingdon), + "London Borough of Hounslow" => Ok(Self::LondonBoroughOfHounslow), + "London Borough of Islington" => Ok(Self::LondonBoroughOfIslington), + "London Borough of Lambeth" => Ok(Self::LondonBoroughOfLambeth), + "London Borough of Lewisham" => Ok(Self::LondonBoroughOfLewisham), + "London Borough of Merton" => Ok(Self::LondonBoroughOfMerton), + "London Borough of Newham" => Ok(Self::LondonBoroughOfNewham), + "London Borough of Redbridge" => Ok(Self::LondonBoroughOfRedbridge), + "London Borough of Richmond upon Thames" => { + Ok(Self::LondonBoroughOfRichmondUponThames) + } + "London Borough of Southwark" => Ok(Self::LondonBoroughOfSouthwark), + "London Borough of Sutton" => Ok(Self::LondonBoroughOfSutton), + "London Borough of Tower Hamlets" => Ok(Self::LondonBoroughOfTowerHamlets), + "London Borough of Waltham Forest" => Ok(Self::LondonBoroughOfWalthamForest), + "London Borough of Wandsworth" => Ok(Self::LondonBoroughOfWandsworth), + "Magherafelt District Council" => Ok(Self::MagherafeltDistrictCouncil), + "Manchester" => Ok(Self::Manchester), + "Medway" => Ok(Self::Medway), + "Merthyr Tydfil County Borough" => Ok(Self::MerthyrTydfilCountyBorough), + "Metropolitan Borough of Wigan" => Ok(Self::MetropolitanBoroughOfWigan), + "Mid and East Antrim" => Ok(Self::MidAndEastAntrim), + "Mid Ulster" => Ok(Self::MidUlster), + "Middlesbrough" => Ok(Self::Middlesbrough), + "Midlothian" => Ok(Self::Midlothian), + "Milton Keynes" => Ok(Self::MiltonKeynes), + "Monmouthshire" => Ok(Self::Monmouthshire), + "Moray" => Ok(Self::Moray), + "Moyle District Council" => Ok(Self::MoyleDistrictCouncil), + "Neath Port Talbot County Borough" => Ok(Self::NeathPortTalbotCountyBorough), + "Newcastle upon Tyne" => Ok(Self::NewcastleUponTyne), + "Newport" => Ok(Self::Newport), + "Newry and Mourne District Council" => Ok(Self::NewryAndMourneDistrictCouncil), + "Newry, Mourne and Down" => Ok(Self::NewryMourneAndDown), + "Newtownabbey Borough Council" => Ok(Self::NewtownabbeyBoroughCouncil), + "Norfolk" => Ok(Self::Norfolk), + "North Ayrshire" => Ok(Self::NorthAyrshire), + "North Down Borough Council" => Ok(Self::NorthDownBoroughCouncil), + "North East Lincolnshire" => Ok(Self::NorthEastLincolnshire), + "North Lanarkshire" => Ok(Self::NorthLanarkshire), + "North Lincolnshire" => Ok(Self::NorthLincolnshire), + "North Somerset" => Ok(Self::NorthSomerset), + "North Tyneside" => Ok(Self::NorthTyneside), + "North Yorkshire" => Ok(Self::NorthYorkshire), + "Northamptonshire" => Ok(Self::Northamptonshire), + "Northern Ireland" => Ok(Self::NorthernIreland), + "Northumberland" => Ok(Self::Northumberland), + "Nottinghamshire" => Ok(Self::Nottinghamshire), + "Oldham" => Ok(Self::Oldham), + "Omagh District Council" => Ok(Self::OmaghDistrictCouncil), + "Orkney Islands" => Ok(Self::OrkneyIslands), + "Outer Hebrides" => Ok(Self::OuterHebrides), + "Oxfordshire" => Ok(Self::Oxfordshire), + "Pembrokeshire" => Ok(Self::Pembrokeshire), + "Perth and Kinross" => Ok(Self::PerthAndKinross), + "Poole" => Ok(Self::Poole), + "Powys" => Ok(Self::Powys), + "Reading" => Ok(Self::Reading), + "Redcar and Cleveland" => Ok(Self::RedcarAndCleveland), + "Renfrewshire" => Ok(Self::Renfrewshire), + "Rhondda Cynon Taf" => Ok(Self::RhonddaCynonTaf), + "Rochdale" => Ok(Self::Rochdale), + "Rotherham" => Ok(Self::Rotherham), + "Royal Borough of Greenwich" => Ok(Self::RoyalBoroughOfGreenwich), + "Royal Borough of Kensington and Chelsea" => { + Ok(Self::RoyalBoroughOfKensingtonAndChelsea) + } + "Royal Borough of Kingston upon Thames" => { + Ok(Self::RoyalBoroughOfKingstonUponThames) + } + "Rutland" => Ok(Self::Rutland), + "Saint Helena" => Ok(Self::SaintHelena), + "Salford" => Ok(Self::Salford), + "Sandwell" => Ok(Self::Sandwell), + "Scotland" => Ok(Self::Scotland), + "Scottish Borders" => Ok(Self::ScottishBorders), + "Sefton" => Ok(Self::Sefton), + "Sheffield" => Ok(Self::Sheffield), + "Shetland Islands" => Ok(Self::ShetlandIslands), + "Shropshire" => Ok(Self::Shropshire), + "Slough" => Ok(Self::Slough), + "Solihull" => Ok(Self::Solihull), + "Somerset" => Ok(Self::Somerset), + "South Ayrshire" => Ok(Self::SouthAyrshire), + "South Gloucestershire" => Ok(Self::SouthGloucestershire), + "South Lanarkshire" => Ok(Self::SouthLanarkshire), + "South Tyneside" => Ok(Self::SouthTyneside), + "Southend-on-Sea" => Ok(Self::SouthendOnSea), + "St Helens" => Ok(Self::StHelens), + "Staffordshire" => Ok(Self::Staffordshire), + "Stirling" => Ok(Self::Stirling), + "Stockport" => Ok(Self::Stockport), + "Stockton-on-Tees" => Ok(Self::StocktonOnTees), + "Strabane District Council" => Ok(Self::StrabaneDistrictCouncil), + "Suffolk" => Ok(Self::Suffolk), + "Surrey" => Ok(Self::Surrey), + "Swindon" => Ok(Self::Swindon), + "Tameside" => Ok(Self::Tameside), + "Telford and Wrekin" => Ok(Self::TelfordAndWrekin), + "Thurrock" => Ok(Self::Thurrock), + "Torbay" => Ok(Self::Torbay), + "Torfaen" => Ok(Self::Torfaen), + "Trafford" => Ok(Self::Trafford), + "United Kingdom" => Ok(Self::UnitedKingdom), + "Vale of Glamorgan" => Ok(Self::ValeOfGlamorgan), + "Wakefield" => Ok(Self::Wakefield), + "Wales" => Ok(Self::Wales), + "Walsall" => Ok(Self::Walsall), + "Warrington" => Ok(Self::Warrington), + "Warwickshire" => Ok(Self::Warwickshire), + "West Berkshire" => Ok(Self::WestBerkshire), + "West Dunbartonshire" => Ok(Self::WestDunbartonshire), + "West Lothian" => Ok(Self::WestLothian), + "West Sussex" => Ok(Self::WestSussex), + "Wiltshire" => Ok(Self::Wiltshire), + "Windsor and Maidenhead" => Ok(Self::WindsorAndMaidenhead), + "Wirral" => Ok(Self::Wirral), + "Wokingham" => Ok(Self::Wokingham), + "Worcestershire" => Ok(Self::Worcestershire), + "Wrexham County Borough" => Ok(Self::WrexhamCountyBorough), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for BelgiumStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BelgiumStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Antwerp" => Ok(Self::Antwerp), + "Brussels-Capital Region" => Ok(Self::BrusselsCapitalRegion), + "East Flanders" => Ok(Self::EastFlanders), + "Flanders" => Ok(Self::Flanders), + "Flemish Brabant" => Ok(Self::FlemishBrabant), + "Hainaut" => Ok(Self::Hainaut), + "Limburg" => Ok(Self::Limburg), + "Liège" => Ok(Self::Liege), + "Luxembourg" => Ok(Self::Luxembourg), + "Namur" => Ok(Self::Namur), + "Wallonia" => Ok(Self::Wallonia), + "Walloon Brabant" => Ok(Self::WalloonBrabant), + "West Flanders" => Ok(Self::WestFlanders), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for LuxembourgStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LuxembourgStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Canton of Capellen" => Ok(Self::CantonOfCapellen), + "Canton of Clervaux" => Ok(Self::CantonOfClervaux), + "Canton of Diekirch" => Ok(Self::CantonOfDiekirch), + "Canton of Echternach" => Ok(Self::CantonOfEchternach), + "Canton of Esch-sur-Alzette" => Ok(Self::CantonOfEschSurAlzette), + "Canton of Grevenmacher" => Ok(Self::CantonOfGrevenmacher), + "Canton of Luxembourg" => Ok(Self::CantonOfLuxembourg), + "Canton of Mersch" => Ok(Self::CantonOfMersch), + "Canton of Redange" => Ok(Self::CantonOfRedange), + "Canton of Remich" => Ok(Self::CantonOfRemich), + "Canton of Vianden" => Ok(Self::CantonOfVianden), + "Canton of Wiltz" => Ok(Self::CantonOfWiltz), + "Diekirch District" => Ok(Self::DiekirchDistrict), + "Grevenmacher District" => Ok(Self::GrevenmacherDistrict), + "Luxembourg District" => Ok(Self::LuxembourgDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for RussiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "RussiaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Altai Krai" => Ok(Self::AltaiKrai), + "Altai Republic" => Ok(Self::AltaiRepublic), + "Amur Oblast" => Ok(Self::AmurOblast), + "Arkhangelsk" => Ok(Self::Arkhangelsk), + "Astrakhan Oblast" => Ok(Self::AstrakhanOblast), + "Belgorod Oblast" => Ok(Self::BelgorodOblast), + "Bryansk Oblast" => Ok(Self::BryanskOblast), + "Chechen Republic" => Ok(Self::ChechenRepublic), + "Chelyabinsk Oblast" => Ok(Self::ChelyabinskOblast), + "Chukotka Autonomous Okrug" => Ok(Self::ChukotkaAutonomousOkrug), + "Chuvash Republic" => Ok(Self::ChuvashRepublic), + "Irkutsk" => Ok(Self::Irkutsk), + "Ivanovo Oblast" => Ok(Self::IvanovoOblast), + "Jewish Autonomous Oblast" => Ok(Self::JewishAutonomousOblast), + "Kabardino-Balkar Republic" => Ok(Self::KabardinoBalkarRepublic), + "Kaliningrad" => Ok(Self::Kaliningrad), + "Kaluga Oblast" => Ok(Self::KalugaOblast), + "Kamchatka Krai" => Ok(Self::KamchatkaKrai), + "Karachay-Cherkess Republic" => Ok(Self::KarachayCherkessRepublic), + "Kemerovo Oblast" => Ok(Self::KemerovoOblast), + "Khabarovsk Krai" => Ok(Self::KhabarovskKrai), + "Khanty-Mansi Autonomous Okrug" => Ok(Self::KhantyMansiAutonomousOkrug), + "Kirov Oblast" => Ok(Self::KirovOblast), + "Komi Republic" => Ok(Self::KomiRepublic), + "Kostroma Oblast" => Ok(Self::KostromaOblast), + "Krasnodar Krai" => Ok(Self::KrasnodarKrai), + "Krasnoyarsk Krai" => Ok(Self::KrasnoyarskKrai), + "Kurgan Oblast" => Ok(Self::KurganOblast), + "Kursk Oblast" => Ok(Self::KurskOblast), + "Leningrad Oblast" => Ok(Self::LeningradOblast), + "Lipetsk Oblast" => Ok(Self::LipetskOblast), + "Magadan Oblast" => Ok(Self::MagadanOblast), + "Mari El Republic" => Ok(Self::MariElRepublic), + "Moscow" => Ok(Self::Moscow), + "Moscow Oblast" => Ok(Self::MoscowOblast), + "Murmansk Oblast" => Ok(Self::MurmanskOblast), + "Nenets Autonomous Okrug" => Ok(Self::NenetsAutonomousOkrug), + "Nizhny Novgorod Oblast" => Ok(Self::NizhnyNovgorodOblast), + "Novgorod Oblast" => Ok(Self::NovgorodOblast), + "Novosibirsk" => Ok(Self::Novosibirsk), + "Omsk Oblast" => Ok(Self::OmskOblast), + "Orenburg Oblast" => Ok(Self::OrenburgOblast), + "Oryol Oblast" => Ok(Self::OryolOblast), + "Penza Oblast" => Ok(Self::PenzaOblast), + "Perm Krai" => Ok(Self::PermKrai), + "Primorsky Krai" => Ok(Self::PrimorskyKrai), + "Pskov Oblast" => Ok(Self::PskovOblast), + "Republic of Adygea" => Ok(Self::RepublicOfAdygea), + "Republic of Bashkortostan" => Ok(Self::RepublicOfBashkortostan), + "Republic of Buryatia" => Ok(Self::RepublicOfBuryatia), + "Republic of Dagestan" => Ok(Self::RepublicOfDagestan), + "Republic of Ingushetia" => Ok(Self::RepublicOfIngushetia), + "Republic of Kalmykia" => Ok(Self::RepublicOfKalmykia), + "Republic of Karelia" => Ok(Self::RepublicOfKarelia), + "Republic of Khakassia" => Ok(Self::RepublicOfKhakassia), + "Republic of Mordovia" => Ok(Self::RepublicOfMordovia), + "Republic of North Ossetia-Alania" => Ok(Self::RepublicOfNorthOssetiaAlania), + "Republic of Tatarstan" => Ok(Self::RepublicOfTatarstan), + "Rostov Oblast" => Ok(Self::RostovOblast), + "Ryazan Oblast" => Ok(Self::RyazanOblast), + "Saint Petersburg" => Ok(Self::SaintPetersburg), + "Sakha Republic" => Ok(Self::SakhaRepublic), + "Sakhalin" => Ok(Self::Sakhalin), + "Samara Oblast" => Ok(Self::SamaraOblast), + "Saratov Oblast" => Ok(Self::SaratovOblast), + "Sevastopol" => Ok(Self::Sevastopol), + "Smolensk Oblast" => Ok(Self::SmolenskOblast), + "Stavropol Krai" => Ok(Self::StavropolKrai), + "Sverdlovsk" => Ok(Self::Sverdlovsk), + "Tambov Oblast" => Ok(Self::TambovOblast), + "Tomsk Oblast" => Ok(Self::TomskOblast), + "Tula Oblast" => Ok(Self::TulaOblast), + "Tuva Republic" => Ok(Self::TuvaRepublic), + "Tver Oblast" => Ok(Self::TverOblast), + "Tyumen Oblast" => Ok(Self::TyumenOblast), + "Udmurt Republic" => Ok(Self::UdmurtRepublic), + "Ulyanovsk Oblast" => Ok(Self::UlyanovskOblast), + "Vladimir Oblast" => Ok(Self::VladimirOblast), + "Vologda Oblast" => Ok(Self::VologdaOblast), + "Voronezh Oblast" => Ok(Self::VoronezhOblast), + "Yamalo-Nenets Autonomous Okrug" => Ok(Self::YamaloNenetsAutonomousOkrug), + "Yaroslavl Oblast" => Ok(Self::YaroslavlOblast), + "Zabaykalsky Krai" => Ok(Self::ZabaykalskyKrai), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for SanMarinoStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SanMarinoStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Acquaviva" => Ok(Self::Acquaviva), + "Borgo Maggiore" => Ok(Self::BorgoMaggiore), + "Chiesanuova" => Ok(Self::Chiesanuova), + "Domagnano" => Ok(Self::Domagnano), + "Faetano" => Ok(Self::Faetano), + "Fiorentino" => Ok(Self::Fiorentino), + "Montegiardino" => Ok(Self::Montegiardino), + "San Marino" => Ok(Self::SanMarino), + "Serravalle" => Ok(Self::Serravalle), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for SerbiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SerbiaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Belgrade" => Ok(Self::Belgrade), + "Bor District" => Ok(Self::BorDistrict), + "Braničevo District" => Ok(Self::BraničevoDistrict), + "Central Banat District" => Ok(Self::CentralBanatDistrict), + "Jablanica District" => Ok(Self::JablanicaDistrict), + "Kolubara District" => Ok(Self::KolubaraDistrict), + "Mačva District" => Ok(Self::MačvaDistrict), + "Moravica District" => Ok(Self::MoravicaDistrict), + "Nišava District" => Ok(Self::NišavaDistrict), + "North Banat District" => Ok(Self::NorthBanatDistrict), + "North Bačka District" => Ok(Self::NorthBačkaDistrict), + "Pirot District" => Ok(Self::PirotDistrict), + "Podunavlje District" => Ok(Self::PodunavljeDistrict), + "Pomoravlje District" => Ok(Self::PomoravljeDistrict), + "Pčinja District" => Ok(Self::PčinjaDistrict), + "Rasina District" => Ok(Self::RasinaDistrict), + "Raška District" => Ok(Self::RaškaDistrict), + "South Banat District" => Ok(Self::SouthBanatDistrict), + "South Bačka District" => Ok(Self::SouthBačkaDistrict), + "Srem District" => Ok(Self::SremDistrict), + "Toplica District" => Ok(Self::ToplicaDistrict), + "Vojvodina" => Ok(Self::Vojvodina), + "West Bačka District" => Ok(Self::WestBačkaDistrict), + "Zaječar District" => Ok(Self::ZaječarDistrict), + "Zlatibor District" => Ok(Self::ZlatiborDistrict), + "Šumadija District" => Ok(Self::ŠumadijaDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for SlovakiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SlovakiaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Banská Bystrica Region" => Ok(Self::BanskaBystricaRegion), + "Bratislava Region" => Ok(Self::BratislavaRegion), + "Košice Region" => Ok(Self::KosiceRegion), + "Nitra Region" => Ok(Self::NitraRegion), + "Prešov Region" => Ok(Self::PresovRegion), + "Trenčín Region" => Ok(Self::TrencinRegion), + "Trnava Region" => Ok(Self::TrnavaRegion), + "Žilina Region" => Ok(Self::ZilinaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for SwedenStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SwedenStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Blekinge" => Ok(Self::Blekinge), + "Dalarna County" => Ok(Self::DalarnaCounty), + "Gotland County" => Ok(Self::GotlandCounty), + "Gävleborg County" => Ok(Self::GävleborgCounty), + "Halland County" => Ok(Self::HallandCounty), + "Jönköping County" => Ok(Self::JönköpingCounty), + "Kalmar County" => Ok(Self::KalmarCounty), + "Kronoberg County" => Ok(Self::KronobergCounty), + "Norrbotten County" => Ok(Self::NorrbottenCounty), + "Skåne County" => Ok(Self::SkåneCounty), + "Stockholm County" => Ok(Self::StockholmCounty), + "Södermanland County" => Ok(Self::SödermanlandCounty), + "Uppsala County" => Ok(Self::UppsalaCounty), + "Värmland County" => Ok(Self::VärmlandCounty), + "Västerbotten County" => Ok(Self::VästerbottenCounty), + "Västernorrland County" => Ok(Self::VästernorrlandCounty), + "Västmanland County" => Ok(Self::VästmanlandCounty), + "Västra Götaland County" => Ok(Self::VästraGötalandCounty), + "Örebro County" => Ok(Self::ÖrebroCounty), + "Östergötland County" => Ok(Self::ÖstergötlandCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for SloveniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SloveniaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Ajdovščina Municipality" => Ok(Self::Ajdovščina), + "Ankaran Municipality" => Ok(Self::Ankaran), + "Beltinci Municipality" => Ok(Self::Beltinci), + "Benedikt Municipality" => Ok(Self::Benedikt), + "Bistrica ob Sotli Municipality" => Ok(Self::BistricaObSotli), + "Bled Municipality" => Ok(Self::Bled), + "Bloke Municipality" => Ok(Self::Bloke), + "Bohinj Municipality" => Ok(Self::Bohinj), + "Borovnica Municipality" => Ok(Self::Borovnica), + "Bovec Municipality" => Ok(Self::Bovec), + "Braslovče Municipality" => Ok(Self::Braslovče), + "Brda Municipality" => Ok(Self::Brda), + "Brezovica Municipality" => Ok(Self::Brezovica), + "Brežice Municipality" => Ok(Self::Brežice), + "Cankova Municipality" => Ok(Self::Cankova), + "Cerklje na Gorenjskem Municipality" => Ok(Self::CerkljeNaGorenjskem), + "Cerknica Municipality" => Ok(Self::Cerknica), + "Cerkno Municipality" => Ok(Self::Cerkno), + "Cerkvenjak Municipality" => Ok(Self::Cerkvenjak), + "City Municipality of Celje" => Ok(Self::CityMunicipalityOfCelje), + "City Municipality of Novo Mesto" => Ok(Self::CityMunicipalityOfNovoMesto), + "Destrnik Municipality" => Ok(Self::Destrnik), + "Divača Municipality" => Ok(Self::Divača), + "Dobje Municipality" => Ok(Self::Dobje), + "Dobrepolje Municipality" => Ok(Self::Dobrepolje), + "Dobrna Municipality" => Ok(Self::Dobrna), + "Dobrova–Polhov Gradec Municipality" => Ok(Self::DobrovaPolhovGradec), + "Dobrovnik Municipality" => Ok(Self::Dobrovnik), + "Dol pri Ljubljani Municipality" => Ok(Self::DolPriLjubljani), + "Dolenjske Toplice Municipality" => Ok(Self::DolenjskeToplice), + "Domžale Municipality" => Ok(Self::Domžale), + "Dornava Municipality" => Ok(Self::Dornava), + "Dravograd Municipality" => Ok(Self::Dravograd), + "Duplek Municipality" => Ok(Self::Duplek), + "Gorenja Vas–Poljane Municipality" => Ok(Self::GorenjaVasPoljane), + "Gorišnica Municipality" => Ok(Self::Gorišnica), + "Gorje Municipality" => Ok(Self::Gorje), + "Gornja Radgona Municipality" => Ok(Self::GornjaRadgona), + "Gornji Grad Municipality" => Ok(Self::GornjiGrad), + "Gornji Petrovci Municipality" => Ok(Self::GornjiPetrovci), + "Grad Municipality" => Ok(Self::Grad), + "Grosuplje Municipality" => Ok(Self::Grosuplje), + "Hajdina Municipality" => Ok(Self::Hajdina), + "Hodoš Municipality" => Ok(Self::Hodoš), + "Horjul Municipality" => Ok(Self::Horjul), + "Hoče–Slivnica Municipality" => Ok(Self::HočeSlivnica), + "Hrastnik Municipality" => Ok(Self::Hrastnik), + "Hrpelje–Kozina Municipality" => Ok(Self::HrpeljeKozina), + "Idrija Municipality" => Ok(Self::Idrija), + "Ig Municipality" => Ok(Self::Ig), + "Ivančna Gorica Municipality" => Ok(Self::IvančnaGorica), + "Izola Municipality" => Ok(Self::Izola), + "Jesenice Municipality" => Ok(Self::Jesenice), + "Jezersko Municipality" => Ok(Self::Jezersko), + "Juršinci Municipality" => Ok(Self::Jursinci), + "Kamnik Municipality" => Ok(Self::Kamnik), + "Kanal ob Soči Municipality" => Ok(Self::KanalObSoci), + "Kidričevo Municipality" => Ok(Self::Kidricevo), + "Kobarid Municipality" => Ok(Self::Kobarid), + "Kobilje Municipality" => Ok(Self::Kobilje), + "Komen Municipality" => Ok(Self::Komen), + "Komenda Municipality" => Ok(Self::Komenda), + "Koper City Municipality" => Ok(Self::Koper), + "Kostanjevica na Krki Municipality" => Ok(Self::KostanjevicaNaKrki), + "Kostel Municipality" => Ok(Self::Kostel), + "Kozje Municipality" => Ok(Self::Kozje), + "Kočevje Municipality" => Ok(Self::Kocevje), + "Kranj City Municipality" => Ok(Self::Kranj), + "Kranjska Gora Municipality" => Ok(Self::KranjskaGora), + "Križevci Municipality" => Ok(Self::Krizevci), + "Kungota" => Ok(Self::Kungota), + "Kuzma Municipality" => Ok(Self::Kuzma), + "Laško Municipality" => Ok(Self::Lasko), + "Lenart Municipality" => Ok(Self::Lenart), + "Lendava Municipality" => Ok(Self::Lendava), + "Litija Municipality" => Ok(Self::Litija), + "Ljubljana City Municipality" => Ok(Self::Ljubljana), + "Ljubno Municipality" => Ok(Self::Ljubno), + "Ljutomer Municipality" => Ok(Self::Ljutomer), + "Logatec Municipality" => Ok(Self::Logatec), + "Log–Dragomer Municipality" => Ok(Self::LogDragomer), + "Lovrenc na Pohorju Municipality" => Ok(Self::LovrencNaPohorju), + "Loška Dolina Municipality" => Ok(Self::LoskaDolina), + "Loški Potok Municipality" => Ok(Self::LoskiPotok), + "Lukovica Municipality" => Ok(Self::Lukovica), + "Luče Municipality" => Ok(Self::Luče), + "Majšperk Municipality" => Ok(Self::Majsperk), + "Makole Municipality" => Ok(Self::Makole), + "Maribor City Municipality" => Ok(Self::Maribor), + "Markovci Municipality" => Ok(Self::Markovci), + "Medvode Municipality" => Ok(Self::Medvode), + "Mengeš Municipality" => Ok(Self::Menges), + "Metlika Municipality" => Ok(Self::Metlika), + "Mežica Municipality" => Ok(Self::Mezica), + "Miklavž na Dravskem Polju Municipality" => Ok(Self::MiklavzNaDravskemPolju), + "Miren–Kostanjevica Municipality" => Ok(Self::MirenKostanjevica), + "Mirna Municipality" => Ok(Self::Mirna), + "Mirna Peč Municipality" => Ok(Self::MirnaPec), + "Mislinja Municipality" => Ok(Self::Mislinja), + "Mokronog–Trebelno Municipality" => Ok(Self::MokronogTrebelno), + "Moravske Toplice Municipality" => Ok(Self::MoravskeToplice), + "Moravče Municipality" => Ok(Self::Moravce), + "Mozirje Municipality" => Ok(Self::Mozirje), + "Municipality of Apače" => Ok(Self::Apače), + "Municipality of Cirkulane" => Ok(Self::Cirkulane), + "Municipality of Ilirska Bistrica" => Ok(Self::IlirskaBistrica), + "Municipality of Krško" => Ok(Self::Krsko), + "Municipality of Škofljica" => Ok(Self::Skofljica), + "Murska Sobota City Municipality" => Ok(Self::MurskaSobota), + "Muta Municipality" => Ok(Self::Muta), + "Naklo Municipality" => Ok(Self::Naklo), + "Nazarje Municipality" => Ok(Self::Nazarje), + "Nova Gorica City Municipality" => Ok(Self::NovaGorica), + "Odranci Municipality" => Ok(Self::Odranci), + "Oplotnica" => Ok(Self::Oplotnica), + "Ormož Municipality" => Ok(Self::Ormoz), + "Osilnica Municipality" => Ok(Self::Osilnica), + "Pesnica Municipality" => Ok(Self::Pesnica), + "Piran Municipality" => Ok(Self::Piran), + "Pivka Municipality" => Ok(Self::Pivka), + "Podlehnik Municipality" => Ok(Self::Podlehnik), + "Podvelka Municipality" => Ok(Self::Podvelka), + "Podčetrtek Municipality" => Ok(Self::Podcetrtek), + "Poljčane Municipality" => Ok(Self::Poljcane), + "Polzela Municipality" => Ok(Self::Polzela), + "Postojna Municipality" => Ok(Self::Postojna), + "Prebold Municipality" => Ok(Self::Prebold), + "Preddvor Municipality" => Ok(Self::Preddvor), + "Prevalje Municipality" => Ok(Self::Prevalje), + "Ptuj City Municipality" => Ok(Self::Ptuj), + "Puconci Municipality" => Ok(Self::Puconci), + "Radenci Municipality" => Ok(Self::Radenci), + "Radeče Municipality" => Ok(Self::Radece), + "Radlje ob Dravi Municipality" => Ok(Self::RadljeObDravi), + "Radovljica Municipality" => Ok(Self::Radovljica), + "Ravne na Koroškem Municipality" => Ok(Self::RavneNaKoroskem), + "Razkrižje Municipality" => Ok(Self::Razkrizje), + "Rače–Fram Municipality" => Ok(Self::RaceFram), + "Renče–Vogrsko Municipality" => Ok(Self::RenčeVogrsko), + "Rečica ob Savinji Municipality" => Ok(Self::RecicaObSavinji), + "Ribnica Municipality" => Ok(Self::Ribnica), + "Ribnica na Pohorju Municipality" => Ok(Self::RibnicaNaPohorju), + "Rogatec Municipality" => Ok(Self::Rogatec), + "Rogaška Slatina Municipality" => Ok(Self::RogaskaSlatina), + "Rogašovci Municipality" => Ok(Self::Rogasovci), + "Ruše Municipality" => Ok(Self::Ruse), + "Selnica ob Dravi Municipality" => Ok(Self::SelnicaObDravi), + "Semič Municipality" => Ok(Self::Semic), + "Sevnica Municipality" => Ok(Self::Sevnica), + "Sežana Municipality" => Ok(Self::Sezana), + "Slovenj Gradec City Municipality" => Ok(Self::SlovenjGradec), + "Slovenska Bistrica Municipality" => Ok(Self::SlovenskaBistrica), + "Slovenske Konjice Municipality" => Ok(Self::SlovenskeKonjice), + "Sodražica Municipality" => Ok(Self::Sodrazica), + "Solčava Municipality" => Ok(Self::Solcava), + "Središče ob Dravi" => Ok(Self::SredisceObDravi), + "Starše Municipality" => Ok(Self::Starse), + "Straža Municipality" => Ok(Self::Straza), + "Sveta Ana Municipality" => Ok(Self::SvetaAna), + "Sveta Trojica v Slovenskih Goricah Municipality" => Ok(Self::SvetaTrojica), + "Sveti Andraž v Slovenskih Goricah Municipality" => Ok(Self::SvetiAndraz), + "Sveti Jurij ob Ščavnici Municipality" => Ok(Self::SvetiJurijObScavnici), + "Sveti Jurij v Slovenskih Goricah Municipality" => { + Ok(Self::SvetiJurijVSlovenskihGoricah) + } + "Sveti Tomaž Municipality" => Ok(Self::SvetiTomaz), + "Tabor Municipality" => Ok(Self::Tabor), + "Tišina Municipality" => Ok(Self::Tišina), + "Tolmin Municipality" => Ok(Self::Tolmin), + "Trbovlje Municipality" => Ok(Self::Trbovlje), + "Trebnje Municipality" => Ok(Self::Trebnje), + "Trnovska Vas Municipality" => Ok(Self::TrnovskaVas), + "Trzin Municipality" => Ok(Self::Trzin), + "Tržič Municipality" => Ok(Self::Tržič), + "Turnišče Municipality" => Ok(Self::Turnišče), + "Velika Polana Municipality" => Ok(Self::VelikaPolana), + "Velike Lašče Municipality" => Ok(Self::VelikeLašče), + "Veržej Municipality" => Ok(Self::Veržej), + "Videm Municipality" => Ok(Self::Videm), + "Vipava Municipality" => Ok(Self::Vipava), + "Vitanje Municipality" => Ok(Self::Vitanje), + "Vodice Municipality" => Ok(Self::Vodice), + "Vojnik Municipality" => Ok(Self::Vojnik), + "Vransko Municipality" => Ok(Self::Vransko), + "Vrhnika Municipality" => Ok(Self::Vrhnika), + "Vuzenica Municipality" => Ok(Self::Vuzenica), + "Zagorje ob Savi Municipality" => Ok(Self::ZagorjeObSavi), + "Zavrč Municipality" => Ok(Self::Zavrč), + "Zreče Municipality" => Ok(Self::Zreče), + "Črenšovci Municipality" => Ok(Self::Črenšovci), + "Črna na Koroškem Municipality" => Ok(Self::ČrnaNaKoroškem), + "Črnomelj Municipality" => Ok(Self::Črnomelj), + "Šalovci Municipality" => Ok(Self::Šalovci), + "Šempeter–Vrtojba Municipality" => Ok(Self::ŠempeterVrtojba), + "Šentilj Municipality" => Ok(Self::Šentilj), + "Šentjernej Municipality" => Ok(Self::Šentjernej), + "Šentjur Municipality" => Ok(Self::Šentjur), + "Šentrupert Municipality" => Ok(Self::Šentrupert), + "Šenčur Municipality" => Ok(Self::Šenčur), + "Škocjan Municipality" => Ok(Self::Škocjan), + "Škofja Loka Municipality" => Ok(Self::ŠkofjaLoka), + "Šmarje pri Jelšah Municipality" => Ok(Self::ŠmarjePriJelšah), + "Šmarješke Toplice Municipality" => Ok(Self::ŠmarješkeToplice), + "Šmartno ob Paki Municipality" => Ok(Self::ŠmartnoObPaki), + "Šmartno pri Litiji Municipality" => Ok(Self::ŠmartnoPriLitiji), + "Šoštanj Municipality" => Ok(Self::Šoštanj), + "Štore Municipality" => Ok(Self::Štore), + "Žalec Municipality" => Ok(Self::Žalec), + "Železniki Municipality" => Ok(Self::Železniki), + "Žetale Municipality" => Ok(Self::Žetale), + "Žiri Municipality" => Ok(Self::Žiri), + "Žirovnica Municipality" => Ok(Self::Žirovnica), + "Žužemberk Municipality" => Ok(Self::Žužemberk), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, + } + } +} + +impl ForeignTryFrom for UkraineStatesAbbreviation { + type Error = error_stack::Report; + + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "UkraineStatesAbbreviation"); + + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => match value.as_str() { + "Autonomous Republic of Crimea" => Ok(Self::AutonomousRepublicOfCrimea), + "Cherkasy Oblast" => Ok(Self::CherkasyOblast), + "Chernihiv Oblast" => Ok(Self::ChernihivOblast), + "Chernivtsi Oblast" => Ok(Self::ChernivtsiOblast), + "Dnipropetrovsk Oblast" => Ok(Self::DnipropetrovskOblast), + "Donetsk Oblast" => Ok(Self::DonetskOblast), + "Ivano-Frankivsk Oblast" => Ok(Self::IvanoFrankivskOblast), + "Kharkiv Oblast" => Ok(Self::KharkivOblast), + "Kherson Oblast" => Ok(Self::KhersonOblast), + "Khmelnytsky Oblast" => Ok(Self::KhmelnytskyOblast), + "Kiev" => Ok(Self::Kiev), + "Kirovohrad Oblast" => Ok(Self::KirovohradOblast), + "Kyiv Oblast" => Ok(Self::KyivOblast), + "Luhansk Oblast" => Ok(Self::LuhanskOblast), + "Lviv Oblast" => Ok(Self::LvivOblast), + "Mykolaiv Oblast" => Ok(Self::MykolaivOblast), + "Odessa Oblast" => Ok(Self::OdessaOblast), + "Rivne Oblast" => Ok(Self::RivneOblast), + "Sumy Oblast" => Ok(Self::SumyOblast), + "Ternopil Oblast" => Ok(Self::TernopilOblast), + "Vinnytsia Oblast" => Ok(Self::VinnytsiaOblast), + "Volyn Oblast" => Ok(Self::VolynOblast), + "Zakarpattia Oblast" => Ok(Self::ZakarpattiaOblast), + "Zaporizhzhya Oblast" => Ok(Self::ZaporizhzhyaOblast), + "Zhytomyr Oblast" => Ok(Self::ZhytomyrOblast), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index ced2151e52b..3de5358a2bd 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -17,6 +17,7 @@ v2 = ["api_models/v2", "diesel_models/v2", "common_utils/v2"] v1 = ["api_models/v1", "diesel_models/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2"] +revenue_recovery= [] [dependencies] # First party deps diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index b2385efb6d2..4a6a84124a7 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -4,7 +4,7 @@ use common_utils::{ encryption::Encryption, errors::{CustomResult, ValidationError}, pii, type_name, - types::keymanager, + types::{keymanager, AlwaysRequestExtendedAuthorization}, }; use diesel_models::business_profile::{ AuthenticationConnectorDetails, BusinessPaymentLinkConfig, BusinessPayoutLinkConfig, @@ -58,6 +58,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -101,6 +102,7 @@ pub struct ProfileSetter { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -151,6 +153,7 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_auto_retries_enabled: value.is_auto_retries_enabled, max_auto_retries_enabled: value.max_auto_retries_enabled, + always_request_extended_authorization: value.always_request_extended_authorization, is_click_to_pay_enabled: value.is_click_to_pay_enabled, authentication_product_ids: value.authentication_product_ids, } @@ -306,6 +309,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization: None, is_click_to_pay_enabled, authentication_product_ids, } @@ -347,6 +351,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -386,6 +391,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -425,6 +431,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -464,6 +471,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -503,6 +511,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: Some(is_network_tokenization_enabled), is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -561,6 +570,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: Some(self.is_auto_retries_enabled), max_auto_retries_enabled: self.max_auto_retries_enabled, + always_request_extended_authorization: self.always_request_extended_authorization, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, }) @@ -631,6 +641,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled.unwrap_or(false), max_auto_retries_enabled: item.max_auto_retries_enabled, + always_request_extended_authorization: item.always_request_extended_authorization, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, }) @@ -1343,6 +1354,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, three_ds_decision_manager_config: self.three_ds_decision_manager_config, diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 6c8f499388a..ef2cbe064d6 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -20,6 +20,8 @@ pub mod payments; pub mod payouts; pub mod refunds; pub mod relay; +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +pub mod revenue_recovery; pub mod router_data; pub mod router_data_v2; pub mod router_flow_types; @@ -404,6 +406,8 @@ impl ApiModelToDieselModelConvertor ) }), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } fn convert_back(self) -> api_models::admin::PaymentLinkConfigRequest { @@ -420,6 +424,8 @@ impl ApiModelToDieselModelConvertor background_image, details_layout, payment_button_text, + custom_message_for_card_terms, + payment_button_colour, } = self; api_models::admin::PaymentLinkConfigRequest { theme, @@ -440,6 +446,8 @@ impl ApiModelToDieselModelConvertor background_image: background_image .map(|background_image| background_image.convert_back()), payment_button_text, + custom_message_for_card_terms, + payment_button_colour, } } } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index f7df4ab555b..c74cb38402b 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -11,7 +11,7 @@ use common_utils::{ encryption::Encryption, errors::CustomResult, id_type, pii, - types::{keymanager::ToEncryptable, MinorUnit}, + types::{keymanager::ToEncryptable, MinorUnit, RequestExtendedAuthorizationBool}, }; use diesel_models::payment_intent::TaxDetails; #[cfg(feature = "v2")] @@ -108,6 +108,7 @@ pub struct PaymentIntent { pub organization_id: id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub platform_merchant_id: Option, } @@ -244,7 +245,7 @@ impl AmountDetails { pub fn proxy_create_attempt_amount_details( &self, - confirm_intent_request: &api_models::payments::ProxyPaymentsIntentRequest, + _confirm_intent_request: &api_models::payments::ProxyPaymentsRequest, ) -> payment_attempt::AttemptAmountDetails { let net_amount = self.calculate_net_amount(); @@ -404,6 +405,8 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, /// Identifier for the platform merchant. pub platform_merchant_id: Option, + /// Split Payment Data + pub split_payments: Option, } #[cfg(feature = "v2")] @@ -546,6 +549,7 @@ impl PaymentIntent { routing_algorithm_id: request.routing_algorithm_id, platform_merchant_id: platform_merchant_id .map(|merchant_account| merchant_account.get_id().to_owned()), + split_payments: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 611e60cfff5..8341c2d2fc5 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -11,7 +11,8 @@ use common_utils::{ id_type, pii, types::{ keymanager::{self, KeyManagerState}, - ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, + ConnectorTransactionId, ConnectorTransactionIdTrait, ExtendedAuthorizationAppliedBool, + MinorUnit, RequestExtendedAuthorizationBool, }, }; use diesel_models::{ @@ -19,6 +20,11 @@ use diesel_models::{ PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; +#[cfg(feature = "v2")] +use diesel_models::{ + PaymentAttemptFeatureMetadata as DieselPaymentAttemptFeatureMetadata, + PaymentAttemptRecoveryData as DieselPassiveChurnRecoveryData, +}; use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::PeekInterface; @@ -199,6 +205,21 @@ pub trait PaymentAttemptInterface { card_discovery: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + + #[cfg(all(feature = "v2", feature = "olap"))] + #[allow(clippy::too_many_arguments)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result; } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] @@ -419,6 +440,8 @@ pub struct PaymentAttempt { pub card_discovery: Option, /// Split payment data pub charges: Option, + /// Additional data that might be required by hyperswitch, to enable some specific features. + pub feature_metadata: Option, } impl PaymentAttempt { @@ -490,6 +513,14 @@ impl PaymentAttempt { .transpose() .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode billing address")?; + + let connector_token = Some(diesel_models::ConnectorTokenDetails { + connector_mandate_id: None, + connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( + consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, + )), + }); + let authentication_type = payment_intent.authentication_type.unwrap_or_default(); Ok(Self { @@ -536,9 +567,10 @@ impl PaymentAttempt { external_reference_id: None, payment_method_billing_address, error: None, - connector_token_details: None, + connector_token_details: connector_token, id, card_discovery: None, + feature_metadata: None, }) } @@ -547,7 +579,7 @@ impl PaymentAttempt { payment_intent: &super::PaymentIntent, cell_id: id_type::CellId, storage_scheme: storage_enums::MerchantStorageScheme, - request: &api_models::payments::ProxyPaymentsIntentRequest, + request: &api_models::payments::ProxyPaymentsRequest, encrypted_data: DecryptedPaymentAttempt, ) -> CustomResult { let id = id_type::GlobalAttemptId::generate(&cell_id); @@ -573,14 +605,26 @@ impl PaymentAttempt { consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), }); + let payment_method_type_data = payment_intent + .feature_metadata + .as_ref() + .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) + .map(|rrm| { + rrm.payment_method_type + }); + + let payment_method_subtype_data = payment_intent + .feature_metadata + .as_ref() + .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) + .map(|rrm| rrm.payment_method_subtype); + let authentication_type = payment_intent.authentication_type.unwrap_or_default(); Ok(Self { payment_id: payment_intent.id.clone(), merchant_id: payment_intent.merchant_id.clone(), amount_details: attempt_amount_details, status: common_enums::AttemptStatus::Started, - // This will be decided by the routing algorithm and updated in update trackers - // right before calling the connector connector: Some(request.connector.clone()), authentication_type, created_at: now, @@ -610,15 +654,16 @@ impl PaymentAttempt { customer_acceptance: None, profile_id: payment_intent.profile_id.clone(), organization_id: payment_intent.organization_id.clone(), - payment_method_type: storage_enums::PaymentMethod::Card, + payment_method_type: payment_method_type_data.unwrap_or(common_enums::PaymentMethod::Card), payment_method_id: None, connector_payment_id: None, - payment_method_subtype: storage_enums::PaymentMethodType::Credit, + payment_method_subtype: payment_method_subtype_data.unwrap_or(common_enums::PaymentMethodType::Credit), authentication_applied: None, external_reference_id: None, payment_method_billing_address, error: None, connector_token_details: connector_token, + feature_metadata: None, id, card_discovery: None, }) @@ -691,6 +736,9 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, pub charges: Option, } @@ -938,6 +986,9 @@ pub struct PaymentAttemptNew { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, } @@ -1662,6 +1713,9 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, @@ -1748,6 +1802,9 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, charges: storage_model.charges, }) @@ -1831,6 +1888,9 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, }) } @@ -1900,6 +1960,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_token_details, card_discovery, charges, + feature_metadata, } = self; let AttemptAmountDetails { @@ -1916,6 +1977,7 @@ impl behaviour::Conversion for PaymentAttempt { .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) .unwrap_or((None, None)); + let feature_metadata = feature_metadata.as_ref().map(From::from); Ok(DieselPaymentAttempt { payment_id, @@ -1977,7 +2039,11 @@ impl behaviour::Conversion for PaymentAttempt { connector_payment_data, connector_token_details, card_discovery, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, charges, + feature_metadata, }) } @@ -2090,6 +2156,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_billing_address, connector_token_details: storage_model.connector_token_details, card_discovery: storage_model.card_discovery, + feature_metadata: storage_model.feature_metadata.map(From::from), }) } .await @@ -2100,9 +2167,55 @@ impl behaviour::Conversion for PaymentAttempt { async fn construct_new(self) -> CustomResult { use common_utils::encryption::Encryption; + let Self { + payment_id, + merchant_id, + status, + error, + amount_details, + authentication_type, + created_at, + modified_at, + last_synced, + cancellation_reason, + browser_info, + payment_token, + connector_metadata, + payment_experience, + payment_method_data, + routing_result, + preprocessing_step_id, + multiple_capture_count, + connector_response_reference_id, + updated_by, + redirection_data, + encoded_data, + merchant_connector_id, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + fingerprint_id, + client_source, + client_version, + customer_acceptance, + profile_id, + organization_id, + payment_method_type, + connector_payment_id, + payment_method_subtype, + authentication_applied, + external_reference_id, + id, + payment_method_id, + payment_method_billing_address, + connector, + connector_token_details, + card_discovery, + charges, + feature_metadata, + } = self; - let card_network = self - .payment_method_data + let card_network = payment_method_data .as_ref() .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) @@ -2111,71 +2224,72 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|network| network.as_str()) .map(|network| network.to_string()); - let error_details = self.error; + let error_details = error; Ok(DieselPaymentAttemptNew { - payment_id: self.payment_id, - merchant_id: self.merchant_id, - status: self.status, + payment_id, + merchant_id, + status, error_message: error_details .as_ref() .map(|details| details.message.clone()), - surcharge_amount: self.amount_details.surcharge_amount, - tax_on_surcharge: self.amount_details.tax_on_surcharge, - payment_method_id: self.payment_method_id, - authentication_type: self.authentication_type, - created_at: self.created_at, - modified_at: self.modified_at, - last_synced: self.last_synced, - cancellation_reason: self.cancellation_reason, - browser_info: self.browser_info, - payment_token: self.payment_token, + surcharge_amount: amount_details.surcharge_amount, + tax_on_surcharge: amount_details.tax_on_surcharge, + payment_method_id, + authentication_type, + created_at, + modified_at, + last_synced, + cancellation_reason, + browser_info, + payment_token, error_code: error_details.as_ref().map(|details| details.code.clone()), - connector_metadata: self.connector_metadata, - payment_experience: self.payment_experience, - payment_method_data: self.payment_method_data, - preprocessing_step_id: self.preprocessing_step_id, + connector_metadata, + payment_experience, + payment_method_data, + preprocessing_step_id, error_reason: error_details .as_ref() .and_then(|details| details.reason.clone()), - connector_response_reference_id: self.connector_response_reference_id, - multiple_capture_count: self.multiple_capture_count, - amount_capturable: self.amount_details.amount_capturable, - updated_by: self.updated_by, - merchant_connector_id: self.merchant_connector_id, - redirection_data: self.redirection_data.map(From::from), - encoded_data: self.encoded_data, + connector_response_reference_id, + multiple_capture_count, + amount_capturable: amount_details.amount_capturable, + updated_by, + merchant_connector_id, + redirection_data: redirection_data.map(From::from), + encoded_data, unified_code: error_details .as_ref() .and_then(|details| details.unified_code.clone()), unified_message: error_details .as_ref() .and_then(|details| details.unified_message.clone()), - net_amount: self.amount_details.net_amount, - external_three_ds_authentication_attempted: self - .external_three_ds_authentication_attempted, - authentication_connector: self.authentication_connector, - authentication_id: self.authentication_id, - fingerprint_id: self.fingerprint_id, - client_source: self.client_source, - client_version: self.client_version, - customer_acceptance: self.customer_acceptance, - profile_id: self.profile_id, - organization_id: self.organization_id, + net_amount: amount_details.net_amount, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + fingerprint_id, + client_source, + client_version, + customer_acceptance, + profile_id, + organization_id, card_network, - order_tax_amount: self.amount_details.order_tax_amount, - shipping_cost: self.amount_details.shipping_cost, - amount_to_capture: self.amount_details.amount_to_capture, - payment_method_billing_address: self - .payment_method_billing_address - .map(Encryption::from), - payment_method_subtype: self.payment_method_subtype, - payment_method_type_v2: self.payment_method_type, - id: self.id, - connector_token_details: self.connector_token_details, - card_discovery: self.card_discovery, - connector: self.connector, - charges: self.charges, + order_tax_amount: amount_details.order_tax_amount, + shipping_cost: amount_details.shipping_cost, + amount_to_capture: amount_details.amount_to_capture, + payment_method_billing_address: payment_method_billing_address.map(Encryption::from), + payment_method_subtype, + payment_method_type_v2: payment_method_type, + id, + charges, + connector_token_details, + card_discovery, + extended_authorization_applied: None, + request_extended_authorization: None, + capture_before: None, + feature_metadata: feature_metadata.as_ref().map(From::from), + connector, }) } } @@ -2209,6 +2323,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: Some(authentication_type), + feature_metadata: None, }, PaymentAttemptUpdate::ErrorUpdate { status, @@ -2235,6 +2350,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => { let ConfirmIntentResponseUpdate { @@ -2266,6 +2382,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details, authentication_type: None, + feature_metadata: None, } } PaymentAttemptUpdate::SyncUpdate { @@ -2291,6 +2408,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::CaptureUpdate { status, @@ -2315,6 +2433,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::PreCaptureUpdate { amount_to_capture, @@ -2338,7 +2457,44 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_capturable: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, } } } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, PartialEq)] +pub struct PaymentAttemptFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, PartialEq)] +pub struct PaymentAttemptRevenueRecoveryData { + pub attempt_triggered_by: common_enums::TriggeredBy, +} + +#[cfg(feature = "v2")] +impl From<&PaymentAttemptFeatureMetadata> for DieselPaymentAttemptFeatureMetadata { + fn from(item: &PaymentAttemptFeatureMetadata) -> Self { + let revenue_recovery = + item.revenue_recovery + .as_ref() + .map(|recovery_data| DieselPassiveChurnRecoveryData { + attempt_triggered_by: recovery_data.attempt_triggered_by, + }); + Self { revenue_recovery } + } +} + +#[cfg(feature = "v2")] +impl From for PaymentAttemptFeatureMetadata { + fn from(item: DieselPaymentAttemptFeatureMetadata) -> Self { + let revenue_recovery = + item.revenue_recovery + .map(|recovery_data| PaymentAttemptRevenueRecoveryData { + attempt_triggered_by: recovery_data.attempt_triggered_by, + }); + Self { revenue_recovery } + } +} diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 728bcfba68e..caf439bedaa 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -120,6 +120,30 @@ pub trait PaymentIntentInterface { storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result< + Vec<( + PaymentIntent, + Option, + )>, + errors::StorageError, + >; + + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result>, errors::StorageError>; + #[cfg(all(feature = "v1", feature = "olap"))] async fn get_filtered_active_attempt_ids_for_total_count( &self, @@ -290,7 +314,6 @@ pub enum PaymentIntentUpdate { #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize)] -#[allow(clippy::large_enum_variant)] pub enum PaymentIntentUpdate { /// PreUpdate tracker of ConfirmIntent ConfirmIntent { @@ -303,7 +326,7 @@ pub enum PaymentIntentUpdate { status: common_enums::IntentStatus, amount_captured: Option, updated_by: String, - feature_metadata: Option, + feature_metadata: Option>, }, /// SyncUpdate of ConfirmIntent in PostUpdateTrackers SyncUpdate { @@ -443,7 +466,7 @@ impl From for diesel_models::PaymentIntentUpdateInternal { allowed_payment_method_types: None, metadata: None, connector_metadata: None, - feature_metadata, + feature_metadata: feature_metadata.map(|val| *val), payment_link_config: None, request_incremental_authorization: None, session_expiry: None, @@ -1080,6 +1103,7 @@ impl From for diesel_models::PaymentIntentUpdateInt } } +#[cfg(feature = "v1")] pub enum PaymentIntentFetchConstraints { Single { payment_intent_id: id_type::PaymentId, @@ -1087,6 +1111,7 @@ pub enum PaymentIntentFetchConstraints { List(Box), } +#[cfg(feature = "v1")] impl PaymentIntentFetchConstraints { pub fn get_profile_id_list(&self) -> Option> { if let Self::List(pi_list_params) = self { @@ -1097,6 +1122,26 @@ impl PaymentIntentFetchConstraints { } } +#[cfg(feature = "v2")] +pub enum PaymentIntentFetchConstraints { + Single { + payment_intent_id: id_type::GlobalPaymentId, + }, + List(Box), +} + +#[cfg(feature = "v2")] +impl PaymentIntentFetchConstraints { + pub fn get_profile_id(&self) -> Option { + if let Self::List(pi_list_params) = self { + pi_list_params.profile_id.clone() + } else { + None + } + } +} + +#[cfg(feature = "v1")] pub struct PaymentIntentListParams { pub offset: u32, pub starting_at: Option, @@ -1120,6 +1165,30 @@ pub struct PaymentIntentListParams { pub merchant_order_reference_id: Option, } +#[cfg(feature = "v2")] +pub struct PaymentIntentListParams { + pub offset: u32, + pub starting_at: Option, + pub ending_at: Option, + pub amount_filter: Option, + pub connector: Option, + pub currency: Option, + pub status: Option, + pub payment_method_type: Option, + pub payment_method_subtype: Option, + pub authentication_type: Option, + pub merchant_connector_id: Option, + pub profile_id: Option, + pub customer_id: Option, + pub starting_after_id: Option, + pub ending_before_id: Option, + pub limit: Option, + pub order: api_models::payments::Order, + pub card_network: Option, + pub merchant_order_reference_id: Option, +} + +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListConstraints) -> Self { let api_models::payments::PaymentListConstraints { @@ -1158,6 +1227,73 @@ impl From for PaymentIntentFetchCo } } +#[cfg(feature = "v2")] +impl From for PaymentIntentFetchConstraints { + fn from(value: api_models::payments::PaymentListConstraints) -> Self { + let api_models::payments::PaymentListConstraints { + customer_id, + starting_after, + ending_before, + limit, + created, + created_lt, + created_gt, + created_lte, + created_gte, + payment_id, + profile_id, + start_amount, + end_amount, + connector, + currency, + status, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + order_on, + order_by, + card_network, + merchant_order_reference_id, + offset, + } = value; + if let Some(payment_intent_id) = payment_id { + Self::Single { payment_intent_id } + } else { + Self::List(Box::new(PaymentIntentListParams { + offset: offset.unwrap_or_default(), + starting_at: created_gte.or(created_gt).or(created), + ending_at: created_lte.or(created_lt).or(created), + amount_filter: (start_amount.is_some() || end_amount.is_some()).then_some({ + api_models::payments::AmountFilter { + start_amount, + end_amount, + } + }), + connector, + currency, + status, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + profile_id, + customer_id, + starting_after_id: starting_after, + ending_before_id: ending_before, + limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), + order: api_models::payments::Order { + on: order_on, + by: order_by, + }, + card_network, + merchant_order_reference_id, + })) + } + } +} + +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: common_utils::types::TimeRange) -> Self { Self::List(Box::new(PaymentIntentListParams { @@ -1185,6 +1321,7 @@ impl From for PaymentIntentFetchConstraints { } } +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListFilterConstraints) -> Self { let api_models::payments::PaymentListFilterConstraints { @@ -1236,6 +1373,7 @@ impl From for PaymentIntentF } } +#[cfg(feature = "v1")] impl TryFrom<(T, Option>)> for PaymentIntentFetchConstraints where Self: From, @@ -1339,6 +1477,7 @@ impl behaviour::Conversion for PaymentIntent { routing_algorithm_id, payment_link_config, platform_merchant_id, + split_payments, } = self; Ok(DieselPaymentIntent { skip_external_tax_calculation: Some(amount_details.get_external_tax_action_as_bool()), @@ -1409,8 +1548,9 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config, routing_algorithm_id, psd2_sca_exemption_type: None, + request_extended_authorization: None, platform_merchant_id, - split_payments: None, + split_payments, }) } async fn convert_back( @@ -1537,6 +1677,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config: storage_model.payment_link_config, routing_algorithm_id: storage_model.routing_algorithm_id, platform_merchant_id: storage_model.platform_merchant_id, + split_payments: storage_model.split_payments, }) } .await @@ -1675,6 +1816,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + request_extended_authorization: self.request_extended_authorization, psd2_sca_exemption_type: self.psd2_sca_exemption_type, platform_merchant_id: self.platform_merchant_id, }) @@ -1764,6 +1906,7 @@ impl behaviour::Conversion for PaymentIntent { is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, organization_id: storage_model.organization_id, skip_external_tax_calculation: storage_model.skip_external_tax_calculation, + request_extended_authorization: storage_model.request_extended_authorization, psd2_sca_exemption_type: storage_model.psd2_sca_exemption_type, platform_merchant_id: storage_model.platform_merchant_id, }) @@ -1829,6 +1972,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + request_extended_authorization: self.request_extended_authorization, psd2_sca_exemption_type: self.psd2_sca_exemption_type, platform_merchant_id: self.platform_merchant_id, }) diff --git a/crates/hyperswitch_domain_models/src/revenue_recovery.rs b/crates/hyperswitch_domain_models/src/revenue_recovery.rs new file mode 100644 index 00000000000..8467189d27a --- /dev/null +++ b/crates/hyperswitch_domain_models/src/revenue_recovery.rs @@ -0,0 +1,190 @@ +use api_models::webhooks; +use time::PrimitiveDateTime; + +/// Recovery payload is unified struct constructed from billing connectors +#[derive(Debug)] +pub struct RevenueRecoveryAttemptData { + /// transaction amount against invoice, accepted in minor unit. + pub amount: common_utils::types::MinorUnit, + /// currency of the transaction + pub currency: common_enums::enums::Currency, + /// merchant reference id at billing connector. ex: invoice_id + pub merchant_reference_id: common_utils::id_type::PaymentReferenceId, + /// transaction id reference at payment connector + pub connector_transaction_id: Option, + /// error code sent by billing connector. + pub error_code: Option, + /// error message sent by billing connector. + pub error_message: Option, + /// mandate token at payment processor end. + pub processor_payment_method_token: Option, + /// customer id at payment connector for which mandate is attached. + pub connector_customer_id: Option, + /// Payment gateway identifier id at billing processor. + pub connector_account_reference_id: Option, + /// timestamp at which transaction has been created at billing connector + pub transaction_created_at: Option, + /// transaction status at billing connector equivalent to payment attempt status. + pub status: common_enums::enums::AttemptStatus, + /// payment method of payment attempt. + pub payment_method_type: common_enums::enums::PaymentMethod, + /// payment method sub type of the payment attempt. + pub payment_method_sub_type: common_enums::enums::PaymentMethodType, +} + +/// This is unified struct for Revenue Recovery Invoice Data and it is constructed from billing connectors +#[derive(Debug)] +pub struct RevenueRecoveryInvoiceData { + /// invoice amount at billing connector + pub amount: common_utils::types::MinorUnit, + /// currency of the amount. + pub currency: common_enums::enums::Currency, + /// merchant reference id at billing connector. ex: invoice_id + pub merchant_reference_id: common_utils::id_type::PaymentReferenceId, +} + +/// type of action that needs to taken after consuming recovery payload +#[derive(Debug)] +pub enum RecoveryAction { + /// Stops the process tracker and update the payment intent. + CancelInvoice, + /// Records the external transaction against payment intent. + ScheduleFailedPayment, + /// Records the external payment and stops the internal process tracker. + SuccessPaymentExternal, + /// Pending payments from billing processor. + PendingPayment, + /// No action required. + NoAction, + /// Invalid event has been received. + InvalidAction, +} + +pub struct RecoveryPaymentIntent { + pub payment_id: common_utils::id_type::GlobalPaymentId, + pub status: common_enums::enums::IntentStatus, + pub feature_metadata: Option, +} + +pub struct RecoveryPaymentAttempt { + pub attempt_id: common_utils::id_type::GlobalAttemptId, + pub attempt_status: common_enums::AttemptStatus, + pub feature_metadata: Option, +} + +impl RecoveryPaymentAttempt { + pub fn get_attempt_triggered_by(self) -> Option { + self.feature_metadata.and_then(|metadata| { + metadata + .revenue_recovery + .map(|recovery| recovery.attempt_triggered_by) + }) + } +} + +impl RecoveryAction { + pub fn get_action( + event_type: webhooks::IncomingWebhookEvent, + attempt_triggered_by: Option, + ) -> Self { + match event_type { + webhooks::IncomingWebhookEvent::PaymentIntentFailure + | webhooks::IncomingWebhookEvent::PaymentIntentSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentProcessing + | webhooks::IncomingWebhookEvent::PaymentIntentPartiallyFunded + | webhooks::IncomingWebhookEvent::PaymentIntentCancelled + | webhooks::IncomingWebhookEvent::PaymentIntentCancelFailure + | webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationFailure + | webhooks::IncomingWebhookEvent::PaymentIntentCaptureSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentCaptureFailure + | webhooks::IncomingWebhookEvent::PaymentActionRequired + | webhooks::IncomingWebhookEvent::EventNotSupported + | webhooks::IncomingWebhookEvent::SourceChargeable + | webhooks::IncomingWebhookEvent::SourceTransactionCreated + | webhooks::IncomingWebhookEvent::RefundFailure + | webhooks::IncomingWebhookEvent::RefundSuccess + | webhooks::IncomingWebhookEvent::DisputeOpened + | webhooks::IncomingWebhookEvent::DisputeExpired + | webhooks::IncomingWebhookEvent::DisputeAccepted + | webhooks::IncomingWebhookEvent::DisputeCancelled + | webhooks::IncomingWebhookEvent::DisputeChallenged + | webhooks::IncomingWebhookEvent::DisputeWon + | webhooks::IncomingWebhookEvent::DisputeLost + | webhooks::IncomingWebhookEvent::MandateActive + | webhooks::IncomingWebhookEvent::MandateRevoked + | webhooks::IncomingWebhookEvent::EndpointVerification + | webhooks::IncomingWebhookEvent::ExternalAuthenticationARes + | webhooks::IncomingWebhookEvent::FrmApproved + | webhooks::IncomingWebhookEvent::FrmRejected + | webhooks::IncomingWebhookEvent::PayoutSuccess + | webhooks::IncomingWebhookEvent::PayoutFailure + | webhooks::IncomingWebhookEvent::PayoutProcessing + | webhooks::IncomingWebhookEvent::PayoutCancelled + | webhooks::IncomingWebhookEvent::PayoutCreated + | webhooks::IncomingWebhookEvent::PayoutExpired + | webhooks::IncomingWebhookEvent::PayoutReversed => Self::InvalidAction, + webhooks::IncomingWebhookEvent::RecoveryPaymentFailure => match attempt_triggered_by { + Some(common_enums::TriggeredBy::Internal) => Self::NoAction, + Some(common_enums::TriggeredBy::External) | None => Self::ScheduleFailedPayment, + }, + webhooks::IncomingWebhookEvent::RecoveryPaymentSuccess => match attempt_triggered_by { + Some(common_enums::TriggeredBy::Internal) => Self::NoAction, + Some(common_enums::TriggeredBy::External) | None => Self::SuccessPaymentExternal, + }, + webhooks::IncomingWebhookEvent::RecoveryPaymentPending => Self::PendingPayment, + webhooks::IncomingWebhookEvent::RecoveryInvoiceCancel => Self::CancelInvoice, + } + } +} + +impl From<&RevenueRecoveryInvoiceData> for api_models::payments::AmountDetails { + fn from(data: &RevenueRecoveryInvoiceData) -> Self { + let amount = api_models::payments::AmountDetailsSetter { + order_amount: data.amount.into(), + currency: data.currency, + shipping_cost: None, + order_tax_amount: None, + skip_external_tax_calculation: common_enums::TaxCalculationOverride::Skip, + skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::Skip, + surcharge_amount: None, + tax_on_surcharge: None, + }; + Self::new(amount) + } +} + +impl From<&RevenueRecoveryInvoiceData> for api_models::payments::PaymentsCreateIntentRequest { + fn from(data: &RevenueRecoveryInvoiceData) -> Self { + let amount_details = api_models::payments::AmountDetails::from(data); + Self { + amount_details, + merchant_reference_id: Some(data.merchant_reference_id.clone()), + routing_algorithm_id: None, + // Payments in the revenue recovery flow are always recurring transactions, + // so capture method will be always automatic. + capture_method: Some(common_enums::CaptureMethod::Automatic), + authentication_type: Some(common_enums::AuthenticationType::NoThreeDs), + billing: None, + shipping: None, + customer_id: None, + customer_present: Some(common_enums::PresenceOfCustomerDuringPayment::Absent), + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_enabled: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index e45f3e9878b..b15206347f2 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -512,7 +512,7 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata, + feature_metadata: updated_feature_metadata.map(Box::new), }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -521,7 +521,7 @@ impl .unwrap_or(common_enums::IntentStatus::Failed), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata, + feature_metadata: updated_feature_metadata.map(Box::new), }, } } @@ -1180,7 +1180,7 @@ impl ), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata, + feature_metadata: updated_feature_metadata.map(Box::new), }, Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { status: error @@ -1189,7 +1189,7 @@ impl .unwrap_or(common_enums::IntentStatus::Failed), amount_captured, updated_by: storage_scheme.to_string(), - feature_metadata: updated_feature_metadata, + feature_metadata: updated_feature_metadata.map(Box::new), }, } } diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs index ddc56eb2a29..ba851e05060 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs @@ -4,5 +4,8 @@ pub struct PreAuthenticate; #[derive(Debug, Clone)] pub struct PostAuthenticate; +#[derive(Debug, Clone)] +pub struct AuthenticationConfirmation; + #[derive(Debug, Clone)] pub struct Authenticate; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 00e915f272d..d4dcd337f82 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -117,7 +117,7 @@ pub struct PaymentsCaptureData { pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. pub capture_method: Option, - + pub split_payments: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, pub minor_amount_to_capture: MinorUnit, @@ -290,6 +290,7 @@ pub struct PaymentsPreProcessingData { pub related_transaction_id: Option, pub redirect_response: Option, pub metadata: Option>, + pub split_payments: Option, // New amount for amount frame work pub minor_amount: Option, @@ -319,6 +320,7 @@ impl TryFrom for PaymentsPreProcessingData { related_transaction_id: data.related_transaction_id, redirect_response: None, enrolled_for_3ds: data.enrolled_for_3ds, + split_payments: data.split_payments, metadata: data.metadata.map(Secret::new), }) } @@ -347,6 +349,7 @@ impl TryFrom for PaymentsPreProcessingData { mandate_id: data.mandate_id, related_transaction_id: None, redirect_response: data.redirect_response, + split_payments: None, enrolled_for_3ds: true, metadata: data.connector_meta.map(Secret::new), }) @@ -603,8 +606,8 @@ impl pub struct AuthenticationData { pub eci: Option, pub cavv: String, - pub threeds_server_transaction_id: String, - pub message_version: common_utils::types::SemanticVersion, + pub threeds_server_transaction_id: Option, + pub message_version: Option, pub ds_trans_id: Option, } @@ -647,6 +650,7 @@ pub struct RefundIntegrityObject { pub enum SplitRefundsRequest { StripeSplitRefund(StripeSplitRefund), AdyenSplitRefund(common_types::domain::AdyenSplitData), + XenditSplitRefund(common_types::domain::XenditSplitSubMerchantData), } #[derive(Debug, serde::Deserialize, Clone)] diff --git a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs index 4238c513f44..248fec6f05e 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs @@ -1,5 +1,7 @@ use api_models::payments::DeviceChannel; +use common_utils::types::MinorUnit; use masking::Secret; +use time::PrimitiveDateTime; use crate::{address::Address, payment_method_data::PaymentMethodData}; @@ -53,7 +55,7 @@ pub struct ServiceSessionIds { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct TransactionDetails { - pub amount: Option, + pub amount: Option, pub currency: Option, pub device_channel: Option, pub message_category: Option, @@ -75,6 +77,7 @@ pub enum UasAuthenticationResponseData { PostAuthentication { authentication_details: PostAuthenticationDetails, }, + Confirmation {}, } #[derive(Debug, Clone)] @@ -120,3 +123,22 @@ pub struct DynamicData { pub dynamic_data_type: Option, pub ds_trans_id: Option, } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct UasConfirmationRequestData { + pub x_src_flow_id: Option, + pub transaction_amount: MinorUnit, + pub transaction_currency: common_enums::Currency, + // Type of event associated with the checkout. Valid values are: - 01 - Authorise - 02 - Capture - 03 - Refund - 04 - Cancel - 05 - Fraud - 06 - Chargeback - 07 - Other + pub checkout_event_type: Option, + pub checkout_event_status: Option, + pub confirmation_status: Option, + pub confirmation_reason: Option, + pub confirmation_timestamp: Option, + // Authorisation code associated with an approved transaction. + pub network_authorization_code: Option, + // The unique authorisation related tracing value assigned by a Payment Network and provided in an authorisation response. Required only when checkoutEventType=01. If checkoutEventType=01 and the value of networkTransactionIdentifier is unknown, please pass UNAVLB + pub network_transaction_identifier: Option, + pub correlation_id: Option, + pub merchant_transaction_id: Option, +} diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index dc64560ead6..a774dda3c95 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -3,15 +3,17 @@ pub use diesel_models::types::OrderDetailsWithAmount; use crate::{ router_data::{AccessToken, RouterData}, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, Authorize, - AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, - Execute, IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, - PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, SetupMandate, Void, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, AuthenticationConfirmation, + Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, + CreateConnectorCustomer, Execute, IncrementalAuthorization, PSync, PaymentMethodToken, + PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, + SetupMandate, Void, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, MandateRevokeRequestData, PaymentMethodTokenizationData, @@ -60,6 +62,12 @@ pub type UasPostAuthenticationRouterData = pub type UasPreAuthenticationRouterData = RouterData; +pub type UasAuthenticationConfirmationRouterData = RouterData< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +>; + pub type MandateRevokeRouterData = RouterData; pub type PaymentsIncrementalAuthorizationRouterData = RouterData< diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 99a06ff2e3a..f0402c45f74 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -10,8 +10,10 @@ license.workspace = true default = ["dummy_connector", "frm", "payouts"] dummy_connector = [] v1 = ["hyperswitch_domain_models/v1", "api_models/v1", "common_utils/v1"] +v2 = [] payouts = ["hyperswitch_domain_models/payouts"] frm = ["hyperswitch_domain_models/frm"] +revenue_recovery= [] [dependencies] actix-web = "4.5.1" diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index ab918b5334b..35a3da46330 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -34,13 +34,14 @@ use hyperswitch_domain_models::{ UasFlowData, }, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, PostAuthenticate, - PreAuthenticate, VerifyWebhookSource, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, AuthenticationConfirmation, + PostAuthenticate, PreAuthenticate, VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, MandateRevokeRequestData, VerifyWebhookSourceRequestData, }, @@ -371,7 +372,11 @@ pub trait ConnectorVerifyWebhookSourceV2: /// trait UnifiedAuthenticationService pub trait UnifiedAuthenticationService: - ConnectorCommon + UasPreAuthentication + UasPostAuthentication + UasAuthentication + ConnectorCommon + + UasPreAuthentication + + UasPostAuthentication + + UasAuthenticationConfirmation + + UasAuthentication { } @@ -395,6 +400,16 @@ pub trait UasPostAuthentication: { } +/// trait UasAuthenticationConfirmation +pub trait UasAuthenticationConfirmation: + ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait UasAuthentication pub trait UasAuthentication: ConnectorIntegration @@ -403,7 +418,11 @@ pub trait UasAuthentication: /// trait UnifiedAuthenticationServiceV2 pub trait UnifiedAuthenticationServiceV2: - ConnectorCommon + UasPreAuthenticationV2 + UasPostAuthenticationV2 + UasAuthenticationV2 + ConnectorCommon + + UasPreAuthenticationV2 + + UasPostAuthenticationV2 + + UasAuthenticationV2 + + UasAuthenticationConfirmationV2 { } @@ -429,6 +448,17 @@ pub trait UasPostAuthenticationV2: { } +/// trait UasAuthenticationConfirmationV2 +pub trait UasAuthenticationConfirmationV2: + ConnectorIntegrationV2< + AuthenticationConfirmation, + UasFlowData, + UasConfirmationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait UasAuthenticationV2 pub trait UasAuthenticationV2: ConnectorIntegrationV2< diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index a047e7f00d7..d7c7360a2db 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -14,13 +14,16 @@ use hyperswitch_domain_models::{ Session, SetupMandate, Void, }, refunds::{Execute, RSync}, - unified_authentication_service::{Authenticate, PostAuthenticate, PreAuthenticate}, + unified_authentication_service::{ + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, + }, webhooks::VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, @@ -206,6 +209,13 @@ pub type UasPostAuthenticationType = dyn ConnectorIntegration< UasAuthenticationResponseData, >; +/// Type alias for `ConnectorIntegration` +pub type UasAuthenticationConfirmationType = dyn ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +>; + /// Type alias for `ConnectorIntegration` pub type UasAuthenticationType = dyn ConnectorIntegration< Authenticate, diff --git a/crates/hyperswitch_interfaces/src/webhooks.rs b/crates/hyperswitch_interfaces/src/webhooks.rs index cc3bd487645..6a243cf09f7 100644 --- a/crates/hyperswitch_interfaces/src/webhooks.rs +++ b/crates/hyperswitch_interfaces/src/webhooks.rs @@ -275,4 +275,33 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { > { Ok(None) } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + /// get revenue recovery invoice details + fn get_revenue_recovery_attempt_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryAttemptData, + errors::ConnectorError, + > { + Err(errors::ConnectorError::NotImplemented( + "get_revenue_recovery_attempt_details method".to_string(), + ) + .into()) + } + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + /// get revenue recovery transaction details + fn get_revenue_recovery_invoice_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryInvoiceData, + errors::ConnectorError, + > { + Err(errors::ConnectorError::NotImplemented( + "get_revenue_recovery_invoice_details method".to_string(), + ) + .into()) + } } diff --git a/crates/masking/src/abs.rs b/crates/masking/src/abs.rs index 6501f89c9db..8996db65f23 100644 --- a/crates/masking/src/abs.rs +++ b/crates/masking/src/abs.rs @@ -6,6 +6,9 @@ use crate::Secret; pub trait PeekInterface { /// Only method providing access to the secret value. fn peek(&self) -> &S; + + /// Provide a mutable reference to the inner value. + fn peek_mut(&mut self) -> &mut S; } /// Interface that consumes a option secret and returns the value. diff --git a/crates/masking/src/bytes.rs b/crates/masking/src/bytes.rs index e828f8a78e0..07b8c289ad8 100644 --- a/crates/masking/src/bytes.rs +++ b/crates/masking/src/bytes.rs @@ -28,6 +28,10 @@ impl PeekInterface for SecretBytesMut { fn peek(&self) -> &BytesMut { &self.0 } + + fn peek_mut(&mut self) -> &mut BytesMut { + &mut self.0 + } } impl fmt::Debug for SecretBytesMut { diff --git a/crates/masking/src/secret.rs b/crates/masking/src/secret.rs index 7f7c18094a0..ee65b1c015b 100644 --- a/crates/masking/src/secret.rs +++ b/crates/masking/src/secret.rs @@ -95,6 +95,10 @@ where fn peek(&self) -> &SecretValue { &self.inner_secret } + + fn peek_mut(&mut self) -> &mut SecretValue { + &mut self.inner_secret + } } impl From for Secret diff --git a/crates/masking/src/strong_secret.rs b/crates/masking/src/strong_secret.rs index 300b5463d25..43d2c97dfb3 100644 --- a/crates/masking/src/strong_secret.rs +++ b/crates/masking/src/strong_secret.rs @@ -32,6 +32,10 @@ impl PeekInterface fn peek(&self) -> &Secret { &self.inner_secret } + + fn peek_mut(&mut self) -> &mut Secret { + &mut self.inner_secret + } } impl From diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index eb5edb08fab..1b6f73fb1cd 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -220,6 +220,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, common_types::domain::AdyenSplitItem, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_utils::types::ChargeRefunds, common_types::refunds::SplitRefund, common_types::refunds::StripeSplitRefundRequest, @@ -275,6 +281,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PaymentType, api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, + api_models::enums::TriggeredBy, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, api_models::enums::PayoutConnectors, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index e5c9c6fa81f..a4eb67b64f4 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -127,6 +127,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_create_and_confirm_intent, routes::payments::payments_connector_session, routes::payments::list_payment_methods, + routes::payments::payments_list, //Routes for payment methods routes::payment_method::create_payment_method_api, @@ -164,6 +165,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::SplitPaymentsRequest, common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_types::domain::AdyenSplitItem, common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, @@ -309,6 +316,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AddressDetails, api_models::payments::BankDebitData, api_models::payments::AliPayQr, + api_models::payments::PaymentAttemptFeatureMetadata, + api_models::payments::PaymentAttemptRevenueRecoveryData, api_models::payments::AliPayRedirection, api_models::payments::MomoRedirection, api_models::payments::TouchNGoRedirection, @@ -375,6 +384,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ConnectorTokenDetails, api_models::payments::PaymentsRequest, api_models::payments::PaymentsResponse, + api_models::payments::PaymentsListResponseItem, api_models::payments::PaymentRetrieveBody, api_models::payments::PaymentsRetrieveRequest, api_models::payments::PaymentsCaptureRequest, @@ -437,7 +447,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SamsungPayCardBrand, api_models::payments::SamsungPayTokenData, api_models::payments::PaymentsCancelRequest, - api_models::payments::PaymentListConstraints, + api_models::payments::PaymentListResponse, api_models::payments::CashappQr, api_models::payments::BankTransferData, api_models::payments::BankTransferNextStepsData, @@ -469,6 +479,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentRevenueRecoveryMetadata, api_models::payments::BillingConnectorPaymentDetails, api_models::enums::PaymentConnectorTransmission, + api_models::enums::TriggeredBy, api_models::payments::PaymentAttemptResponse, api_models::payments::PaymentAttemptAmountDetails, api_models::payments::CaptureResponse, @@ -493,6 +504,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsConfirmIntentResponse, api_models::payments::AmountDetailsResponse, api_models::payments::BankCodeResponse, + api_models::payments::Order, + api_models::payments::SortOn, + api_models::payments::SortBy, api_models::payments::PaymentMethodListResponseForPayments, api_models::payments::ResponsePaymentMethodTypesForPayments, api_models::payment_methods::RequiredFieldInfo, diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index ceccd53c4dd..633e0685e60 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -457,6 +457,7 @@ pub fn payments_cancel() {} /// Payments - List /// /// To list the *payments* +#[cfg(feature = "v1")] #[utoipa::path( get, path = "/payments/list", @@ -846,3 +847,21 @@ pub(crate) enum ForceSync { security(("publishable_key" = [])) )] pub fn list_payment_methods() {} + +/// Payments - List +/// +/// To list the *payments* +#[cfg(feature = "v2")] +#[utoipa::path( + get, + path = "/v2/payments/list", + params(api_models::payments::PaymentListConstraints), + responses( + (status = 200, description = "Successfully retrieved a payment list", body = PaymentListResponse), + (status = 404, description = "No payments found") + ), + tag = "Payments", + operation_id = "List all Payments", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub fn payments_list() {} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index d590797d642..8197670acf3 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -33,11 +33,12 @@ payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_connectors payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] -v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2"] +v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2","hyperswitch_interfaces/v2"] v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1", "hyperswitch_connectors/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] +revenue_recovery =["api_models/revenue_recovery","hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index a9f5ae59493..3849054045c 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -1997,6 +1997,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { @@ -5457,6 +5504,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { @@ -9034,7 +9128,86 @@ impl Default for settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), - common: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), } ), ]), diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 254c0346e8e..4d9f5978b08 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -407,8 +407,13 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme force_3ds: true, eci: authentication_data.and_then(|auth| auth.eci.clone()), cryptogram: authentication_data.map(|auth| auth.cavv.clone()), - xid: authentication_data.map(|auth| auth.threeds_server_transaction_id.clone()), - version: authentication_data.map(|auth| auth.message_version.to_string()), + xid: authentication_data + .and_then(|auth| auth.threeds_server_transaction_id.clone()), + version: authentication_data.and_then(|auth| { + auth.message_version + .clone() + .map(|version| version.to_string()) + }), }, enums::AuthenticationType::NoThreeDs => CheckoutThreeDS { enabled: false, diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 36daa62e558..8f4a596131c 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1,15 +1,12 @@ use std::collections::HashMap; use common_utils::pii::Email; +use hyperswitch_connectors::utils::AddressDetailsData; use masking::ExposeInterface; use serde::{Deserialize, Serialize}; use unidecode::unidecode; -use crate::{ - connector::utils::{AddressDetailsData, PhoneDetailsData}, - errors, - types::api::MessageCategory, -}; +use crate::{connector::utils::PhoneDetailsData, errors, types::api::MessageCategory}; #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index ab228d95a9e..ab6002ba312 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -253,12 +253,13 @@ pub struct NetceteraErrorDetails { #[derive(Debug, Serialize, Deserialize)] pub struct NetceteraMetaData { - pub mcc: String, - pub merchant_country_code: String, - pub merchant_name: String, + pub mcc: Option, + pub merchant_country_code: Option, + pub merchant_name: Option, pub endpoint_prefix: String, - pub three_ds_requestor_name: String, - pub three_ds_requestor_id: String, + pub three_ds_requestor_name: Option, + pub three_ds_requestor_id: Option, + pub merchant_configuration_id: Option, } impl TryFrom<&Option> for NetceteraMetaData { @@ -515,13 +516,13 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio .parse_value("NetceteraMetaData") .change_context(errors::ConnectorError::RequestEncodingFailed)?; let merchant_data = netcetera_types::MerchantData { - merchant_configuration_id: None, - mcc: Some(connector_meta_data.mcc), - merchant_country_code: Some(connector_meta_data.merchant_country_code), - merchant_name: Some(connector_meta_data.merchant_name), + merchant_configuration_id: connector_meta_data.merchant_configuration_id, + mcc: connector_meta_data.mcc, + merchant_country_code: connector_meta_data.merchant_country_code, + merchant_name: connector_meta_data.merchant_name, notification_url: request.return_url.clone(), - three_ds_requestor_id: Some(connector_meta_data.three_ds_requestor_id), - three_ds_requestor_name: Some(connector_meta_data.three_ds_requestor_name), + three_ds_requestor_id: connector_meta_data.three_ds_requestor_id, + three_ds_requestor_name: connector_meta_data.three_ds_requestor_name, white_list_status: None, trust_list_status: None, seller_info: None, diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index e70813216d2..1e8b745107d 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -620,8 +620,14 @@ impl TryFrom<(&domain::payments::Card, &types::PaymentsAuthorizeData)> for Payme cavv: Some(auth_data.cavv.clone()), eci: auth_data.eci.clone(), cardholder_auth: None, - three_ds_version: Some(auth_data.message_version.to_string()), - directory_server_id: Some(auth_data.threeds_server_transaction_id.clone().into()), + three_ds_version: auth_data + .message_version + .clone() + .map(|version| version.to_string()), + directory_server_id: auth_data + .threeds_server_transaction_id + .clone() + .map(Secret::new), }; Ok(Self::CardThreeDs(Box::new(card_3ds_details))) diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 66e20e54616..a971bd96741 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -1520,15 +1520,13 @@ impl services::ConnectorIntegration { - RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( - req, - refund_amount, - ))?)) - } Some(SplitRefundsRequest::StripeSplitRefund(_)) => RequestContent::FormUrlEncoded( Box::new(stripe::ChargeRefundRequest::try_from(req)?), ), + _ => RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( + req, + refund_amount, + ))?)), }; Ok(request_body) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 7611f375c09..cdcf7b4c7b2 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1973,9 +1973,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }; (charges, None) } - Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) | None => { - (None, item.connector_customer.to_owned().map(Secret::new)) - } + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) + | Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) + | None => (None, item.connector_customer.to_owned().map(Secret::new)), }; Ok(Self { @@ -3092,11 +3092,9 @@ impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { }, }) } - types::SplitRefundsRequest::AdyenSplitRefund(_) => { - Err(errors::ConnectorError::MissingRequiredField { - field_name: "stripe_split_refund", - })? - } + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "stripe_split_refund", + })?, }, } } diff --git a/crates/router/src/connector/threedsecureio/transformers.rs b/crates/router/src/connector/threedsecureio/transformers.rs index 140cef0fbce..c66a0aa0b67 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -4,6 +4,7 @@ use api_models::payments::{DeviceChannel, ThreeDsCompletionIndicator}; use base64::Engine; use common_utils::date_time; use error_stack::ResultExt; +use hyperswitch_connectors::utils::AddressDetailsData; use iso_currency::Currency; use isocountry; use masking::{ExposeInterface, Secret}; @@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, to_string}; use crate::{ - connector::utils::{get_card_details, to_connector_meta, AddressDetailsData, CardData}, + connector::utils::{get_card_details, to_connector_meta, CardData}, consts::{BASE64_ENGINE, NO_ERROR_MESSAGE}, core::errors, types::{ diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 0aca263b5c7..2d7e3cc6d52 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1857,8 +1857,6 @@ pub trait AddressDetailsData { fn get_zip(&self) -> Result<&Secret, Error>; fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>; fn get_combined_address_line(&self) -> Result, Error>; - fn to_state_code(&self) -> Result, Error>; - fn to_state_code_as_optional(&self) -> Result>, Error>; fn get_optional_line2(&self) -> Option>; fn get_optional_country(&self) -> Option; } @@ -1931,31 +1929,6 @@ impl AddressDetailsData for hyperswitch_domain_models::address::AddressDetails { self.get_line2()?.peek() ))) } - fn to_state_code(&self) -> Result, Error> { - let country = self.get_country()?; - let state = self.get_state()?; - match country { - api_models::enums::CountryAlpha2::US => Ok(Secret::new( - UsStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), - )), - api_models::enums::CountryAlpha2::CA => Ok(Secret::new( - CanadaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), - )), - _ => Ok(state.clone()), - } - } - fn to_state_code_as_optional(&self) -> Result>, Error> { - self.state - .as_ref() - .map(|state| { - if state.peek().len() == 2 { - Ok(state.to_owned()) - } else { - self.to_state_code() - } - }) - .transpose() - } fn get_optional_line2(&self) -> Option> { self.line2.clone() diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index c22cecc20f9..18b3ad14358 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -18,6 +18,7 @@ pub mod customers; pub mod disputes; pub mod encryption; pub mod errors; +pub mod external_service_auth; pub mod files; #[cfg(feature = "frm")] pub mod fraud_check; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 958dbf74382..98b55cd9823 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1317,6 +1317,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { coinbase::transformers::CoinbaseConnectorMeta::try_from(self.connector_meta_data)?; Ok(()) } + api_enums::Connector::Coingate => { + coingate::transformers::CoingateAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Cryptopay => { cryptopay::transformers::CryptopayAuthType::try_from(self.auth_type)?; Ok(()) @@ -1429,6 +1433,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { mollie::transformers::MollieAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Moneris => { + moneris::transformers::MonerisAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Multisafepay => { multisafepay::transformers::MultisafepayAuthType::try_from(self.auth_type)?; Ok(()) @@ -3706,6 +3714,7 @@ impl ProfileCreateBridge for api::ProfileCreate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: self.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), + always_request_extended_authorization: self.always_request_extended_authorization, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, })) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 3dbaa999219..d06ffea581f 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -445,3 +445,20 @@ pub enum NetworkTokenizationError { #[error("Failed while calling Network Token Service API")] ApiError, } + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +#[derive(Debug, thiserror::Error)] +pub enum RevenueRecoveryError { + #[error("Failed to fetch payment intent")] + PaymentIntentFetchFailed, + #[error("Failed to fetch payment attempt")] + PaymentAttemptFetchFailed, + #[error("Failed to get revenue recovery invoice webhook")] + InvoiceWebhookProcessingFailed, + #[error("Failed to get revenue recovery invoice transaction")] + TransactionWebhookProcessingFailed, + #[error("Failed to create payment intent")] + PaymentIntentCreateFailed, + #[error("Source verification failed for billing connector")] + WebhookAuthenticationFailed, +} diff --git a/crates/router/src/core/external_service_auth.rs b/crates/router/src/core/external_service_auth.rs new file mode 100644 index 00000000000..92c8f5b249d --- /dev/null +++ b/crates/router/src/core/external_service_auth.rs @@ -0,0 +1,94 @@ +use api_models::external_service_auth as external_service_auth_api; +use common_utils::fp_utils; +use error_stack::ResultExt; +use masking::ExposeInterface; + +use crate::{ + core::errors::{self, RouterResponse}, + services::{ + api as service_api, + authentication::{self, ExternalServiceType, ExternalToken}, + }, + SessionState, +}; + +pub async fn generate_external_token( + state: SessionState, + user: authentication::UserFromToken, + external_service_type: ExternalServiceType, +) -> RouterResponse { + let token = ExternalToken::new_token( + user.user_id.clone(), + user.merchant_id.clone(), + &state.conf, + external_service_type.clone(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed to create external token for params [user_id, mid, external_service_type] [{}, {:?}, {:?}]", + user.user_id, user.merchant_id, external_service_type, + ) + })?; + + Ok(service_api::ApplicationResponse::Json( + external_service_auth_api::ExternalTokenResponse { + token: token.into(), + }, + )) +} + +pub async fn signout_external_token( + state: SessionState, + json_payload: external_service_auth_api::ExternalSignoutTokenRequest, +) -> RouterResponse<()> { + let token = authentication::decode_jwt::(&json_payload.token.expose(), &state) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + authentication::blacklist::insert_user_in_blacklist(&state, &token.user_id) + .await + .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; + + Ok(service_api::ApplicationResponse::StatusOk) +} + +pub async fn verify_external_token( + state: SessionState, + json_payload: external_service_auth_api::ExternalVerifyTokenRequest, + external_service_type: ExternalServiceType, +) -> RouterResponse { + let token_from_payload = json_payload.token.expose(); + + let token = authentication::decode_jwt::(&token_from_payload, &state) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + fp_utils::when( + authentication::blacklist::check_user_in_blacklist(&state, &token.user_id, token.exp) + .await?, + || Err(errors::ApiErrorResponse::InvalidJwtToken), + )?; + + token.check_service_type(&external_service_type)?; + + let user_in_db = state + .global_store + .find_user_by_id(&token.user_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("User not found in database")?; + + let email = user_in_db.email.clone(); + let name = user_in_db.name; + + Ok(service_api::ApplicationResponse::Json( + external_service_auth_api::ExternalVerifyTokenResponse::Hypersense { + user_id: user_in_db.user_id, + merchant_id: token.merchant_id, + name, + email, + }, + )) +} diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 477803c92a2..ba63319aabb 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -130,6 +130,8 @@ pub async fn form_payment_link_data( details_layout: None, branding_visibility: None, payment_button_text: None, + custom_message_for_card_terms: None, + payment_button_colour: None, } }; @@ -276,6 +278,8 @@ pub async fn form_payment_link_data( details_layout: payment_link_config.details_layout, branding_visibility: payment_link_config.branding_visibility, payment_button_text: payment_link_config.payment_button_text.clone(), + custom_message_for_card_terms: payment_link_config.custom_message_for_card_terms.clone(), + payment_button_colour: payment_link_config.payment_button_colour.clone(), }; Ok(( @@ -327,6 +331,8 @@ pub async fn initiate_secure_payment_link_flow( show_card_form_by_default: payment_link_config.show_card_form_by_default, payment_link_details: *link_details.to_owned(), payment_button_text: payment_link_config.payment_button_text, + custom_message_for_card_terms: payment_link_config.custom_message_for_card_terms, + payment_button_colour: payment_link_config.payment_button_colour, }; let js_script = format!( "window.__PAYMENT_DETAILS = {}", @@ -625,6 +631,24 @@ pub fn get_payment_link_config_based_on_priority( (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD), (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM) ); + + let ( + details_layout, + background_image, + payment_button_text, + custom_message_for_card_terms, + payment_button_colour, + ) = get_payment_link_config_value!( + payment_create_link_config, + business_theme_configs, + (details_layout), + (background_image, |background_image| background_image + .foreign_into()), + (payment_button_text), + (custom_message_for_card_terms), + (payment_button_colour), + ); + let payment_link_config = PaymentLinkConfig { theme, @@ -640,41 +664,11 @@ pub fn get_payment_link_config_based_on_priority( transaction_details: payment_create_link_config.as_ref().and_then( |payment_link_config| payment_link_config.theme_config.transaction_details.clone(), ), - details_layout: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| payment_link_config.theme_config.details_layout) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| business_theme_config.details_layout) - }), - background_image: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| { - payment_link_config.theme_config.background_image.clone() - }) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| { - business_theme_config - .background_image - .as_ref() - .map(|background_image| background_image.clone().foreign_into()) - }) - }), - payment_button_text: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| { - payment_link_config.theme_config.payment_button_text.clone() - }) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| { - business_theme_config.payment_button_text.clone() - }) - }), + details_layout, + background_image, + payment_button_text, + custom_message_for_card_terms, + payment_button_colour, }; Ok((payment_link_config, domain_name)) @@ -778,6 +772,8 @@ pub async fn get_payment_link_status( details_layout: None, branding_visibility: None, payment_button_text: None, + custom_message_for_card_terms: None, + payment_button_colour: None, } }; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css index b8ccdeba33a..7b347306809 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css @@ -640,7 +640,6 @@ body { margin-top: 20px; width: 100%; height: 38px; - background-color: var(--primary-color); border: 0; border-radius: 4px; font-size: 18px; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js index 378900ba9ae..a73067a5bce 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js @@ -308,6 +308,7 @@ function initializeEventListeners(paymentDetails) { if (submitButtonNode instanceof HTMLButtonElement) { submitButtonNode.style.color = contrastBWColor; + submitButtonNode.style.backgroundColor = paymentDetails.payment_button_colour || primaryColor; } if (hyperCheckoutCartImageNode instanceof HTMLDivElement) { diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index 2d2b2c30ae2..6abe5f7d5b2 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -64,6 +64,7 @@ function initializeSDK() { }, showCardFormByDefault: paymentDetails.show_card_form_by_default, hideCardNicknameField: hideCardNicknameField, + customMessageForCardTerms: paymentDetails.custom_message_for_card_terms, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 9ae64ec3133..a8a00e4de9d 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -87,6 +87,7 @@ if (!isFramed) { }, hideCardNicknameField: hideCardNicknameField, showCardFormByDefault: paymentDetails.show_card_form_by_default, + customMessageForCardTerms: paymentDetails.custom_message_for_card_terms, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 6ebc7bde5e4..237a097b792 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -921,6 +921,7 @@ pub async fn create_payment_method( merchant_account, key_store, None, + &customer_id, ) .await; @@ -1513,14 +1514,16 @@ pub async fn vault_payment_method( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, existing_vault_id: Option, + customer_id: &id_type::GlobalCustomerId, ) -> RouterResult<(pm_types::AddVaultResponse, String)> { let db = &*state.store; // get fingerprint_id from vault - let fingerprint_id_from_vault = vault::get_fingerprint_id_from_vault(state, pmd) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get fingerprint_id from vault")?; + let fingerprint_id_from_vault = + vault::get_fingerprint_id_from_vault(state, pmd, customer_id.get_string_repr().to_owned()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get fingerprint_id from vault")?; // throw back error if payment method is duplicated when( @@ -1796,6 +1799,7 @@ pub async fn update_payment_method_core( &merchant_account, &key_store, current_vault_id, // using current vault_id for now, will have to refactor this + &payment_method.customer_id, ) // to generate new one on each vaulting later on .await .attach_printable("Failed to add payment method in vault")?; diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 300f1e3054f..93461c63152 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1257,8 +1257,8 @@ pub async fn get_fingerprint_id_from_vault< >( state: &routes::SessionState, data: &D, + key: String, ) -> CustomResult { - let key = data.get_vaulting_data_key(); let data = serde_json::to_string(data) .change_context(errors::VaultError::RequestEncodingFailed) .attach_printable("Failed to encode Vaulting data to string")?; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c6dcda0af70..4269398246a 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -388,9 +388,12 @@ where should_continue_capture, ); - if helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), state) - .await? - { + let is_eligible_for_uas = + helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), state) + .await?; + + if is_eligible_for_uas { + let should_do_uas_confirmation_call = false; operation .to_domain()? .call_unified_authentication_service_if_eligible( @@ -401,6 +404,7 @@ where &business_profile, &key_store, mandate_type, + &should_do_uas_confirmation_call, ) .await?; } else { @@ -459,7 +463,7 @@ where _ => (), }; payment_data = match connector_details { - ConnectorCallType::PreDetermined(connector) => { + ConnectorCallType::PreDetermined(ref connector) => { #[cfg(all(feature = "dynamic_routing", feature = "v1"))] let routable_connectors = convert_connector_data_to_routable_connectors(&[connector.clone()]) @@ -500,6 +504,23 @@ where ) .await?; + if is_eligible_for_uas { + let should_do_uas_confirmation_call = true; + operation + .to_domain()? + .call_unified_authentication_service_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + mandate_type, + &should_do_uas_confirmation_call, + ) + .await?; + } + let op_ref = &operation; let should_trigger_post_processing_flows = is_operation_confirm(&operation); @@ -545,7 +566,7 @@ where &key_store, &customer, &mca, - &connector, + connector, &mut payment_data, op_ref, Some(header_payload.clone()), @@ -556,14 +577,14 @@ where payment_data } - ConnectorCallType::Retryable(connectors) => { + ConnectorCallType::Retryable(ref connectors) => { #[cfg(all(feature = "dynamic_routing", feature = "v1"))] let routable_connectors = - convert_connector_data_to_routable_connectors(&connectors) + convert_connector_data_to_routable_connectors(connectors) .map_err(|e| logger::error!(routable_connector_error=?e)) .unwrap_or_default(); - let mut connectors = connectors.into_iter(); + let mut connectors = connectors.clone().into_iter(); let connector_data = get_connector_data(&mut connectors)?; @@ -641,6 +662,23 @@ where let op_ref = &operation; let should_trigger_post_processing_flows = is_operation_confirm(&operation); + if is_eligible_for_uas { + let should_do_uas_confirmation_call = true; + operation + .to_domain()? + .call_unified_authentication_service_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + mandate_type, + &should_do_uas_confirmation_call, + ) + .await?; + } + let operation = Box::new(PaymentResponse); connector_http_status_code = router_data.connector_http_status_code; external_latency = router_data.external_latency; @@ -1933,7 +1971,7 @@ pub(crate) async fn payments_create_and_confirm_intent( #[cfg(feature = "v2")] #[inline] -fn handle_payments_intent_response( +pub fn handle_payments_intent_response( response: hyperswitch_domain_models::api::ApplicationResponse, ) -> CustomResult { match response { @@ -4461,6 +4499,17 @@ where { router_data = router_data.preprocessing_steps(state, connector).await?; (router_data, should_continue_payment) + } else if connector.connector_name == router_types::Connector::Xendit { + match payment_data.get_payment_intent().split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + router_data = router_data.preprocessing_steps(state, connector).await?; + let is_error_in_response = router_data.response.is_err(); + (router_data, !is_error_in_response) + } + _ => (router_data, should_continue_payment), + } } else { (router_data, should_continue_payment) } @@ -5511,6 +5560,82 @@ pub async fn list_payments( )) } +#[cfg(all(feature = "v2", feature = "olap"))] +pub async fn list_payments( + state: SessionState, + merchant: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + constraints: api::PaymentListConstraints, +) -> RouterResponse { + common_utils::metrics::utils::record_operation_time( + async { + let limit = &constraints.limit; + helpers::validate_payment_list_request_for_joins(*limit)?; + let db: &dyn StorageInterface = state.store.as_ref(); + let fetch_constraints = constraints.clone().into(); + let list: Vec<(storage::PaymentIntent, Option)> = db + .get_filtered_payment_intents_attempt( + &(&state).into(), + merchant.get_id(), + &fetch_constraints, + &key_store, + merchant.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let data: Vec = + list.into_iter().map(ForeignFrom::foreign_from).collect(); + + let active_attempt_ids = db + .get_filtered_active_attempt_ids_for_total_count( + merchant.get_id(), + &fetch_constraints, + merchant.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while retrieving active_attempt_ids for merchant")?; + + let total_count = if constraints.has_no_attempt_filters() { + i64::try_from(active_attempt_ids.len()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while converting from usize to i64") + } else { + let active_attempt_ids = active_attempt_ids + .into_iter() + .flatten() + .collect::>(); + + db.get_total_count_of_filtered_payment_attempts( + merchant.get_id(), + &active_attempt_ids, + constraints.connector, + constraints.payment_method_type, + constraints.payment_method_subtype, + constraints.authentication_type, + constraints.merchant_connector_id, + constraints.card_network, + merchant.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while retrieving total count of payment attempts") + }?; + + Ok(services::ApplicationResponse::Json( + api_models::payments::PaymentListResponse { + count: data.len(), + total_count, + data, + }, + )) + }, + &metrics::PAYMENT_LIST_LATENCY, + router_env::metric_attributes!(("merchant_id", merchant.get_id().clone())), + ) + .await +} + #[cfg(all(feature = "olap", feature = "v1"))] pub async fn apply_filters_on_payments( state: SessionState, diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 03006706e25..1219363b212 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1,9 +1,9 @@ use hyperswitch_domain_models::router_flow_types::{ - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }; use hyperswitch_interfaces::api::{ - UasAuthenticationV2, UasPostAuthenticationV2, UasPreAuthenticationV2, - UnifiedAuthenticationServiceV2, + UasAuthenticationConfirmationV2, UasAuthenticationV2, UasPostAuthenticationV2, + UasPreAuthenticationV2, UnifiedAuthenticationServiceV2, }; #[cfg(feature = "frm")] @@ -1907,6 +1907,7 @@ macro_rules! default_imp_for_new_connector_integration_uas { $( impl UnifiedAuthenticationServiceV2 for $path::$connector {} impl UasPreAuthenticationV2 for $path::$connector {} impl UasPostAuthenticationV2 for $path::$connector {} + impl UasAuthenticationConfirmationV2 for $path::$connector {} impl UasAuthenticationV2 for $path::$connector {} impl services::ConnectorIntegrationV2< @@ -1926,6 +1927,13 @@ macro_rules! default_imp_for_new_connector_integration_uas { {} impl services::ConnectorIntegrationV2< + AuthenticationConfirmation, + types::UasFlowData, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData, + > for $path::$connector + {} + impl services::ConnectorIntegrationV2< Authenticate, types::UasFlowData, types::UasAuthenticationRequestData, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 1aa5ed66294..f55c1f58f60 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -14,12 +14,14 @@ pub mod setup_mandate_flow; use async_trait::async_trait; use hyperswitch_domain_models::{ mandates::CustomerAcceptance, - router_flow_types::{Authenticate, PostAuthenticate, PreAuthenticate}, + router_flow_types::{ + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, + }, router_request_types::PaymentsCaptureData, }; use hyperswitch_interfaces::api::{ - payouts::Payouts, UasAuthentication, UasPostAuthentication, UasPreAuthentication, - UnifiedAuthenticationService, + payouts::Payouts, UasAuthentication, UasAuthenticationConfirmation, UasPostAuthentication, + UasPreAuthentication, UnifiedAuthenticationService, }; #[cfg(feature = "frm")] @@ -2276,6 +2278,57 @@ default_imp_for_uas_post_authentication!( connector::Wise ); +macro_rules! default_imp_for_uas_authentication_confirmation { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthenticationConfirmation for $path::$connector {} + impl + services::ConnectorIntegration< + AuthenticationConfirmation, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_authentication_confirmation!( + connector::Adyenplatform, + connector::Adyen, + connector::Authorizedotnet, + connector::Checkout, + connector::Ebanx, + connector::Gpayments, + connector::Netcetera, + connector::Nmi, + connector::Noon, + connector::Opayo, + connector::Opennode, + connector::Payme, + connector::Payone, + connector::Paypal, + connector::Plaid, + connector::Riskified, + connector::Signifyd, + connector::Stripe, + connector::Threedsecureio, + connector::Trustpay, + connector::Wellsfargopayout, + connector::Wise +); + +#[cfg(feature = "dummy_connector")] +impl UasAuthenticationConfirmation for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + AuthenticationConfirmation, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData, + > for connector::DummyConnector +{ +} + macro_rules! default_imp_for_uas_authentication { ($($path:ident::$connector:ident),*) => { $( impl UasAuthentication for $path::$connector {} @@ -2289,6 +2342,7 @@ macro_rules! default_imp_for_uas_authentication { )* }; } + #[cfg(feature = "dummy_connector")] impl UasAuthentication for connector::DummyConnector {} #[cfg(feature = "dummy_connector")] @@ -2325,6 +2379,7 @@ default_imp_for_uas_authentication!( connector::Wellsfargopayout, connector::Wise ); + /// Determines whether a capture API call should be made for a payment attempt /// This function evaluates whether an authorized payment should proceed with a capture API call /// based on various payment parameters. It's primarily used in two-step (auth + capture) payment flows for CaptureMethod SequentialAutomatic diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 7efa9a5e180..b5104391ab8 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -626,6 +626,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: item.request.split_payments, }) } } diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 44bb9f2703a..6e8152716d0 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -318,6 +318,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: None, }) } } diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index f6e5d1e7f05..662453c4a3e 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -610,8 +610,11 @@ fn create_paze_session_token( } fn create_samsung_pay_session_token( + state: &routes::SessionState, router_data: &types::PaymentsSessionRouterData, header_payload: hyperswitch_domain_models::payments::HeaderPayload, + connector: &api::ConnectorData, + business_profile: &domain::Profile, ) -> RouterResult { let samsung_pay_session_token_data = router_data .connector_wallets_details @@ -657,6 +660,20 @@ fn create_samsung_pay_session_token( let formatted_payment_id = router_data.payment_id.replace("_", "-"); + let billing_address_required = is_billing_address_required_to_be_collected_from_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::SamsungPay, + ); + + let shipping_address_required = is_shipping_address_required_to_be_collected_form_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::SamsungPay, + ); + Ok(types::PaymentsSessionRouterData { response: Ok(types::PaymentsResponseData::SessionResponse { session_token: payment_types::SessionToken::SamsungPay(Box::new( @@ -677,6 +694,8 @@ fn create_samsung_pay_session_token( }, protocol: payment_types::SamsungPayProtocolType::Protocol3ds, allowed_brands: samsung_pay_wallet_details.allowed_brands, + billing_address_required, + shipping_address_required, }, )), }), @@ -684,6 +703,86 @@ fn create_samsung_pay_session_token( }) } +/// Function to determine whether the billing address is required to be collected from the wallet, +/// based on business profile settings, the payment method type, and the connector's required fields +/// for the specific payment method. +/// +/// If `always_collect_billing_details_from_wallet_connector` is enabled, it indicates that the +/// billing address is always required to be collected from the wallet. +/// +/// If only `collect_billing_details_from_wallet_connector` is enabled, the billing address will be +/// collected only if the connector required fields for the specific payment method type contain +/// the billing fields. +fn is_billing_address_required_to_be_collected_from_wallet( + state: &routes::SessionState, + connector: &api::ConnectorData, + business_profile: &domain::Profile, + payment_method_type: enums::PaymentMethodType, +) -> bool { + let always_collect_billing_details_from_wallet_connector = business_profile + .always_collect_billing_details_from_wallet_connector + .unwrap_or(false); + + if always_collect_billing_details_from_wallet_connector { + always_collect_billing_details_from_wallet_connector + } else if business_profile + .collect_billing_details_from_wallet_connector + .unwrap_or(false) + { + let billing_variants = enums::FieldType::get_billing_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + payment_method_type, + connector.connector_name, + billing_variants, + ) + } else { + false + } +} + +/// Function to determine whether the shipping address is required to be collected from the wallet, +/// based on business profile settings, the payment method type, and the connector required fields +/// for the specific payment method type. +/// +/// If `always_collect_shipping_details_from_wallet_connector` is enabled, it indicates that the +/// shipping address is always required to be collected from the wallet. +/// +/// If only `collect_shipping_details_from_wallet_connector` is enabled, the shipping address will be +/// collected only if the connector required fields for the specific payment method type contain +/// the shipping fields. +fn is_shipping_address_required_to_be_collected_form_wallet( + state: &routes::SessionState, + connector: &api::ConnectorData, + business_profile: &domain::Profile, + payment_method_type: enums::PaymentMethodType, +) -> bool { + let always_collect_shipping_details_from_wallet_connector = business_profile + .always_collect_shipping_details_from_wallet_connector + .unwrap_or(false); + + if always_collect_shipping_details_from_wallet_connector { + always_collect_shipping_details_from_wallet_connector + } else if business_profile + .collect_shipping_details_from_wallet_connector + .unwrap_or(false) + { + let shipping_variants = enums::FieldType::get_shipping_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + payment_method_type, + connector.connector_name, + shipping_variants, + ) + } else { + false + } +} + fn get_session_request_for_simplified_apple_pay( apple_pay_merchant_identifier: String, session_token_data: payment_types::SessionTokenForSimplifiedApplePay, @@ -865,27 +964,12 @@ fn create_gpay_session_token( ..router_data.clone() }) } else { - let always_collect_billing_details_from_wallet_connector = business_profile - .always_collect_billing_details_from_wallet_connector - .unwrap_or(false); - - let is_billing_details_required = if always_collect_billing_details_from_wallet_connector { - always_collect_billing_details_from_wallet_connector - } else if business_profile - .collect_billing_details_from_wallet_connector - .unwrap_or(false) - { - let billing_variants = enums::FieldType::get_billing_variants(); - is_dynamic_fields_required( - &state.conf.required_fields, - enums::PaymentMethod::Wallet, - enums::PaymentMethodType::GooglePay, - connector.connector_name, - billing_variants, - ) - } else { - false - }; + let is_billing_details_required = is_billing_address_required_to_be_collected_from_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::GooglePay, + ); let required_amount_type = StringMajorUnitForConnector; let google_pay_amount = required_amount_type @@ -904,29 +988,13 @@ fn create_gpay_session_token( total_price: google_pay_amount, }; - let always_collect_shipping_details_from_wallet_connector = business_profile - .always_collect_shipping_details_from_wallet_connector - .unwrap_or(false); - let required_shipping_contact_fields = - if always_collect_shipping_details_from_wallet_connector { - true - } else if business_profile - .collect_shipping_details_from_wallet_connector - .unwrap_or(false) - { - let shipping_variants = enums::FieldType::get_shipping_variants(); - - is_dynamic_fields_required( - &state.conf.required_fields, - enums::PaymentMethod::Wallet, - enums::PaymentMethodType::GooglePay, - connector.connector_name, - shipping_variants, - ) - } else { - false - }; + is_shipping_address_required_to_be_collected_form_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::GooglePay, + ); if connector_wallets_details.google_pay.is_some() { let gpay_data = router_data @@ -1199,9 +1267,13 @@ impl RouterDataSession for types::PaymentsSessionRouterData { api::GetToken::GpayMetadata => { create_gpay_session_token(state, self, connector, business_profile) } - api::GetToken::SamsungPayMetadata => { - create_samsung_pay_session_token(self, header_payload) - } + api::GetToken::SamsungPayMetadata => create_samsung_pay_session_token( + state, + self, + header_payload, + connector, + business_profile, + ), api::GetToken::ApplePayMetadata => { create_applepay_session_token( state, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 39bbef1f3d7..032262b30aa 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -591,10 +591,13 @@ pub async fn get_token_pm_type_mandate_details( mandate_generic_data.mandate_connector, mandate_generic_data.payment_method_info, ) - } else if request.payment_method_type - == Some(api_models::enums::PaymentMethodType::ApplePay) - || request.payment_method_type - == Some(api_models::enums::PaymentMethodType::GooglePay) + } else if request + .payment_method_type + .map(|payment_method_type_value| { + payment_method_type_value + .should_check_for_customer_saved_payment_method_type() + }) + .unwrap_or(false) { let payment_request_customer_id = request.get_customer_id(); if let Some(customer_id) = @@ -3613,6 +3616,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -3684,6 +3688,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -3753,6 +3758,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -4286,6 +4292,9 @@ impl AttemptType { organization_id: old_payment_attempt.organization_id, profile_id: old_payment_attempt.profile_id, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, } } @@ -6533,13 +6542,14 @@ pub fn validate_mandate_data_and_future_usage( pub enum UnifiedAuthenticationServiceFlow { ClickToPayInitiate, ExternalAuthenticationInitiate { - acquirer_details: authentication::types::AcquirerDetails, + acquirer_details: Option, card_number: ::cards::CardNumber, token: String, }, ExternalAuthenticationPostAuthenticate { authentication_id: String, }, + ClickToPayConfirmation, } #[cfg(feature = "v1")] @@ -6550,6 +6560,7 @@ pub async fn decide_action_for_unified_authentication_service( payment_data: &mut PaymentData, connector_call_type: &api::ConnectorCallType, mandate_type: Option, + do_authorisation_confirmation: &bool, ) -> RouterResult> { let external_authentication_flow = get_payment_external_authentication_flow_during_confirm( state, @@ -6580,7 +6591,9 @@ pub async fn decide_action_for_unified_authentication_service( ) } None => { - if let Some(payment_method) = payment_data.payment_attempt.payment_method { + if *do_authorisation_confirmation { + Some(UnifiedAuthenticationServiceFlow::ClickToPayConfirmation) + } else if let Some(payment_method) = payment_data.payment_attempt.payment_method { if payment_method == storage_enums::PaymentMethod::Card && business_profile.is_click_to_pay_enabled && payment_data.service_details.is_some() @@ -6590,6 +6603,11 @@ pub async fn decide_action_for_unified_authentication_service( None } } else { + logger::info!( + payment_method=?payment_data.payment_attempt.payment_method, + click_to_pay_enabled=?business_profile.is_click_to_pay_enabled, + "skipping unified authentication service call since payment conditions are not satisfied" + ); None } } @@ -6598,7 +6616,7 @@ pub async fn decide_action_for_unified_authentication_service( pub enum PaymentExternalAuthenticationFlow { PreAuthenticationFlow { - acquirer_details: authentication::types::AcquirerDetails, + acquirer_details: Option, card_number: ::cards::CardNumber, token: String, }, @@ -6675,17 +6693,27 @@ pub async fn get_payment_external_authentication_flow_during_confirm( connector_data.merchant_connector_id.as_ref(), ) .await?; - let acquirer_details: authentication::types::AcquirerDetails = payment_connector_mca + let acquirer_details = 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(), - })?; + .and_then(|metadata| { + 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(), + }) + .inspect_err(|err| { + logger::error!( + "Failed to parse acquirer details from Payment Connector's Metadata: {:?}", + err + ); + }) + .ok() + }); Some(PaymentExternalAuthenticationFlow::PreAuthenticationFlow { card_number, token, @@ -6978,6 +7006,70 @@ pub fn validate_platform_request_for_marketplace( Ok(()) })?; } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + xendit_split_payment, + )) => match xendit_split_payment { + common_types::payments::XenditSplitRequest::MultipleSplits( + xendit_multiple_split_payment, + ) => { + match amount { + api::Amount::Zero => { + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .iter() + .map(|route| { + route + .flat_amount + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64() + }) + .sum(); + + if total_split_amount != 0 { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: + "Sum of split amounts should be equal to the total amount", + }); + } + } + api::Amount::Value(amount) => { + let total_payment_amount: i64 = amount.into(); + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .into_iter() + .map(|route| { + if route.flat_amount.is_none() && route.percent_amount.is_none() { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount to be provided".to_string(), + }) + } else if route.flat_amount.is_some() && route.percent_amount.is_some(){ + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount, but not both".to_string(), + }) + } else { + Ok(route + .flat_amount + .map(|amount| amount.get_amount_as_i64()) + .or(route.percent_amount.map(|percentage| (percentage * total_payment_amount) / 100)) + .unwrap_or(0)) + } + }) + .collect::, _>>()? + .into_iter() + .sum(); + + if total_payment_amount < total_split_amount { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: + "The sum of split amounts should not exceed the total amount" + .to_string(), + }); + } + } + }; + } + common_types::payments::XenditSplitRequest::SingleSplit(_) => (), + }, None => (), } Ok(()) diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 4d494e75c9a..45a4df37000 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -321,9 +321,10 @@ pub trait Domain: Send + Sync { _payment_data: &mut D, _should_continue_confirm_transaction: &mut bool, _connector_call_type: &ConnectorCallType, - _merchant_account: &domain::Profile, + _business_profile: &domain::Profile, _key_store: &domain::MerchantKeyStore, _mandate_type: Option, + _do_authorization_confirmation: &bool, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 84ff9933875..43057855467 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -983,7 +983,7 @@ impl Domain> for card_number, token, business_profile, - Some(acquirer_details), + acquirer_details, Some(payment_data.payment_attempt.payment_id.clone()), payment_data.payment_attempt.organization_id.clone(), ) @@ -1050,6 +1050,7 @@ impl Domain> for business_profile: &domain::Profile, key_store: &domain::MerchantKeyStore, mandate_type: Option, + do_authorisation_confirmation: &bool, ) -> CustomResult<(), errors::ApiErrorResponse> { let unified_authentication_service_flow = helpers::decide_action_for_unified_authentication_service( @@ -1059,135 +1060,180 @@ impl Domain> for payment_data, connector_call_type, mandate_type, + do_authorisation_confirmation, ) .await?; + if let Some(unified_authentication_service_flow) = unified_authentication_service_flow { match unified_authentication_service_flow { - helpers::UnifiedAuthenticationServiceFlow::ClickToPayInitiate => { - let authentication_product_ids = business_profile - .authentication_product_ids - .clone() - .ok_or(errors::ApiErrorResponse::PreconditionFailed { - message: "authentication_product_ids is not configured in business profile" - .to_string(), - })?; - let click_to_pay_mca_id = authentication_product_ids - .get_click_to_pay_connector_account_id() - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "authentication_product_ids", - })?; - - let key_manager_state = &(state).into(); - let merchant_id = &business_profile.merchant_id; - - let connector_mca = state - .store - .find_by_merchant_connector_account_merchant_id_merchant_connector_id( - key_manager_state, - merchant_id, - &click_to_pay_mca_id, - key_store, - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::MerchantConnectorAccountNotFound { - id: click_to_pay_mca_id.get_string_repr().to_string(), - }, - )?; - - let authentication_id = - common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); - - let payment_method = payment_data.payment_attempt.payment_method.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method", - }, - )?; - - ClickToPay::pre_authentication( - state, - key_store, - business_profile, - payment_data, - &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), - &connector_mca.connector_name, - &authentication_id, - payment_method, - ) - .await?; - - payment_data.payment_attempt.authentication_id = Some(authentication_id.clone()); + helpers::UnifiedAuthenticationServiceFlow::ClickToPayInitiate => { + let authentication_product_ids = business_profile + .authentication_product_ids + .clone() + .ok_or(errors::ApiErrorResponse::PreconditionFailed { + message: "authentication_product_ids is not configured in business profile" + .to_string(), + })?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "authentication_product_ids", + })?; + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; + let authentication_id = + common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); + let payment_method = payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method", + }, + )?; + ClickToPay::pre_authentication( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + &authentication_id, + payment_method, + ) + .await?; - let response = ClickToPay::post_authentication( - state, - key_store, - business_profile, - payment_data, - &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), - &connector_mca.connector_name, - payment_method, - None, - ) - .await?; + payment_data.payment_attempt.authentication_id = Some(authentication_id.clone()); + let response = ClickToPay::post_authentication( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + payment_method, + None, + ) + .await?; + let (network_token, authentication_status) = match response.response.clone() { + Ok(unified_authentication_service::UasAuthenticationResponseData::PostAuthentication { + authentication_details, + }) => { + let token_details = authentication_details.token_details.ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Missing authentication_details.token_details")?; + (Some( + hyperswitch_domain_models::payment_method_data::NetworkTokenData { + token_number: token_details.payment_token, + token_exp_month: token_details + .token_expiration_month, + token_exp_year: token_details + .token_expiration_year, + token_cryptogram: authentication_details + .dynamic_data_details + .and_then(|data| data.dynamic_data_value), + card_issuer: None, + card_network: None, + card_type: None, + card_issuing_country: None, + bank_code: None, + nick_name: None, + eci: authentication_details.eci, + }),common_enums::AuthenticationStatus::Success) + }, - let (network_token, authentication_status) = match response.response.clone() { - Ok(unified_authentication_service::UasAuthenticationResponseData::PostAuthentication { - authentication_details, - }) => { - let token_details = authentication_details.token_details.ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Missing authentication_details.token_details")?; - (Some( - hyperswitch_domain_models::payment_method_data::NetworkTokenData { - token_number: token_details.payment_token, - token_exp_month: token_details - .token_expiration_month, - token_exp_year: token_details - .token_expiration_year, - token_cryptogram: authentication_details - .dynamic_data_details - .and_then(|data| data.dynamic_data_value), - card_issuer: None, - card_network: None, - card_type: None, - card_issuing_country: None, - bank_code: None, - nick_name: None, - eci: authentication_details.eci, - }),common_enums::AuthenticationStatus::Success) - }, - Ok(unified_authentication_service::UasAuthenticationResponseData::Authentication { .. }) - | Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication { .. }) - => Err(errors::ApiErrorResponse::InternalServerError).attach_printable("unexpected response received")?, - Err(_) => (None, common_enums::AuthenticationStatus::Failed), - }; + Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication { .. }) + | Ok(unified_authentication_service::UasAuthenticationResponseData::Confirmation {}) + | Ok(unified_authentication_service::UasAuthenticationResponseData::Authentication { .. }) => Err(errors::ApiErrorResponse::InternalServerError).attach_printable("unexpected response received from unified authentication service")?, + Err(_) => (None, common_enums::AuthenticationStatus::Failed) + }; + payment_data.payment_attempt.payment_method = + Some(common_enums::PaymentMethod::Card); - payment_data.payment_attempt.payment_method = - Some(common_enums::PaymentMethod::Card); + payment_data.payment_method_data = network_token + .clone() + .map(domain::PaymentMethodData::NetworkToken); - payment_data.payment_method_data = network_token + let authentication = uas_utils::create_new_authentication( + state, + payment_data.payment_attempt.merchant_id.clone(), + 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(), + &authentication_id, + payment_data.service_details.clone(), + authentication_status, + network_token, + payment_data.payment_attempt.organization_id.clone(), + ) + .await?; + payment_data.authentication = Some(authentication); + }, + helpers::UnifiedAuthenticationServiceFlow::ClickToPayConfirmation => { + let authentication_product_ids = business_profile + .authentication_product_ids .clone() - .map(domain::PaymentMethodData::NetworkToken); + .ok_or(errors::ApiErrorResponse::PreconditionFailed { + message: "authentication_product_ids is not configured in business profile" + .to_string(), + })?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "click_to_pay_mca_id", + })?; + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; + + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; - uas_utils::create_new_authentication( - state, - payment_data.payment_attempt.merchant_id.clone(), - 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(), - &authentication_id, - payment_data.service_details.clone(), - authentication_status, - payment_data.payment_attempt.organization_id.clone(), - ) - .await?; - }, - helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { - acquirer_details, - token, - .. - } => { - let (authentication_connector, three_ds_connector_account) = + let payment_method = payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method", + }, + )?; + + ClickToPay::confirmation( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + payment_method, + ) + .await? + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { + acquirer_details, + token, + .. + } => { + let (authentication_connector, three_ds_connector_account) = authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; let authentication_connector_name = authentication_connector.to_string(); let authentication = authentication::utils::create_new_authentication( @@ -1221,7 +1267,7 @@ impl Domain> for state, pre_auth_response, authentication.clone(), - Some(acquirer_details), + acquirer_details, ).await?; payment_data.authentication = Some(updated_authentication.clone()); @@ -1252,9 +1298,9 @@ impl Domain> for .attach_printable("Error while parsing PollConfig")?; payment_data.poll_config = Some(poll_config) } - }, - helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {authentication_id} => { - let (authentication_connector, three_ds_connector_account) = + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {authentication_id} => { + let (authentication_connector, three_ds_connector_account) = authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; let is_pull_mechanism_enabled = utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata( @@ -1301,8 +1347,9 @@ impl Domain> for *should_continue_confirm_transaction = false; } }, + } } - } + Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 1fd9f2404ff..1d3a56162e9 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1305,6 +1305,9 @@ impl PaymentCreate { organization_id: organization_id.clone(), profile_id, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, }, additional_pm_data, @@ -1513,6 +1516,7 @@ impl PaymentCreate { shipping_cost: request.shipping_cost, tax_details, skip_external_tax_calculation, + request_extended_authorization: None, psd2_sca_exemption_type: request.psd2_sca_exemption_type, platform_merchant_id: platform_merchant_account .map(|platform_merchant_account| platform_merchant_account.get_id().to_owned()), diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 6541480138a..22f79995342 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -1,7 +1,7 @@ use api_models::{ enums::FrmSuggestion, payments::{ - ConnectorMandateReferenceId, MandateIds, MandateReferenceId, ProxyPaymentsIntentRequest, + ConnectorMandateReferenceId, MandateIds, MandateReferenceId, ProxyPaymentsRequest, }, }; // use diesel_models::payment_attempt::ConnectorMandateReferenceId; @@ -64,66 +64,66 @@ impl ValidateStatusForOperation for PaymentProxyIntent { } } type BoxedConfirmOperation<'b, F> = - super::BoxedOperation<'b, F, ProxyPaymentsIntentRequest, PaymentConfirmData>; + super::BoxedOperation<'b, F, ProxyPaymentsRequest, PaymentConfirmData>; -impl Operation for &PaymentProxyIntent { +impl Operation for &PaymentProxyIntent { type Data = PaymentConfirmData; fn to_validate_request( &self, - ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Ok(*self) } fn to_get_tracker( &self, - ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(*self) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(*self) } fn to_update_tracker( &self, - ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(*self) } } #[automatically_derived] -impl Operation for PaymentProxyIntent { +impl Operation for PaymentProxyIntent { type Data = PaymentConfirmData; fn to_validate_request( &self, - ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Ok(self) } fn to_get_tracker( &self, - ) -> RouterResult<&(dyn GetTracker + Send + Sync)> + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(self) } - fn to_domain(&self) -> RouterResult<&dyn Domain> { + fn to_domain(&self) -> RouterResult<&dyn Domain> { Ok(self) } fn to_update_tracker( &self, - ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(self) } } -impl ValidateRequest> +impl ValidateRequest> for PaymentProxyIntent { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, - _request: &ProxyPaymentsIntentRequest, + _request: &ProxyPaymentsRequest, merchant_account: &'a domain::MerchantAccount, ) -> RouterResult { let validate_result = operations::ValidateResult { @@ -137,7 +137,7 @@ impl ValidateRequest GetTracker, ProxyPaymentsIntentRequest> +impl GetTracker, ProxyPaymentsRequest> for PaymentProxyIntent { #[instrument(skip_all)] @@ -145,7 +145,7 @@ impl GetTracker, ProxyPaymentsI &'a self, state: &'a SessionState, payment_id: &common_utils::id_type::GlobalPaymentId, - request: &ProxyPaymentsIntentRequest, + request: &ProxyPaymentsRequest, merchant_account: &domain::MerchantAccount, _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, @@ -260,7 +260,7 @@ impl GetTracker, ProxyPaymentsI } #[async_trait] -impl Domain> +impl Domain> for PaymentProxyIntent { async fn get_customer_details<'a>( @@ -320,7 +320,7 @@ impl Domain UpdateTracker, ProxyPaymentsIntentRequest> +impl UpdateTracker, ProxyPaymentsRequest> for PaymentProxyIntent { #[instrument(skip_all)] diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 1a0a7ad9e5d..d113cfd55de 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -653,6 +653,9 @@ pub fn make_new_payment_attempt( fingerprint_id: Default::default(), customer_acceptance: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: old_payment_attempt.card_discovery, } } diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index d46977d8c99..b4e4db3d161 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -655,9 +655,11 @@ where }, None => { let customer_saved_pm_option = if payment_method_type - == Some(api_models::enums::PaymentMethodType::ApplePay) - || payment_method_type - == Some(api_models::enums::PaymentMethodType::GooglePay) + .map(|payment_method_type_value| { + payment_method_type_value + .should_check_for_customer_saved_payment_method_type() + }) + .unwrap_or(false) { match state .store diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index ac1a69a89e8..8cbbafcbc5e 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -483,6 +483,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }; // TODO: evaluate the fields in router data, if they are required or not @@ -1624,7 +1625,7 @@ where let error = payment_attempt .error - .clone() + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from); let payment_address = self.payment_address; @@ -1721,8 +1722,7 @@ where let error = payment_attempt .error .clone() - .map(api_models::payments::ErrorDetails::foreign_from); - + .map(|from: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails| api_models::payments::ErrorDetails::foreign_from(&from)); let payment_address = self.payment_address; let payment_method_data = @@ -1800,6 +1800,7 @@ where let error = optional_payment_attempt .and_then(|payment_attempt| payment_attempt.error.clone()) + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from); let attempts = self.attempts.as_ref().map(|attempts| { attempts @@ -2605,6 +2606,8 @@ where order_tax_amount, connector_mandate_id, shipping_cost: payment_intent.shipping_cost, + capture_before: payment_attempt.capture_before, + extended_authorization_applied: payment_attempt.extended_authorization_applied, card_discovery: payment_attempt.card_discovery, }; @@ -2859,6 +2862,8 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay updated: None, split_payments: None, frm_metadata: None, + capture_before: pa.capture_before, + extended_authorization_applied: pa.extended_authorization_applied, order_tax_amount: None, connector_mandate_id:None, shipping_cost: None, @@ -2867,6 +2872,54 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay } } +#[cfg(feature = "v2")] +impl ForeignFrom<(storage::PaymentIntent, Option)> + for api_models::payments::PaymentsListResponseItem +{ + fn foreign_from((pi, pa): (storage::PaymentIntent, Option)) -> Self { + Self { + id: pi.id, + merchant_id: pi.merchant_id, + profile_id: pi.profile_id, + customer_id: pi.customer_id, + payment_method_id: pa.as_ref().and_then(|p| p.payment_method_id.clone()), + status: pi.status, + amount: api_models::payments::PaymentAmountDetailsResponse::foreign_from(( + &pi.amount_details, + pa.as_ref().map(|p| &p.amount_details), + )), + created: pi.created_at, + payment_method_type: pa.as_ref().and_then(|p| p.payment_method_type.into()), + payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype.into()), + connector: pa.as_ref().and_then(|p| p.connector.clone()), + merchant_connector_id: pa.as_ref().and_then(|p| p.merchant_connector_id.clone()), + customer: None, + merchant_reference_id: pi.merchant_reference_id, + connector_payment_id: pa.as_ref().and_then(|p| p.connector_payment_id.clone()), + connector_response_reference_id: pa + .as_ref() + .and_then(|p| p.connector_response_reference_id.clone()), + metadata: pi.metadata, + description: pi.description.map(|val| val.get_string_repr().to_string()), + authentication_type: pi.authentication_type, + capture_method: Some(pi.capture_method), + setup_future_usage: Some(pi.setup_future_usage), + attempt_count: pi.attempt_count, + error: pa + .as_ref() + .and_then(|p| p.error.as_ref()) + .map(api_models::payments::ErrorDetails::foreign_from), + cancellation_reason: pa.as_ref().and_then(|p| p.cancellation_reason.clone()), + order_details: None, + return_url: pi.return_url, + statement_descriptor: pi.statement_descriptor, + allowed_payment_method_types: pi.allowed_payment_method_types, + authorization_count: pi.authorization_count, + modified_at: pa.as_ref().map(|p| p.modified_at), + } + } +} + #[cfg(feature = "v1")] impl ForeignFrom for api::ephemeral_key::EphemeralKeyCreateResponse { fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self { @@ -3386,6 +3439,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }) } } @@ -3441,6 +3495,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info, metadata: payment_data.payment_intent.metadata, integrity_object: None, + split_payments: payment_data.payment_intent.split_payments, }) } } @@ -4159,6 +4214,7 @@ impl TryFrom> for types::PaymentsPreProce mandate_id: payment_data.mandate_id, related_transaction_id: None, enrolled_for_3ds: true, + split_payments: payment_data.payment_intent.split_payments, metadata: payment_data.payment_intent.metadata.map(Secret::new), }) } @@ -4286,7 +4342,7 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::PaymentA connector: attempt.connector.clone(), error: attempt .error - .clone() + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from), authentication_type: attempt.authentication_type, created_at: attempt.created_at, @@ -4298,10 +4354,16 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::PaymentA payment_method_type: attempt.payment_method_type, connector_reference_id: attempt.connector_response_reference_id.clone(), payment_method_subtype: attempt.get_payment_method_type(), - connector_payment_id: attempt.get_connector_payment_id().map(ToString::to_string), + connector_payment_id: attempt + .get_connector_payment_id() + .map(|str| common_utils::types::ConnectorTransactionId::from(str.to_owned())), payment_method_id: attempt.payment_method_id.clone(), client_source: attempt.client_source.clone(), client_version: attempt.client_version.clone(), + feature_metadata: attempt + .feature_metadata + .as_ref() + .map(api_models::payments::PaymentAttemptFeatureMetadata::foreign_from), } } } @@ -4326,29 +4388,39 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::AttemptA } #[cfg(feature = "v2")] -impl ForeignFrom +impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::ErrorDetails> for api_models::payments::ErrorDetails { fn foreign_from( - amount_details: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails, + error_details: &hyperswitch_domain_models::payments::payment_attempt::ErrorDetails, ) -> Self { - let hyperswitch_domain_models::payments::payment_attempt::ErrorDetails { - code, - message, - reason, - unified_code, - unified_message, - } = amount_details; - Self { - code, - message: reason.unwrap_or(message), - unified_code, - unified_message, + code: error_details.code.to_owned(), + message: error_details.message.to_owned(), + unified_code: error_details.unified_code.clone(), + unified_message: error_details.unified_message.clone(), } } } +#[cfg(feature = "v2")] +impl + ForeignFrom< + &hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptFeatureMetadata, + > for api_models::payments::PaymentAttemptFeatureMetadata +{ + fn foreign_from( + feature_metadata: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptFeatureMetadata, + ) -> Self { + let revenue_recovery = feature_metadata.revenue_recovery.as_ref().map(|recovery| { + api_models::payments::PaymentAttemptRevenueRecoveryData { + attempt_triggered_by: recovery.attempt_triggered_by, + } + }); + Self { revenue_recovery } + } +} + #[cfg(feature = "v2")] impl ForeignFrom for api_models::payments::AmountDetailsResponse @@ -4398,6 +4470,8 @@ impl ForeignFrom ) }), payment_button_text: config.payment_button_text, + custom_message_for_card_terms: config.custom_message_for_card_terms, + payment_button_colour: config.payment_button_colour, } } } @@ -4461,6 +4535,8 @@ impl ForeignFrom ) }), payment_button_text: config.payment_button_text, + custom_message_for_card_terms: config.custom_message_for_card_terms, + payment_button_colour: config.payment_button_colour, } } } diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 13f4de956ec..2d147db2447 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -370,20 +370,9 @@ impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { type Error = error_stack::Report; fn foreign_try_from(authentication: &storage::Authentication) -> Result { if authentication.authentication_status == common_enums::AuthenticationStatus::Success { - let threeds_server_transaction_id = authentication - .threeds_server_transaction_id - .clone() - .get_required_value("threeds_server_transaction_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("threeds_server_transaction_id must not be null when authentication_status is success")?; - let message_version = authentication - .message_version - .clone() - .get_required_value("message_version") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "message_version must not be null when authentication_status is success", - )?; + let threeds_server_transaction_id = + authentication.threeds_server_transaction_id.clone(); + let message_version = authentication.message_version.clone(); let cavv = authentication .cavv .clone() diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 90c4a7c4434..573543dfbf7 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -252,3 +252,32 @@ pub fn validate_adyen_charge_refund( } Ok(()) } +pub fn validate_xendit_charge_refund( + xendit_split_payment_response: &common_types::payments::XenditChargeResponseData, + xendit_split_refund_request: &common_types::domain::XenditSplitSubMerchantData, +) -> RouterResult> { + match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id + != Some(xendit_split_refund_request.for_user_id.clone()) + { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + common_types::payments::XenditChargeResponseData::SingleSplit( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id != xendit_split_refund_request.for_user_id { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + } +} diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 6a21dddc066..b8a7ee5dc1c 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -727,225 +727,231 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { - let success_based_algo_ref = dynamic_routing_algo_ref - .success_based_algorithm - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; - - if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { - let client = state - .grpc_client - .dynamic_routing - .success_rate_client - .as_ref() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_rate gRPC client not found".to_string(), - })?; - - let payment_connector = &payment_attempt.connector.clone().ok_or( - errors::ApiErrorResponse::GenericNotFoundError { - message: "unable to derive payment connector from payment attempt".to_string(), - }, - )?; - - let success_based_routing_configs = - fetch_dynamic_routing_configs::( + if let Some(success_based_algo_ref) = dynamic_routing_algo_ref.success_based_algorithm { + if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate gRPC client not found".to_string(), + })?; + + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; + + let success_based_routing_configs = fetch_dynamic_routing_configs::< + routing_types::SuccessBasedRoutingConfig, + >( state, profile_id, success_based_algo_ref .algorithm_id_with_timestamp .algorithm_id - .ok_or(errors::ApiErrorResponse::InternalServerError) + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate algorithm_id not found".to_string(), + }) .attach_printable( "success_based_routing_algorithm_id not found in business_profile", )?, ) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate based dynamic routing configs not found".to_string(), + }) .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; - let success_based_routing_config_params = dynamic_routing_config_params_interpolator - .get_string_val( - success_based_routing_configs - .params - .as_ref() - .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError) - .change_context(errors::ApiErrorResponse::InternalServerError)?, - ); + let success_based_routing_config_params = dynamic_routing_config_params_interpolator + .get_string_val( + success_based_routing_configs + .params + .as_ref() + .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError) + .change_context(errors::ApiErrorResponse::InternalServerError)?, + ); - let success_based_connectors = client - .calculate_entity_and_global_success_rate( - profile_id.get_string_repr().into(), - success_based_routing_configs.clone(), - success_based_routing_config_params.clone(), - routable_connectors.clone(), - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to calculate/fetch success rate from dynamic routing service", - )?; + let success_based_connectors = client + .calculate_entity_and_global_success_rate( + profile_id.get_string_repr().into(), + success_based_routing_configs.clone(), + success_based_routing_config_params.clone(), + routable_connectors.clone(), + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to calculate/fetch success rate from dynamic routing service", + )?; - let payment_status_attribute = - get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + let payment_status_attribute = + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); - let first_merchant_success_based_connector = &success_based_connectors - .entity_scores_with_labels - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first connector from list of connectors obtained from dynamic routing service", - )?; + let first_merchant_success_based_connector = &success_based_connectors + .entity_scores_with_labels + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first connector from list of connectors obtained from dynamic routing service", + )?; - let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label - .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!( - "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", - first_merchant_success_based_connector.label - ))?; - - let first_global_success_based_connector = &success_based_connectors - .global_scores_with_labels - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", - )?; + let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", + first_merchant_success_based_connector.label + ))?; + + let first_global_success_based_connector = &success_based_connectors + .global_scores_with_labels + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", + )?; - let outcome = get_dynamic_routing_based_metrics_outcome_for_payment( - payment_status_attribute, - payment_connector.to_string(), - first_merchant_success_based_connector_label.to_string(), - ); - - let dynamic_routing_stats = DynamicRoutingStatsNew { - payment_id: payment_attempt.payment_id.to_owned(), - attempt_id: payment_attempt.attempt_id.clone(), - merchant_id: payment_attempt.merchant_id.to_owned(), - profile_id: payment_attempt.profile_id.to_owned(), - amount: payment_attempt.get_total_amount(), - success_based_routing_connector: first_merchant_success_based_connector_label - .to_string(), - payment_connector: payment_connector.to_string(), - payment_method_type: payment_attempt.payment_method_type, - currency: payment_attempt.currency, - payment_method: payment_attempt.payment_method, - capture_method: payment_attempt.capture_method, - authentication_type: payment_attempt.authentication_type, - payment_status: payment_attempt.status, - conclusive_classification: outcome, - created_at: common_utils::date_time::now(), - global_success_based_connector: Some( - first_global_success_based_connector.label.to_string(), - ), - }; + let outcome = get_dynamic_routing_based_metrics_outcome_for_payment( + payment_status_attribute, + payment_connector.to_string(), + first_merchant_success_based_connector_label.to_string(), + ); - core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( - 1, - router_env::metric_attributes!( - ( - "tenant", - state.tenant.tenant_id.get_string_repr().to_owned(), - ), - ( - "merchant_profile_id", - format!( - "{}:{}", - payment_attempt.merchant_id.get_string_repr(), - payment_attempt.profile_id.get_string_repr() - ), - ), - ( - "merchant_specific_success_based_routing_connector", - first_merchant_success_based_connector_label.to_string(), - ), - ( - "merchant_specific_success_based_routing_connector_score", - first_merchant_success_based_connector.score.to_string(), - ), - ( - "global_success_based_routing_connector", + let dynamic_routing_stats = DynamicRoutingStatsNew { + payment_id: payment_attempt.payment_id.to_owned(), + attempt_id: payment_attempt.attempt_id.clone(), + merchant_id: payment_attempt.merchant_id.to_owned(), + profile_id: payment_attempt.profile_id.to_owned(), + amount: payment_attempt.get_total_amount(), + success_based_routing_connector: first_merchant_success_based_connector_label + .to_string(), + payment_connector: payment_connector.to_string(), + payment_method_type: payment_attempt.payment_method_type, + currency: payment_attempt.currency, + payment_method: payment_attempt.payment_method, + capture_method: payment_attempt.capture_method, + authentication_type: payment_attempt.authentication_type, + payment_status: payment_attempt.status, + conclusive_classification: outcome, + created_at: common_utils::date_time::now(), + global_success_based_connector: Some( first_global_success_based_connector.label.to_string(), ), - ( - "global_success_based_routing_connector_score", - first_global_success_based_connector.score.to_string(), - ), - ("payment_connector", payment_connector.to_string()), - ( - "currency", - payment_attempt - .currency - .map_or_else(|| "None".to_string(), |currency| currency.to_string()), - ), - ( - "payment_method", - payment_attempt.payment_method.map_or_else( - || "None".to_string(), - |payment_method| payment_method.to_string(), + }; + + core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( + 1, + router_env::metric_attributes!( + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), ), - ), - ( - "payment_method_type", - payment_attempt.payment_method_type.map_or_else( - || "None".to_string(), - |payment_method_type| payment_method_type.to_string(), + ( + "merchant_profile_id", + format!( + "{}:{}", + payment_attempt.merchant_id.get_string_repr(), + payment_attempt.profile_id.get_string_repr() + ), ), - ), - ( - "capture_method", - payment_attempt.capture_method.map_or_else( - || "None".to_string(), - |capture_method| capture_method.to_string(), + ( + "merchant_specific_success_based_routing_connector", + first_merchant_success_based_connector_label.to_string(), ), - ), - ( - "authentication_type", - payment_attempt.authentication_type.map_or_else( - || "None".to_string(), - |authentication_type| authentication_type.to_string(), + ( + "merchant_specific_success_based_routing_connector_score", + first_merchant_success_based_connector.score.to_string(), + ), + ( + "global_success_based_routing_connector", + first_global_success_based_connector.label.to_string(), + ), + ( + "global_success_based_routing_connector_score", + first_global_success_based_connector.score.to_string(), + ), + ("payment_connector", payment_connector.to_string()), + ( + "currency", + payment_attempt + .currency + .map_or_else(|| "None".to_string(), |currency| currency.to_string()), + ), + ( + "payment_method", + payment_attempt.payment_method.map_or_else( + || "None".to_string(), + |payment_method| payment_method.to_string(), + ), + ), + ( + "payment_method_type", + payment_attempt.payment_method_type.map_or_else( + || "None".to_string(), + |payment_method_type| payment_method_type.to_string(), + ), + ), + ( + "capture_method", + payment_attempt.capture_method.map_or_else( + || "None".to_string(), + |capture_method| capture_method.to_string(), + ), ), + ( + "authentication_type", + payment_attempt.authentication_type.map_or_else( + || "None".to_string(), + |authentication_type| authentication_type.to_string(), + ), + ), + ("payment_status", payment_attempt.status.to_string()), + ("conclusive_classification", outcome.to_string()), ), - ("payment_status", payment_attempt.status.to_string()), - ("conclusive_classification", outcome.to_string()), - ), - ); - logger::debug!("successfully pushed success_based_routing metrics"); - - state - .store - .insert_dynamic_routing_stat_entry(dynamic_routing_stats) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to push dynamic routing stats to db")?; - - client - .update_success_rate( - profile_id.get_string_repr().into(), - success_based_routing_configs, - success_based_routing_config_params, - vec![routing_types::RoutableConnectorChoiceWithStatus::new( - routing_types::RoutableConnectorChoice { - choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, - connector: common_enums::RoutableConnectors::from_str( - payment_connector.as_str(), - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to infer routable_connector from connector")?, - merchant_connector_id: payment_attempt.merchant_connector_id.clone(), - }, - payment_status_attribute == common_enums::AttemptStatus::Charged, - )], - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to update success based routing window in dynamic routing service", - )?; - Ok(()) + ); + logger::debug!("successfully pushed success_based_routing metrics"); + + state + .store + .insert_dynamic_routing_stat_entry(dynamic_routing_stats) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to push dynamic routing stats to db")?; + + client + .update_success_rate( + profile_id.get_string_repr().into(), + success_based_routing_configs, + success_based_routing_config_params, + vec![routing_types::RoutableConnectorChoiceWithStatus::new( + routing_types::RoutableConnectorChoice { + choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str( + payment_connector.as_str(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to infer routable_connector from connector", + )?, + merchant_connector_id: payment_attempt.merchant_connector_id.clone(), + }, + payment_status_attribute == common_enums::AttemptStatus::Charged, + )], + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update success based routing window in dynamic routing service", + )?; + Ok(()) + } else { + Ok(()) + } } else { Ok(()) } @@ -962,194 +968,205 @@ pub async fn push_metrics_with_update_window_for_contract_based_routing( dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, _dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { - let contract_routing_algo_ref = dynamic_routing_algo_ref - .contract_based_routing - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("contract_routing_algorithm not found in dynamic_routing_algorithm from business_profile table")?; - - if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { - let client = state - .grpc_client - .dynamic_routing - .contract_based_client - .clone() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "contract_routing gRPC client not found".to_string(), - })?; - - let payment_connector = &payment_attempt.connector.clone().ok_or( - errors::ApiErrorResponse::GenericNotFoundError { - message: "unable to derive payment connector from payment attempt".to_string(), - }, - )?; - - let contract_based_routing_config = - fetch_dynamic_routing_configs::( - state, - profile_id, - contract_routing_algo_ref - .algorithm_id_with_timestamp - .algorithm_id - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "contract_based_routing_algorithm_id not found in business_profile", - )?, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve contract based dynamic routing configs")?; - - let mut existing_label_info = None; + if let Some(contract_routing_algo_ref) = dynamic_routing_algo_ref.contract_based_routing { + if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None + { + let client = state + .grpc_client + .dynamic_routing + .contract_based_client + .clone() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract_routing gRPC client not found".to_string(), + })?; + + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; - contract_based_routing_config - .label_info - .as_ref() - .map(|label_info_vec| { - for label_info in label_info_vec { - if Some(&label_info.mca_id) == payment_attempt.merchant_connector_id.as_ref() { - existing_label_info = Some(label_info.clone()); + let contract_based_routing_config = + fetch_dynamic_routing_configs::( + state, + profile_id, + contract_routing_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract_routing algorithm_id not found".to_string(), + }) + .attach_printable( + "contract_based_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract based dynamic routing configs not found".to_string(), + }) + .attach_printable("unable to retrieve contract based dynamic routing configs")?; + + let mut existing_label_info = None; + + contract_based_routing_config + .label_info + .as_ref() + .map(|label_info_vec| { + for label_info in label_info_vec { + if Some(&label_info.mca_id) + == payment_attempt.merchant_connector_id.as_ref() + { + existing_label_info = Some(label_info.clone()); + } } - } - }); - - let final_label_info = existing_label_info - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to get LabelInformation from ContractBasedRoutingConfig")?; - - logger::debug!( - "contract based routing: matched LabelInformation - {:?}", - final_label_info - ); - - let request_label_info = routing_types::LabelInformation { - label: format!( - "{}:{}", - final_label_info.label.clone(), - final_label_info.mca_id.get_string_repr() - ), - target_count: final_label_info.target_count, - target_time: final_label_info.target_time, - mca_id: final_label_info.mca_id.to_owned(), - }; + }); - let payment_status_attribute = - get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + let final_label_info = existing_label_info + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "LabelInformation from ContractBasedRoutingConfig not found" + .to_string(), + }) + .attach_printable( + "unable to get LabelInformation from ContractBasedRoutingConfig", + )?; - if payment_status_attribute == common_enums::AttemptStatus::Charged { - client - .update_contracts( + logger::debug!( + "contract based routing: matched LabelInformation - {:?}", + final_label_info + ); + + let request_label_info = routing_types::LabelInformation { + label: format!( + "{}:{}", + final_label_info.label.clone(), + final_label_info.mca_id.get_string_repr() + ), + target_count: final_label_info.target_count, + target_time: final_label_info.target_time, + mca_id: final_label_info.mca_id.to_owned(), + }; + + let payment_status_attribute = + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + + if payment_status_attribute == common_enums::AttemptStatus::Charged { + client + .update_contracts( + profile_id.get_string_repr().into(), + vec![request_label_info], + "".to_string(), + vec![], + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update contract based routing window in dynamic routing service", + )?; + } + + let contract_scores = client + .calculate_contract_score( profile_id.get_string_repr().into(), - vec![request_label_info], + contract_based_routing_config.clone(), "".to_string(), - vec![], + routable_connectors.clone(), state.get_grpc_headers(), ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( - "unable to update contract based routing window in dynamic routing service", + "unable to calculate/fetch contract scores from dynamic routing service", )?; - } - let contract_scores = client - .calculate_contract_score( - profile_id.get_string_repr().into(), - contract_based_routing_config.clone(), - "".to_string(), - routable_connectors.clone(), - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to calculate/fetch contract scores from dynamic routing service", - )?; - - let first_contract_based_connector = &contract_scores - .labels_with_score - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first connector from list of connectors obtained from dynamic routing service", - )?; + let first_contract_based_connector = &contract_scores + .labels_with_score + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first connector from list of connectors obtained from dynamic routing service", + )?; - let (first_contract_based_connector, connector_score, current_payment_cnt) = (first_contract_based_connector.label - .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!( - "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", - first_contract_based_connector - ))? - .0, first_contract_based_connector.score, first_contract_based_connector.current_count ); - - core_metrics::DYNAMIC_CONTRACT_BASED_ROUTING.add( - 1, - router_env::metric_attributes!( - ( - "tenant", - state.tenant.tenant_id.get_string_repr().to_owned(), - ), - ( - "merchant_profile_id", - format!( - "{}:{}", - payment_attempt.merchant_id.get_string_repr(), - payment_attempt.profile_id.get_string_repr() + let (first_contract_based_connector, connector_score, current_payment_cnt) = (first_contract_based_connector.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", + first_contract_based_connector + ))? + .0, first_contract_based_connector.score, first_contract_based_connector.current_count ); + + core_metrics::DYNAMIC_CONTRACT_BASED_ROUTING.add( + 1, + router_env::metric_attributes!( + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), ), - ), - ( - "contract_based_routing_connector", - first_contract_based_connector.to_string(), - ), - ( - "contract_based_routing_connector_score", - connector_score.to_string(), - ), - ( - "current_payment_count_contract_based_routing_connector", - current_payment_cnt.to_string(), - ), - ("payment_connector", payment_connector.to_string()), - ( - "currency", - payment_attempt - .currency - .map_or_else(|| "None".to_string(), |currency| currency.to_string()), - ), - ( - "payment_method", - payment_attempt.payment_method.map_or_else( - || "None".to_string(), - |payment_method| payment_method.to_string(), + ( + "merchant_profile_id", + format!( + "{}:{}", + payment_attempt.merchant_id.get_string_repr(), + payment_attempt.profile_id.get_string_repr() + ), ), - ), - ( - "payment_method_type", - payment_attempt.payment_method_type.map_or_else( - || "None".to_string(), - |payment_method_type| payment_method_type.to_string(), + ( + "contract_based_routing_connector", + first_contract_based_connector.to_string(), ), - ), - ( - "capture_method", - payment_attempt.capture_method.map_or_else( - || "None".to_string(), - |capture_method| capture_method.to_string(), + ( + "contract_based_routing_connector_score", + connector_score.to_string(), ), - ), - ( - "authentication_type", - payment_attempt.authentication_type.map_or_else( - || "None".to_string(), - |authentication_type| authentication_type.to_string(), + ( + "current_payment_count_contract_based_routing_connector", + current_payment_cnt.to_string(), + ), + ("payment_connector", payment_connector.to_string()), + ( + "currency", + payment_attempt + .currency + .map_or_else(|| "None".to_string(), |currency| currency.to_string()), ), + ( + "payment_method", + payment_attempt.payment_method.map_or_else( + || "None".to_string(), + |payment_method| payment_method.to_string(), + ), + ), + ( + "payment_method_type", + payment_attempt.payment_method_type.map_or_else( + || "None".to_string(), + |payment_method_type| payment_method_type.to_string(), + ), + ), + ( + "capture_method", + payment_attempt.capture_method.map_or_else( + || "None".to_string(), + |capture_method| capture_method.to_string(), + ), + ), + ( + "authentication_type", + payment_attempt.authentication_type.map_or_else( + || "None".to_string(), + |authentication_type| authentication_type.to_string(), + ), + ), + ("payment_status", payment_attempt.status.to_string()), ), - ("payment_status", payment_attempt.status.to_string()), - ), - ); - logger::debug!("successfully pushed contract_based_routing metrics"); + ); + logger::debug!("successfully pushed contract_based_routing metrics"); - Ok(()) + Ok(()) + } else { + Ok(()) + } } else { Ok(()) } diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index 28d32f627cb..63824a40c9a 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -11,7 +11,8 @@ use hyperswitch_domain_models::{ authentication::{MessageCategory, PreAuthenticationData}, unified_authentication_service::{ PaymentDetails, ServiceSessionIds, TransactionDetails, UasAuthenticationRequestData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, BrowserInformation, }, @@ -20,6 +21,7 @@ use hyperswitch_domain_models::{ UasPreAuthenticationRouterData, }, }; +use masking::ExposeInterface; use super::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType}; use crate::{ @@ -152,12 +154,76 @@ impl UnifiedAuthenticationService for ClickToPay { .await } - fn confirmation( - _state: &SessionState, + async fn confirmation( + state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, + payment_data: &PaymentData, + merchant_connector_account: &MerchantConnectorAccountType, + connector_name: &str, + payment_method: common_enums::PaymentMethod, ) -> RouterResult<()> { + let authentication_id = payment_data + .payment_attempt + .authentication_id + .clone() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("Missing authentication id in payment attempt")?; + + let currency = payment_data.payment_attempt.currency.ok_or( + ApiErrorResponse::MissingRequiredField { + field_name: "currency", + }, + )?; + + let current_time = common_utils::date_time::now(); + + let payment_attempt_status = payment_data.payment_attempt.status; + + let (checkout_event_status, confirmation_reason) = + utils::get_checkout_event_status_and_reason(payment_attempt_status); + + let click_to_pay_details = payment_data.service_details.clone(); + + let authentication_confirmation_data = UasConfirmationRequestData { + x_src_flow_id: payment_data + .service_details + .as_ref() + .and_then(|details| details.x_src_flow_id.clone()), + transaction_amount: payment_data.payment_attempt.net_amount.get_order_amount(), + transaction_currency: currency, + checkout_event_type: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + checkout_event_status: checkout_event_status.clone(), + confirmation_status: checkout_event_status.clone(), + confirmation_reason, + confirmation_timestamp: Some(current_time), + network_authorization_code: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + network_transaction_identifier: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + correlation_id: click_to_pay_details + .clone() + .and_then(|details| details.correlation_id), + merchant_transaction_id: click_to_pay_details + .and_then(|details| details.merchant_transaction_id), + }; + + let authentication_confirmation_router_data : hyperswitch_domain_models::types::UasAuthenticationConfirmationRouterData = utils::construct_uas_router_data( + state, + connector_name.to_string(), + payment_method, + payment_data.payment_attempt.merchant_id.clone(), + None, + authentication_confirmation_data, + merchant_connector_account, + Some(authentication_id.clone()), + )?; + + utils::do_auth_connector_call( + state, + UNIFIED_AUTHENTICATION_SERVICE.to_string(), + authentication_confirmation_router_data, + ) + .await?; + Ok(()) } } @@ -388,17 +454,12 @@ impl UnifiedAuthenticationService for ExternalAuthentication ) .await } - - fn confirmation( - _state: &SessionState, - _key_store: &domain::MerchantKeyStore, - _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, - ) -> RouterResult<()> { - Ok(()) - } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[allow(clippy::too_many_arguments)] pub async fn create_new_authentication( state: &SessionState, @@ -410,6 +471,7 @@ pub async fn create_new_authentication( authentication_id: &str, service_details: Option, authentication_status: common_enums::AuthenticationStatus, + network_token: Option, organization_id: common_utils::id_type::OrganizationId, ) -> RouterResult { let service_details_value = service_details @@ -433,10 +495,12 @@ pub async fn create_new_authentication( connector_metadata: None, maximum_supported_version: None, threeds_server_transaction_id: None, - cavv: None, + cavv: network_token + .clone() + .and_then(|data| data.token_cryptogram.map(|cavv| cavv.expose())), authentication_flow_type: None, message_version: None, - eci: None, + eci: network_token.and_then(|data| data.eci), trans_status: None, acquirer_bin: None, acquirer_merchant_id: None, diff --git a/crates/router/src/core/unified_authentication_service/types.rs b/crates/router/src/core/unified_authentication_service/types.rs index bb9abdb1219..7a1caf2d0e7 100644 --- a/crates/router/src/core/unified_authentication_service/types.rs +++ b/crates/router/src/core/unified_authentication_service/types.rs @@ -145,11 +145,14 @@ pub trait UnifiedAuthenticationService { .into()) } - fn confirmation( + async fn confirmation( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, + _payment_data: &PaymentData, _merchant_connector_account: &MerchantConnectorAccountType, + _connector_name: &str, + _payment_method: common_enums::PaymentMethod, ) -> RouterResult<()> { Err(errors::ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("confirmation".to_string()), diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index 15ab09e3cdc..b74cd4d0c89 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -136,70 +136,74 @@ pub async fn external_authentication_update_trackers( Ok(response) => match response { UasAuthenticationResponseData::PreAuthentication { authentication_details, - } => diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate { - threeds_server_transaction_id: authentication_details - .threeds_server_transaction_id - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing threeds_server_transaction_id in PreAuthentication Details", - )?, - maximum_supported_3ds_version: authentication_details - .maximum_supported_3ds_version - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing maximum_supported_3ds_version in PreAuthentication Details", - )?, - connector_authentication_id: authentication_details - .connector_authentication_id - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing connector_authentication_id in PreAuthentication Details", - )?, - three_ds_method_data: authentication_details.three_ds_method_data, - three_ds_method_url: authentication_details.three_ds_method_url, - message_version: authentication_details - .message_version - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable("missing message_version in PreAuthentication Details")?, - connector_metadata: authentication_details.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: authentication_details.directory_server_id, - }, + } => Ok( + diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate { + threeds_server_transaction_id: authentication_details + .threeds_server_transaction_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing threeds_server_transaction_id in PreAuthentication Details", + )?, + maximum_supported_3ds_version: authentication_details + .maximum_supported_3ds_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing maximum_supported_3ds_version in PreAuthentication Details", + )?, + connector_authentication_id: authentication_details + .connector_authentication_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing connector_authentication_id in PreAuthentication Details", + )?, + three_ds_method_data: authentication_details.three_ds_method_data, + three_ds_method_url: authentication_details.three_ds_method_url, + message_version: authentication_details + .message_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("missing message_version in PreAuthentication Details")?, + connector_metadata: authentication_details.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: authentication_details.directory_server_id, + }, + ), UasAuthenticationResponseData::Authentication { authentication_details, } => { let authentication_status = common_enums::AuthenticationStatus::foreign_from( authentication_details.trans_status.clone(), ); - diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate { - authentication_value: authentication_details.authentication_value, - trans_status: authentication_details.trans_status, - acs_url: authentication_details.authn_flow_type.get_acs_url(), - challenge_request: authentication_details - .authn_flow_type - .get_challenge_request(), - acs_reference_number: authentication_details - .authn_flow_type - .get_acs_reference_number(), - acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(), - acs_signed_content: authentication_details - .authn_flow_type - .get_acs_signed_content(), - authentication_type: authentication_details - .authn_flow_type - .get_decoupled_authentication_type(), - authentication_status, - connector_metadata: authentication_details.connector_metadata, - ds_trans_id: authentication_details.ds_trans_id, - } + Ok( + diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate { + authentication_value: authentication_details.authentication_value, + trans_status: authentication_details.trans_status, + acs_url: authentication_details.authn_flow_type.get_acs_url(), + challenge_request: authentication_details + .authn_flow_type + .get_challenge_request(), + acs_reference_number: authentication_details + .authn_flow_type + .get_acs_reference_number(), + acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(), + acs_signed_content: authentication_details + .authn_flow_type + .get_acs_signed_content(), + authentication_type: authentication_details + .authn_flow_type + .get_decoupled_authentication_type(), + authentication_status, + connector_metadata: authentication_details.connector_metadata, + ds_trans_id: authentication_details.ds_trans_id, + }, + ) } UasAuthenticationResponseData::PostAuthentication { authentication_details, @@ -208,29 +212,39 @@ pub async fn external_authentication_update_trackers( .trans_status .ok_or(ApiErrorResponse::InternalServerError) .attach_printable("missing trans_status in PostAuthentication Details")?; - diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate { - authentication_status: common_enums::AuthenticationStatus::foreign_from( - trans_status.clone(), - ), - trans_status, - authentication_value: authentication_details - .dynamic_data_details - .and_then(|details| details.dynamic_data_value) - .map(masking::ExposeInterface::expose), - eci: authentication_details.eci, - } + Ok( + diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate { + authentication_status: common_enums::AuthenticationStatus::foreign_from( + trans_status.clone(), + ), + trans_status, + authentication_value: authentication_details + .dynamic_data_details + .and_then(|details| details.dynamic_data_value) + .map(masking::ExposeInterface::expose), + eci: authentication_details.eci, + }, + ) } + + UasAuthenticationResponseData::Confirmation { .. } => Err( + ApiErrorResponse::InternalServerError, + ) + .attach_printable("unexpected api confirmation in external authentication flow."), }, - Err(error) => diesel_models::authentication::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), - }, - }; + Err(error) => Ok( + diesel_models::authentication::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( @@ -241,3 +255,18 @@ pub async fn external_authentication_update_trackers( .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Error while updating authentication") } + +pub fn get_checkout_event_status_and_reason( + attempt_status: common_enums::AttemptStatus, +) -> (Option, Option) { + match attempt_status { + common_enums::AttemptStatus::Charged | common_enums::AttemptStatus::Authorized => ( + Some("02".to_string()), + Some("Approval Code received".to_string()), + ), + _ => ( + Some("03".to_string()), + Some("No Approval Code received".to_string()), + ), + } +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 49075e7f548..8f5a80b24db 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -538,6 +538,58 @@ pub fn get_split_refunds( _ => Ok(None), } } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) => { + match ( + &split_refund_input.payment_charges, + &split_refund_input.refund_request, + ) { + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + Some(common_types::refunds::SplitRefund::XenditSplitRefund( + split_refund_request, + )), + ) => { + let user_id = super::refunds::validator::validate_xendit_charge_refund( + xendit_split_payment_response, + split_refund_request, + )?; + + Ok(user_id.map(|for_user_id| { + router_request_types::SplitRefundsRequest::XenditSplitRefund( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) + })) + } + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + None, + ) => { + let option_for_user_id = match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + common_types::payments::XenditMultipleSplitResponse { + for_user_id, .. + }, + ) => for_user_id.clone(), + common_types::payments::XenditChargeResponseData::SingleSplit( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) => Some(for_user_id.clone()), + }; + + if option_for_user_id.is_some() { + Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "split_refunds.xendit_split_refund.for_user_id", + })? + } else { + Ok(None) + } + } + _ => Ok(None), + } + } _ => Ok(None), } } diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 64c5617398b..4a9c4106b9f 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -4,6 +4,8 @@ mod incoming; mod incoming_v2; #[cfg(feature = "v1")] mod outgoing; +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +mod recovery_incoming; pub mod types; pub mod utils; #[cfg(feature = "olap")] diff --git a/crates/router/src/core/webhooks/incoming_v2.rs b/crates/router/src/core/webhooks/incoming_v2.rs index 900a455f993..c564b405836 100644 --- a/crates/router/src/core/webhooks/incoming_v2.rs +++ b/crates/router/src/core/webhooks/incoming_v2.rs @@ -15,6 +15,8 @@ use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails; use router_env::{instrument, tracing, tracing_actix_web::RequestId}; use super::{types, utils, MERCHANT_ID}; +#[cfg(feature = "revenue_recovery")] +use crate::core::webhooks::recovery_incoming; use crate::{ core::{ api_locking, @@ -349,6 +351,24 @@ async fn incoming_webhooks_core( api::WebhookFlow::Payout => todo!(), api::WebhookFlow::Subscription => todo!(), + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + api::WebhookFlow::Recovery => { + Box::pin(recovery_incoming::recovery_incoming_webhook_flow( + state.clone(), + merchant_account, + profile, + key_store, + webhook_details, + source_verified, + &connector, + &request_details, + event_type, + req_state, + )) + .await + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("Failed to process recovery incoming webhook")? + } } } } diff --git a/crates/router/src/core/webhooks/recovery_incoming.rs b/crates/router/src/core/webhooks/recovery_incoming.rs new file mode 100644 index 00000000000..3634acfab97 --- /dev/null +++ b/crates/router/src/core/webhooks/recovery_incoming.rs @@ -0,0 +1,307 @@ +use api_models::webhooks; +use common_utils::ext_traits::AsyncExt; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::revenue_recovery; +use hyperswitch_interfaces::webhooks as interface_webhooks; +use router_env::{instrument, tracing}; + +use crate::{ + core::{ + errors::{self, CustomResult}, + payments::{self, operations}, + }, + routes::{app::ReqState, SessionState}, + services::{self, connector_integration_interface}, + types::{api, domain}, +}; + +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +#[cfg(feature = "revenue_recovery")] +pub async fn recovery_incoming_webhook_flow( + state: SessionState, + merchant_account: domain::MerchantAccount, + business_profile: domain::Profile, + key_store: domain::MerchantKeyStore, + _webhook_details: api::IncomingWebhookDetails, + source_verified: bool, + connector: &connector_integration_interface::ConnectorEnum, + request_details: &hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails<'_>, + event_type: webhooks::IncomingWebhookEvent, + req_state: ReqState, +) -> CustomResult { + // Source verification is necessary for revenue recovery webhooks flow since We don't have payment intent/attempt object created before in our system. + + common_utils::fp_utils::when(!source_verified, || { + Err(report!( + errors::RevenueRecoveryError::WebhookAuthenticationFailed + )) + })?; + + let invoice_details = RevenueRecoveryInvoice( + interface_webhooks::IncomingWebhook::get_revenue_recovery_invoice_details( + connector, + request_details, + ) + .change_context(errors::RevenueRecoveryError::InvoiceWebhookProcessingFailed) + .attach_printable("Failed while getting revenue recovery invoice details")?, + ); + // Fetch the intent using merchant reference id, if not found create new intent. + let payment_intent = invoice_details + .get_payment_intent( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + ) + .await + .transpose() + .async_unwrap_or_else(|| async { + invoice_details + .create_payment_intent( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + ) + .await + }) + .await?; + + let payment_attempt = match event_type.is_recovery_transaction_event() { + true => { + let invoice_transaction_details = RevenueRecoveryAttempt( + interface_webhooks::IncomingWebhook::get_revenue_recovery_attempt_details( + connector, + request_details, + ) + .change_context(errors::RevenueRecoveryError::TransactionWebhookProcessingFailed)?, + ); + + invoice_transaction_details + .get_payment_attempt( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + payment_intent.payment_id.clone(), + ) + .await? + } + false => None, + }; + + let attempt_triggered_by = payment_attempt + .and_then(revenue_recovery::RecoveryPaymentAttempt::get_attempt_triggered_by); + + let action = revenue_recovery::RecoveryAction::get_action(event_type, attempt_triggered_by); + + match action { + revenue_recovery::RecoveryAction::CancelInvoice => todo!(), + revenue_recovery::RecoveryAction::ScheduleFailedPayment => { + todo!() + } + revenue_recovery::RecoveryAction::SuccessPaymentExternal => { + todo!() + } + revenue_recovery::RecoveryAction::PendingPayment => { + router_env::logger::info!( + "Pending transactions are not consumed by the revenue recovery webhooks" + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + revenue_recovery::RecoveryAction::NoAction => { + router_env::logger::info!( + "No Recovery action is taken place for recovery event : {:?} and attempt triggered_by : {:?} ", event_type.clone(), attempt_triggered_by + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + revenue_recovery::RecoveryAction::InvalidAction => { + router_env::logger::error!( + "Invalid Revenue recovery action state has been received, event : {:?}, triggered_by : {:?}", event_type, attempt_triggered_by + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + } +} + +pub struct RevenueRecoveryInvoice(revenue_recovery::RevenueRecoveryInvoiceData); +pub struct RevenueRecoveryAttempt(revenue_recovery::RevenueRecoveryAttemptData); + +impl RevenueRecoveryInvoice { + async fn get_payment_intent( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult, errors::RevenueRecoveryError> + { + let payment_response = Box::pin(payments::payments_get_intent_using_merchant_reference( + state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + req_state.clone(), + &self.0.merchant_reference_id, + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, + )) + .await; + let response = match payment_response { + Ok(services::ApplicationResponse::JsonWithHeaders((payments_response, _))) => { + let payment_id = payments_response.id.clone(); + let status = payments_response.status; + let feature_metadata = payments_response.feature_metadata; + Ok(Some(revenue_recovery::RecoveryPaymentIntent { + payment_id, + status, + feature_metadata, + })) + } + Err(err) + if matches!( + err.current_context(), + &errors::ApiErrorResponse::PaymentNotFound + ) => + { + Ok(None) + } + Ok(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("Unexpected response from payment intent core"), + error @ Err(_) => { + router_env::logger::error!(?error); + Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("failed to fetch payment intent recovery webhook flow") + } + }?; + Ok(response) + } + async fn create_payment_intent( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let payload = api_models::payments::PaymentsCreateIntentRequest::from(&self.0); + let global_payment_id = + common_utils::id_type::GlobalPaymentId::generate(&state.conf.cell_information.id); + + let create_intent_response = Box::pin(payments::payments_intent_core::< + hyperswitch_domain_models::router_flow_types::payments::PaymentCreateIntent, + api_models::payments::PaymentsIntentResponse, + _, + _, + hyperswitch_domain_models::payments::PaymentIntentData< + hyperswitch_domain_models::router_flow_types::payments::PaymentCreateIntent, + >, + >( + state.clone(), + req_state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + payments::operations::PaymentIntentCreate, + payload, + global_payment_id, + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, + )) + .await + .change_context(errors::RevenueRecoveryError::PaymentIntentCreateFailed)?; + let response = payments::handle_payments_intent_response(create_intent_response) + .change_context(errors::RevenueRecoveryError::PaymentIntentCreateFailed)?; + + Ok(revenue_recovery::RecoveryPaymentIntent { + payment_id: response.id, + status: response.status, + feature_metadata: response.feature_metadata, + }) + } +} + +impl RevenueRecoveryAttempt { + async fn get_payment_attempt( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + payment_id: common_utils::id_type::GlobalPaymentId, + ) -> CustomResult, errors::RevenueRecoveryError> + { + let attempt_response = Box::pin(payments::payments_core::< + hyperswitch_domain_models::router_flow_types::payments::PSync, + api_models::payments::PaymentsRetrieveResponse, + _, + _, + _, + hyperswitch_domain_models::payments::PaymentStatusData< + hyperswitch_domain_models::router_flow_types::payments::PSync, + >, + >( + state.clone(), + req_state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + payments::operations::PaymentGet, + api_models::payments::PaymentsRetrieveRequest { + force_sync: false, + expand_attempts: true, + param: None, + }, + payment_id.clone(), + payments::CallConnectorAction::Avoid, + hyperswitch_domain_models::payments::HeaderPayload::default(), + )) + .await; + let response = match attempt_response { + Ok(services::ApplicationResponse::JsonWithHeaders((payments_response, _))) => { + let final_attempt = + self.0 + .connector_transaction_id + .as_ref() + .and_then(|transaction_id| { + payments_response + .find_attempt_in_attempts_list_using_connector_transaction_id( + transaction_id, + ) + }); + let payment_attempt = + final_attempt.map(|attempt_res| revenue_recovery::RecoveryPaymentAttempt { + attempt_id: attempt_res.id.to_owned(), + attempt_status: attempt_res.status.to_owned(), + feature_metadata: attempt_res.feature_metadata.to_owned(), + }); + Ok(payment_attempt) + } + Ok(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("Unexpected response from payment intent core"), + error @ Err(_) => { + router_env::logger::error!(?error); + Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("failed to fetch payment intent recovery webhook flow") + } + }?; + Ok(response) + } + async fn record_payment_attempt( + &self, + _state: &SessionState, + _req_state: &ReqState, + _merchant_account: &domain::MerchantAccount, + _profile: &domain::Profile, + _key_store: &domain::MerchantKeyStore, + _payment_id: common_utils::id_type::GlobalPaymentId, + ) -> CustomResult { + todo!() + } +} diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index d21c345b5fe..a8189468b16 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1735,6 +1735,34 @@ impl PaymentAttemptInterface for KafkaStore { .await } + #[cfg(feature = "v2")] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.diesel_store + .get_total_count_of_filtered_payment_attempts( + merchant_id, + active_attempt_ids, + connector, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] async fn find_attempts_by_merchant_id_payment_id( &self, @@ -1914,6 +1942,32 @@ impl PaymentIntentInterface for KafkaStore { .await } + #[cfg(all(feature = "olap", feature = "v2"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + key_store: &domain::MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult< + Vec<( + hyperswitch_domain_models::payments::PaymentIntent, + Option, + )>, + errors::DataStorageError, + > { + self.diesel_store + .get_filtered_payment_intents_attempt( + state, + merchant_id, + constraints, + key_store, + storage_scheme, + ) + .await + } + #[cfg(all(feature = "olap", feature = "v1"))] async fn get_filtered_active_attempt_ids_for_total_count( &self, @@ -1952,6 +2006,21 @@ impl PaymentIntentInterface for KafkaStore { ) .await } + #[cfg(all(feature = "olap", feature = "v2"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult>, errors::DataStorageError> { + self.diesel_store + .get_filtered_active_attempt_ids_for_total_count( + merchant_id, + constraints, + storage_scheme, + ) + .await + } } #[async_trait::async_trait] diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 9b43d7f2b16..18e8e7cdccf 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -144,6 +144,7 @@ pub fn mk_app( .service(routes::MerchantConnectorAccount::server(state.clone())) .service(routes::RelayWebhooks::server(state.clone())) .service(routes::Webhooks::server(state.clone())) + .service(routes::Hypersense::server(state.clone())) .service(routes::Relay::server(state.clone())); #[cfg(feature = "oltp")] diff --git a/crates/router/src/macros.rs b/crates/router/src/macros.rs index 962bdb96529..580ccb73f53 100644 --- a/crates/router/src/macros.rs +++ b/crates/router/src/macros.rs @@ -32,4 +32,37 @@ macro_rules! get_payment_link_config_value { $(get_payment_link_config_value_based_on_priority!($config, $business_config, $field, $default)),* ) }; + ($config:expr, $business_config:expr, $(($field:ident)),*) => { + ( + $( + $config + .as_ref() + .and_then(|pc_config| pc_config.theme_config.$field.clone()) + .or_else(|| { + $business_config + .as_ref() + .and_then(|business_config| business_config.$field.clone()) + }) + ),* + ) + }; + ($config:expr, $business_config:expr, $(($field:ident $(, $transform:expr)?)),* $(,)?) => { + ( + $( + $config + .as_ref() + .and_then(|pc_config| pc_config.theme_config.$field.clone()) + .or_else(|| { + $business_config + .as_ref() + .and_then(|business_config| { + let value = business_config.$field.clone(); + $(let value = value.map($transform);)? + value + }) + }) + ),* + ) + }; + } diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index 80906570d7c..0b506611665 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -23,6 +23,7 @@ pub mod files; pub mod fraud_check; pub mod gsm; pub mod health; +pub mod hypersense; pub mod lock_utils; #[cfg(feature = "v1")] pub mod locker_migration; @@ -59,6 +60,9 @@ pub mod verify_connector; pub mod webhook_events; pub mod webhooks; +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +pub mod recovery_webhooks; + pub mod relay; #[cfg(feature = "dummy_connector")] @@ -69,9 +73,9 @@ pub use self::app::PaymentMethodsSession; pub use self::app::Recon; pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, - Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Mandates, - MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, - Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, Webhooks, + Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Hypersense, + Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, + Poll, Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, 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 10c63049164..7eae79acc2d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -46,6 +46,8 @@ use super::payouts::*; use super::pm_auth; #[cfg(feature = "oltp")] use super::poll; +#[cfg(all(feature = "v2", feature = "revenue_recovery", feature = "oltp"))] +use super::recovery_webhooks::*; #[cfg(feature = "olap")] use super::routing; #[cfg(all(feature = "olap", feature = "v1"))] @@ -88,6 +90,7 @@ pub use crate::{ use crate::{ configs::{secrets_transformers, Settings}, db::kafka_store::{KafkaStore, TenantID}, + routes::hypersense as hypersense_routes, }; #[derive(Clone)] @@ -572,6 +575,7 @@ impl Payments { web::resource("/create-intent") .route(web::post().to(payments::payments_create_intent)), ) + .service(web::resource("/list").route(web::get().to(payments::payments_list))) .service( web::resource("/aggregate").route(web::get().to(payments::get_payments_aggregates)), ) @@ -1334,6 +1338,27 @@ impl Recon { } } +pub struct Hypersense; + +impl Hypersense { + pub fn server(state: AppState) -> Scope { + web::scope("/hypersense") + .app_data(web::Data::new(state)) + .service( + web::resource("/token") + .route(web::get().to(hypersense_routes::get_hypersense_token)), + ) + .service( + web::resource("/verify_token") + .route(web::post().to(hypersense_routes::verify_hypersense_token)), + ) + .service( + web::resource("/signout") + .route(web::post().to(hypersense_routes::signout_hypersense_token)), + ) + } +} + #[cfg(feature = "olap")] pub struct Blocklist; @@ -1623,6 +1648,16 @@ impl Webhooks { ), ); + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + { + route = route.service( + web::resource("/recovery/{merchant_id}/{profile_id}/{connector_id}").route( + web::post() + .to(recovery_receive_incoming_webhook::), + ), + ); + } + route } } diff --git a/crates/router/src/routes/hypersense.rs b/crates/router/src/routes/hypersense.rs new file mode 100644 index 00000000000..a018dfa6605 --- /dev/null +++ b/crates/router/src/routes/hypersense.rs @@ -0,0 +1,76 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::external_service_auth as external_service_auth_api; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{api_locking, external_service_auth}, + services::{ + api, + authentication::{self, ExternalServiceType}, + }, +}; + +pub async fn get_hypersense_token(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::HypersenseTokenRequest; + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, user, _, _| { + external_service_auth::generate_external_token( + state, + user, + ExternalServiceType::Hypersense, + ) + }, + &authentication::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn signout_hypersense_token( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::HypersenseSignoutToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _: (), json_payload, _| { + external_service_auth::signout_external_token(state, json_payload) + }, + &authentication::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn verify_hypersense_token( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::HypersenseVerifyToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _: (), json_payload, _| { + external_service_auth::verify_external_token( + state, + json_payload, + ExternalServiceType::Hypersense, + ) + }, + &authentication::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 1e663eedf64..0f45b352c7f 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -39,6 +39,7 @@ pub enum ApiIdentifier { ApplePayCertificatesMigration, Relay, Documentation, + Hypersense, PaymentMethodsSession, } @@ -178,7 +179,8 @@ impl From for ApiIdentifier { | Flow::IncomingRelayWebhookReceive | Flow::WebhookEventInitialDeliveryAttemptList | Flow::WebhookEventDeliveryAttemptList - | Flow::WebhookEventDeliveryRetry => Self::Webhooks, + | Flow::WebhookEventDeliveryRetry + | Flow::RecoveryIncomingWebhookReceive => Self::Webhooks, Flow::ApiKeyCreate | Flow::ApiKeyRetrieve @@ -312,6 +314,10 @@ impl From for ApiIdentifier { Flow::FeatureMatrix => Self::Documentation, + Flow::HypersenseTokenRequest + | Flow::HypersenseVerifyToken + | Flow::HypersenseSignoutToken => Self::Hypersense, + Flow::PaymentMethodSessionCreate | Flow::PaymentMethodSessionRetrieve | Flow::PaymentMethodSessionUpdateSavedPaymentMethod => Self::PaymentMethodsSession, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index faf74e37368..35b1edc11d6 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1233,6 +1233,35 @@ pub async fn payments_list( .await } +#[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] +#[cfg(all(feature = "olap", feature = "v2"))] +pub async fn payments_list( + state: web::Data, + req: actix_web::HttpRequest, + payload: web::Query, +) -> impl Responder { + let flow = Flow::PaymentsList; + let payload = payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, _| { + payments::list_payments(state, auth.merchant_account, auth.key_store, req) + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::MerchantPaymentRead, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] #[cfg(all(feature = "olap", feature = "v1"))] pub async fn profile_payments_list( @@ -2427,7 +2456,7 @@ pub async fn payment_confirm_intent( pub async fn proxy_confirm_intent( state: web::Data, req: actix_web::HttpRequest, - json_payload: web::Json, + json_payload: web::Json, path: web::Path, ) -> impl Responder { use hyperswitch_domain_models::payments::PaymentConfirmData; @@ -2488,7 +2517,7 @@ pub async fn proxy_confirm_intent( header_payload.clone(), )) }, - &auth::PublishableKeyAuth, + &auth::HeaderAuth(auth::ApiKeyAuth), locking_action, )) .await @@ -2637,7 +2666,7 @@ pub async fn payments_finish_redirection( let locking_action = payload.get_locking_input(flow.clone()); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -2659,7 +2688,7 @@ pub async fn payments_finish_redirection( profile_id: profile_id.clone(), }, locking_action, - ) + )) .await } diff --git a/crates/router/src/routes/recovery_webhooks.rs b/crates/router/src/routes/recovery_webhooks.rs new file mode 100644 index 00000000000..0c83ba1b395 --- /dev/null +++ b/crates/router/src/routes/recovery_webhooks.rs @@ -0,0 +1,53 @@ +use actix_web::{web, HttpRequest, Responder}; +use router_env::{instrument, tracing, Flow}; + +use super::app::AppState; +use crate::{ + core::{ + api_locking, + webhooks::{self, types}, + }, + services::{api, authentication as auth}, +}; + +#[instrument(skip_all, fields(flow = ?Flow::IncomingWebhookReceive))] +pub async fn recovery_receive_incoming_webhook( + state: web::Data, + req: HttpRequest, + body: web::Bytes, + path: web::Path<( + common_utils::id_type::MerchantId, + common_utils::id_type::ProfileId, + common_utils::id_type::MerchantConnectorAccountId, + )>, +) -> impl Responder { + let flow = Flow::RecoveryIncomingWebhookReceive; + let (merchant_id, profile_id, connector_id) = path.into_inner(); + + Box::pin(api::server_wrap( + flow.clone(), + state, + &req, + (), + |state, auth, _, req_state| { + webhooks::incoming_webhooks_wrapper::( + &flow, + state.to_owned(), + req_state, + &req, + auth.merchant_account, + auth.profile, + auth.key_store, + &connector_id, + body.clone(), + false, + ) + }, + &auth::MerchantIdAndProfileIdAuth { + merchant_id, + profile_id, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index edf127ac30f..502ebb5099f 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -13,9 +13,7 @@ use api_models::payouts; use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; use common_enums::TokenPurpose; -#[cfg(feature = "v2")] -use common_utils::fp_utils; -use common_utils::{date_time, id_type}; +use common_utils::{date_time, fp_utils, id_type}; #[cfg(feature = "v2")] use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; @@ -195,6 +193,13 @@ impl AuthenticationType { } } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, serde::Deserialize, strum::Display)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ExternalServiceType { + Hypersense, +} + #[cfg(feature = "olap")] #[derive(Clone, Debug)] pub struct UserFromSinglePurposeToken { @@ -3857,3 +3862,44 @@ impl ReconToken { jwt::generate_jwt(&token_payload, settings).await } } +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ExternalToken { + pub user_id: String, + pub merchant_id: id_type::MerchantId, + pub exp: u64, + pub external_service_type: ExternalServiceType, +} + +impl ExternalToken { + pub async fn new_token( + user_id: String, + merchant_id: id_type::MerchantId, + settings: &Settings, + external_service_type: ExternalServiceType, + ) -> UserResult { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration)?.as_secs(); + + let token_payload = Self { + user_id, + merchant_id, + exp, + external_service_type, + }; + jwt::generate_jwt(&token_payload, settings).await + } + + pub fn check_service_type( + &self, + required_service_type: &ExternalServiceType, + ) -> RouterResult<()> { + Ok(fp_utils::when( + &self.external_service_type != required_service_type, + || { + Err(errors::ApiErrorResponse::AccessForbidden { + resource: required_service_type.to_string(), + }) + }, + )?) + } +} diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index 5903c8e9cc0..d9b04f57344 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -338,6 +338,34 @@ impl api::IncomingWebhook for ConnectorEnum { Self::New(connector) => connector.get_network_txn_id(request), } } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_attempt_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryAttemptData, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_revenue_recovery_attempt_details(request), + Self::New(connector) => connector.get_revenue_recovery_attempt_details(request), + } + } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_invoice_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryInvoiceData, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_revenue_recovery_invoice_details(request), + Self::New(connector) => connector.get_revenue_recovery_invoice_details(request), + } + } } impl api::ConnectorTransactionId for ConnectorEnum { diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index a5bdef9572f..7bfbc22d05e 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -59,7 +59,8 @@ pub use hyperswitch_domain_models::{ router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, BrowserInformation, ChargeRefunds, ChargeRefundsOptions, CompleteAuthorizeData, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 11f2a1902d0..43556eb581a 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -368,7 +368,9 @@ impl ConnectorData { enums::Connector::Coinbase => { Ok(ConnectorEnum::Old(Box::new(&connector::Coinbase))) } - // enums::Connector::Coingate => Ok(ConnectorEnum::Old(Box::new(connector::Coingate))), + enums::Connector::Coingate => { + Ok(ConnectorEnum::Old(Box::new(connector::Coingate::new()))) + } enums::Connector::Cryptopay => { Ok(ConnectorEnum::Old(Box::new(connector::Cryptopay::new()))) } @@ -462,6 +464,9 @@ impl ConnectorData { // enums::Connector::Moneris => Ok(ConnectorEnum::Old(Box::new(connector::Moneris))), Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) } + enums::Connector::Moneris => { + Ok(ConnectorEnum::Old(Box::new(connector::Moneris::new()))) + } enums::Connector::Nexixpay => { Ok(ConnectorEnum::Old(Box::new(connector::Nexixpay::new()))) } diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 28533e33e17..289b56a7800 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -176,6 +176,7 @@ impl ForeignTryFrom for ProfileResponse { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled, max_auto_retries_enabled: item.max_auto_retries_enabled, + always_request_extended_authorization: item.always_request_extended_authorization, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, }) @@ -375,6 +376,7 @@ pub async fn create_profile_from_merchant_account( is_network_tokenization_enabled: request.is_network_tokenization_enabled, is_auto_retries_enabled: request.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: request.max_auto_retries_enabled.map(i16::from), + always_request_extended_authorization: request.always_request_extended_authorization, is_click_to_pay_enabled: request.is_click_to_pay_enabled, authentication_product_ids: request.authentication_product_ids, })) diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 58159fc5688..74329844a76 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -1,5 +1,7 @@ #[cfg(feature = "v1")] -pub use api_models::payments::{PaymentListResponse, PaymentListResponseV2}; +pub use api_models::payments::{ + PaymentListFilterConstraints, PaymentListResponse, PaymentListResponseV2, +}; #[cfg(feature = "v2")] pub use api_models::payments::{ PaymentsConfirmIntentRequest, PaymentsCreateIntentRequest, PaymentsIntentResponse, @@ -14,9 +16,8 @@ pub use api_models::{ CryptoData, CustomerAcceptance, CustomerDetailsResponse, MandateAmountData, MandateData, MandateTransactionType, MandateType, MandateValidationFields, NextActionType, OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, - PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, - PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, - PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, + PaymentListConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentMethodData, + PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index c82a921fe17..ce6c1e92bd7 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -218,6 +218,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; @@ -302,6 +305,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; let store = state @@ -399,6 +405,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; let store = state diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 99ed67ff836..aa27e362df2 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -228,7 +228,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::Chargebee => Self::Chargebee, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, - // api_enums::Connector::Coingate => Self::Coingate, + api_enums::Connector::Coingate => Self::Coingate, api_enums::Connector::Cryptopay => Self::Cryptopay, api_enums::Connector::CtpMastercard => { Err(common_utils::errors::ValidationError::InvalidValue { @@ -263,7 +263,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, - // api_enums::Connector::Moneris => Self::Moneris, + api_enums::Connector::Moneris => Self::Moneris, api_enums::Connector::Multisafepay => Self::Multisafepay, api_enums::Connector::Netcetera => { Err(common_utils::errors::ValidationError::InvalidValue { @@ -2105,6 +2105,8 @@ impl ForeignFrom .background_image .map(|background_image| background_image.foreign_into()), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } } @@ -2128,6 +2130,8 @@ impl ForeignFrom .background_image .map(|background_image| background_image.foreign_into()), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } } diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index e189deb9487..8217f9e34cd 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -274,6 +274,7 @@ pub async fn generate_sample_data( shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -361,6 +362,9 @@ pub async fn generate_sample_data( order_tax_amount: None, processor_transaction_data, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, }; diff --git a/crates/router/tests/connectors/moneris.rs b/crates/router/tests/connectors/moneris.rs index ac98cc17f3d..ba8c7e491f3 100644 --- a/crates/router/tests/connectors/moneris.rs +++ b/crates/router/tests/connectors/moneris.rs @@ -13,7 +13,7 @@ impl utils::Connector for MonerisTest { use router::connector::Moneris; utils::construct_connector_data_old( Box::new(Moneris::new()), - types::Connector::Plaid, + types::Connector::Moneris, api::GetToken::Connector, None, ) diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 82871fcfe25..94dcba750af 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -448,6 +448,8 @@ async fn payments_create_core() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, @@ -713,6 +715,8 @@ async fn payments_create_core_adyen_no_redirect() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 63df5a2decf..66cd41dbc74 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -209,6 +209,8 @@ async fn payments_create_core() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, @@ -483,6 +485,8 @@ async fn payments_create_core_adyen_no_redirect() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 9428751efa8..4755cacf6a3 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -267,6 +267,8 @@ pub enum Flow { ToggleBlocklistGuard, /// Incoming Webhook Receive IncomingWebhookReceive, + /// Recovery incoming webhook receive + RecoveryIncomingWebhookReceive, /// Validate payment method flow ValidatePaymentMethod, /// API Key create flow @@ -544,6 +546,12 @@ pub enum Flow { RelayRetrieve, /// Incoming Relay Webhook Receive IncomingRelayWebhookReceive, + /// Generate Hypersense Token + HypersenseTokenRequest, + /// Verify Hypersense Token + HypersenseVerifyToken, + /// Signout Hypersense Token + HypersenseSignoutToken, /// Payment Method Session Create PaymentMethodSessionCreate, /// Payment Method Session Retrieve diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index c9d250d306b..1e4efb2b350 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -59,6 +59,22 @@ impl PaymentAttemptInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_total_count_of_filtered_payment_attempts( + &self, + _merchant_id: &id_type::MerchantId, + _active_attempt_ids: &[String], + _connector: Option, + _payment_method_type: Option, + _payment_method_subtype: Option, + _authentication_type: Option, + _merchanat_connector_id: Option, + _card_network: Option, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult { + Err(StorageError::MockDbError)? + } + #[cfg(feature = "v1")] async fn find_payment_attempt_by_attempt_id_merchant_id( &self, @@ -208,6 +224,9 @@ impl PaymentAttemptInterface for MockDb { organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, connector_mandate_detail: payment_attempt.connector_mandate_detail, + request_extended_authorization: payment_attempt.request_extended_authorization, + extended_authorization_applied: payment_attempt.extended_authorization_applied, + capture_before: payment_attempt.capture_before, card_discovery: payment_attempt.card_discovery, charges: None, }; diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 6a46702e525..3c90ecbdc7f 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -28,6 +28,24 @@ impl PaymentIntentInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result< + Vec<( + PaymentIntent, + Option, + )>, + StorageError, + > { + Err(StorageError::MockDbError)? + } + #[cfg(all(feature = "v1", feature = "olap"))] async fn filter_payment_intents_by_time_range_constraints( &self, @@ -63,6 +81,17 @@ impl PaymentIntentInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + _merchant_id: &common_utils::id_type::MerchantId, + _constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + #[cfg(all(feature = "v1", feature = "olap"))] async fn get_filtered_payment_intents_attempt( &self, diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 7fb1d3ab80d..96643cefc06 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -507,6 +507,44 @@ impl PaymentAttemptInterface for RouterStore { er.change_context(new_err) }) } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = self + .db_store + .get_replica_pool() + .get() + .await + .change_context(errors::StorageError::DatabaseConnectionError)?; + + DieselPaymentAttempt::get_total_count_of_attempts( + &conn, + merchant_id, + active_attempt_ids, + connector.map(|val| val.to_string()), + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + ) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + }) + } } #[async_trait::async_trait] @@ -603,6 +641,9 @@ impl PaymentAttemptInterface for KVRouterStore { organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), + request_extended_authorization: payment_attempt.request_extended_authorization, + extended_authorization_applied: payment_attempt.extended_authorization_applied, + capture_before: payment_attempt.capture_before, card_discovery: payment_attempt.card_discovery, charges: None, }; @@ -1424,6 +1465,34 @@ impl PaymentAttemptInterface for KVRouterStore { ) .await } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.router_store + .get_total_count_of_filtered_payment_attempts( + merchant_id, + active_attempt_ids, + connector, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + storage_scheme, + ) + .await + } } impl DataModelExt for MandateAmountData { @@ -1572,6 +1641,9 @@ impl DataModelExt for PaymentAttempt { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, @@ -1653,6 +1725,9 @@ impl DataModelExt for PaymentAttempt { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, charges: storage_model.charges, } @@ -1737,6 +1812,9 @@ impl DataModelExt for PaymentAttemptNew { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, } } @@ -1809,6 +1887,9 @@ impl DataModelExt for PaymentAttemptNew { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, } } diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index b64d1aee364..e53a72c8734 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -164,6 +164,27 @@ impl PaymentIntentInterface for KVRouterStore { } } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result)>, StorageError> { + self.router_store + .get_filtered_payment_intents_attempt( + state, + merchant_id, + constraints, + merchant_key_store, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn update_payment_intent( @@ -459,6 +480,23 @@ impl PaymentIntentInterface for KVRouterStore { ) .await } + + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + self.router_store + .get_filtered_active_attempt_ids_for_total_count( + merchant_id, + constraints, + storage_scheme, + ) + .await + } + #[cfg(feature = "v2")] async fn find_payment_intent_by_merchant_reference_id_profile_id( &self, @@ -1086,6 +1124,303 @@ impl PaymentIntentInterface for crate::RouterStore { .await } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result)>, StorageError> { + use diesel::NullableExpressionMethods as _; + use futures::{future::try_join_all, FutureExt}; + + use crate::DataModelExt; + + let conn = connection::pg_connection_read(self).await.switch()?; + let conn = async_bb8_diesel::Connection::as_async_conn(&conn); + let mut query = DieselPaymentIntent::table() + .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) + .left_join( + payment_attempt_schema::table + .on(pi_dsl::active_attempt_id.eq(pa_dsl::id.nullable())), + ) + // Filtering on merchant_id for payment_attempt is not required for v2 as payment_attempt_ids are globally unique + .into_boxed(); + + query = match constraints { + PaymentIntentFetchConstraints::Single { payment_intent_id } => { + query.filter(pi_dsl::id.eq(payment_intent_id.to_owned())) + } + PaymentIntentFetchConstraints::List(params) => { + query = match params.order { + Order { + on: SortOn::Amount, + by: SortBy::Asc, + } => query.order(pi_dsl::amount.asc()), + Order { + on: SortOn::Amount, + by: SortBy::Desc, + } => query.order(pi_dsl::amount.desc()), + Order { + on: SortOn::Created, + by: SortBy::Asc, + } => query.order(pi_dsl::created_at.asc()), + Order { + on: SortOn::Created, + by: SortBy::Desc, + } => query.order(pi_dsl::created_at.desc()), + }; + + if let Some(limit) = params.limit { + query = query.limit(limit.into()); + } + + if let Some(customer_id) = ¶ms.customer_id { + query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); + } + + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } + + query = match (params.starting_at, ¶ms.starting_after_id) { + (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)), + (None, Some(starting_after_id)) => { + // TODO: Fetch partial columns for this query since we only need some columns + let starting_at = self + .find_payment_intent_by_id( + state, + starting_after_id, + merchant_key_store, + storage_scheme, + ) + .await? + .created_at; + query.filter(pi_dsl::created_at.ge(starting_at)) + } + (None, None) => query, + }; + + query = match (params.ending_at, ¶ms.ending_before_id) { + (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)), + (None, Some(ending_before_id)) => { + // TODO: Fetch partial columns for this query since we only need some columns + let ending_at = self + .find_payment_intent_by_id( + state, + ending_before_id, + merchant_key_store, + storage_scheme, + ) + .await? + .created_at; + query.filter(pi_dsl::created_at.le(ending_at)) + } + (None, None) => query, + }; + + query = query.offset(params.offset.into()); + + query = match params.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => query.filter(pi_dsl::amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.le(end)), + _ => query, + }; + + query = match ¶ms.currency { + Some(currency) => query.filter(pi_dsl::currency.eq(*currency)), + None => query, + }; + + query = match ¶ms.connector { + Some(connector) => query.filter(pa_dsl::connector.eq(*connector)), + None => query, + }; + + query = match ¶ms.status { + Some(status) => query.filter(pi_dsl::status.eq(*status)), + None => query, + }; + + query = match ¶ms.payment_method_type { + Some(payment_method_type) => { + query.filter(pa_dsl::payment_method_type_v2.eq(*payment_method_type)) + } + None => query, + }; + + query = match ¶ms.payment_method_subtype { + Some(payment_method_subtype) => { + query.filter(pa_dsl::payment_method_subtype.eq(*payment_method_subtype)) + } + None => query, + }; + + query = match ¶ms.authentication_type { + Some(authentication_type) => { + query.filter(pa_dsl::authentication_type.eq(*authentication_type)) + } + None => query, + }; + + query = match ¶ms.merchant_connector_id { + Some(merchant_connector_id) => query + .filter(pa_dsl::merchant_connector_id.eq(merchant_connector_id.clone())), + None => query, + }; + + if let Some(card_network) = ¶ms.card_network { + query = query.filter(pa_dsl::card_network.eq(card_network.clone())); + } + query + } + }; + + logger::debug!(filter = %diesel::debug_query::(&query).to_string()); + + query + .get_results_async::<( + DieselPaymentIntent, + Option, + )>(conn) + .await + .change_context(StorageError::DecryptionError) + .async_and_then(|output| async { + try_join_all(output.into_iter().map( + |(pi, pa): (_, Option)| async { + let payment_intent = PaymentIntent::convert_back( + state, + pi, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ); + let payment_attempt = pa + .async_map(|val| { + PaymentAttempt::convert_back( + state, + val, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ) + }) + .map(|val| val.transpose()); + + let output = futures::try_join!(payment_intent, payment_attempt); + output.change_context(StorageError::DecryptionError) + }, + )) + .await + }) + .await + .change_context(StorageError::DecryptionError) + } + + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + let conn = connection::pg_connection_read(self).await.switch()?; + let conn = async_bb8_diesel::Connection::as_async_conn(&conn); + let mut query = DieselPaymentIntent::table() + .select(pi_dsl::active_attempt_id) + .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) + .order(pi_dsl::created_at.desc()) + .into_boxed(); + + query = match constraints { + PaymentIntentFetchConstraints::Single { payment_intent_id } => { + query.filter(pi_dsl::id.eq(payment_intent_id.to_owned())) + } + PaymentIntentFetchConstraints::List(params) => { + if let Some(customer_id) = ¶ms.customer_id { + query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); + } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } + + query = match params.starting_at { + Some(starting_at) => query.filter(pi_dsl::created_at.ge(starting_at)), + None => query, + }; + + query = match params.ending_at { + Some(ending_at) => query.filter(pi_dsl::created_at.le(ending_at)), + None => query, + }; + + query = match params.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => query.filter(pi_dsl::amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.le(end)), + _ => query, + }; + + query = match ¶ms.currency { + Some(currency) => query.filter(pi_dsl::currency.eq(*currency)), + None => query, + }; + + query = match ¶ms.status { + Some(status) => query.filter(pi_dsl::status.eq(*status)), + None => query, + }; + + query + } + }; + + db_metrics::track_database_call::<::Table, _, _>( + query.get_results_async::>(conn), + db_metrics::DatabaseOperation::Filter, + ) + .await + .map_err(|er| { + StorageError::DatabaseError( + error_stack::report!(diesel_models::errors::DatabaseError::from(er)) + .attach_printable("Error filtering payment records"), + ) + .into() + }) + } + #[cfg(all(feature = "v1", feature = "olap"))] #[instrument(skip_all)] async fn get_filtered_active_attempt_ids_for_total_count( diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 30de321a4b6..7a5b48f03a6 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -57,7 +57,7 @@ pub struct ConnectorAuthentication { pub jpmorgan: Option, pub mifinity: Option, pub mollie: Option, - pub moneris: Option, + pub moneris: Option, pub multisafepay: Option, pub netcetera: Option, pub nexinets: Option, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Moneris.js b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js new file mode 100644 index 00000000000..7382c6a25d5 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js @@ -0,0 +1,316 @@ +import { getCustomExchange } from "./Modifiers"; + +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "John", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "Joseph", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + amount: 5000, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + amount: 5000, + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + amount: 5000, + shipping_cost: 50, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 5050, + amount: 5000, + net_amount: 5050, + }, + }, + }, + "3DSManualCapture": getCustomExchange({ + Request: { + amount: 5000, + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + "3DSAutoCapture": getCustomExchange({ + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + No3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + Capture: { + Request: { + amount_to_capture: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 5000, + amount_capturable: 0, + amount_received: 5000, + }, + }, + }, + PartialCapture: { + Request: { + amount_to_capture: 2000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 5000, + amount_capturable: 0, + amount_received: 2000, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthMandate: { + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index b3de4db78d4..8a7b559b95e 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -14,6 +14,7 @@ import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; import { connectorDetails as jpmorganConnectorDetails } from "./Jpmorgan.js"; +import { connectorDetails as monerisConnectorDetails } from "./Moneris.js"; import { connectorDetails as nexixpayConnectorDetails } from "./Nexixpay.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as noonConnectorDetails } from "./Noon.js"; @@ -39,6 +40,7 @@ const connectorDetails = { iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, jpmorgan: jpmorganConnectorDetails, + moneris: monerisConnectorDetails, nexixpay: nexixpayConnectorDetails, nmi: nmiConnectorDetails, novalnet: novalnetConnectorDetails, diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index fc25220b80b..2257b9ffe54 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1275,6 +1275,7 @@ Cypress.Commands.add( createPaymentBody.profile_id = profile_id; globalState.set("paymentAmount", createPaymentBody.amount); + globalState.set("setupFutureUsage", createPaymentBody.setup_future_usage); cy.request({ method: "POST", @@ -3054,6 +3055,7 @@ Cypress.Commands.add("listCustomerPMCallTest", (globalState) => { Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { const clientSecret = globalState.get("clientSecret"); + const setupFutureUsage = globalState.get("setupFutureUsage"); cy.request({ method: "GET", @@ -3078,6 +3080,18 @@ Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { response.body.customer_payment_methods[0].payment_method_id, "payment_method_id" ).to.not.be.null; + + if (setupFutureUsage === "off_session") { + expect( + response.body.customer_payment_methods[0].requires_cvv, + "requires_cvv" + ).to.be.false; + } else if (setupFutureUsage === "on_session") { + expect( + response.body.customer_payment_methods[0].requires_cvv, + "requires_cvv" + ).to.be.true; + } } else { // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test expect(response.body) diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index ed2ea37307d..6766d7cb7e6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -94,7 +94,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql new file mode 100644 index 00000000000..a5e982f8ac3 --- /dev/null +++ b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql @@ -0,0 +1,16 @@ +-- Remove the 'request_extended_authorization' column from the 'payment_intent' table +ALTER TABLE payment_intent +DROP COLUMN request_extended_authorization; + +-- Remove the 'request_extended_authorization' and 'extended_authorization_applied' columns from the 'payment_attempt' table +ALTER TABLE payment_attempt +DROP COLUMN request_extended_authorization, +DROP COLUMN extended_authorization_applied; + +-- Remove the 'capture_before' column from the 'payment_attempt' table +ALTER TABLE payment_attempt +DROP COLUMN capture_before; + +-- Remove the 'always_request_extended_authorization' column from the 'business_profile' table +ALTER TABLE business_profile +DROP COLUMN always_request_extended_authorization; \ No newline at end of file diff --git a/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql new file mode 100644 index 00000000000..ab5eac19411 --- /dev/null +++ b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql @@ -0,0 +1,21 @@ +-- stores the flag send by the merchant during payments-create call +ALTER TABLE payment_intent +ADD COLUMN request_extended_authorization boolean; + + +ALTER TABLE payment_attempt +-- stores the flag sent to the connector +ADD COLUMN request_extended_authorization boolean; + +ALTER TABLE payment_attempt +-- Set to true if extended authentication request was successfully processed by the connector +ADD COLUMN extended_authorization_applied boolean; + + +ALTER TABLE payment_attempt +-- stores the flag sent to the connector +ADD COLUMN capture_before timestamp; + +ALTER TABLE business_profile +-- merchant can configure the default value for request_extended_authorization here +ADD COLUMN always_request_extended_authorization boolean; diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index a2bf5872c07..021854ab3f1 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -52,12 +52,12 @@ previous_connector='' find_prev_connector $payment_gateway previous_connector previous_connector_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${previous_connector:0:1})${previous_connector:1}" sed -i'' -e "s|pub mod $previous_connector;|pub mod $previous_connector;\npub mod ${payment_gateway};|" $conn.rs -sed -i'' -e "s/};/, ${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs -sed -i'' -e "0,/};/s/};/, ${payment_gateway}, ${payment_gateway}::${payment_gateway_camelcase},\n};/" $src/connector.rs +sed -i'' -e "s/};/ ${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs +sed -i'' -e "/pub use hyperswitch_connectors::connectors::{/ s/{/{\n ${payment_gateway}, ${payment_gateway}::${payment_gateway_camelcase},/" $src/connector.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tenums::Connector::${payment_gateway_camelcase} => Ok(ConnectorEnum::Old(\Box::new(\connector::${payment_gateway_camelcase}))),|" $src/types/api.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tRoutableConnectors::${payment_gateway_camelcase} => euclid_enums::Connector::${payment_gateway_camelcase},|" crates/api_models/src/routing.rs sed -i'' -e "s/pub $previous_connector: \(.*\)/pub $previous_connector: \1\n\tpub ${payment_gateway}: ConnectorParams,/" crates/hyperswitch_interfaces/src/configs.rs -sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml +sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml config/deployments/integration_test.toml config/deployments/production.toml config/deployments/sandbox.toml sed -r -i'' -e "s/\"$previous_connector\",/\"$previous_connector\",\n \"${payment_gateway}\",/" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/connector_enums.rs sed -i '' -e "/\/\/ Add Separate authentication support for connectors/{N;s/\(.*\)\n/\1\n\t\t\t| Self::${payment_gateway_camelcase}\n/;}" crates/api_models/src/connector_enums.rs @@ -67,9 +67,27 @@ sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" c sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tapi_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations_v2.rs +sed -i'' -e "s/^default_imp_for_connector_request_id!(/default_imp_for_connector_request_id!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "s/^default_imp_for_fraud_check!(/default_imp_for_fraud_check!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "s/^default_imp_for_connector_authentication!(/default_imp_for_connector_authentication!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "/pub struct ConnectorConfig {/ s/{/{\n pub ${payment_gateway}: Option,/" crates/connector_configs/src/connector.rs +sed -i'' -e "/mod utils;/ s/mod utils;/mod ${payment_gateway};\nmod utils;/" crates/router/tests/connectors/main.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_payouts!(/default_imp_for_new_connector_integration_payouts!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_frm!(/default_imp_for_new_connector_integration_frm!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_connector_authentication!(/default_imp_for_new_connector_integration_connector_authentication!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match self {/ s/match self {/match self {\n | Self::${payment_gateway_camelcase}/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match routable_connector {/ s/match routable_connector {/match routable_connector {\n RoutableConnectors::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match self.connector_name {/a\\ + api_enums::Connector::${payment_gateway_camelcase} => {\\ + ${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(self.auth_type)?;\\ + Ok(())\\ + },\\ +" crates/router/src/core/admin.rs + # Remove temporary files created in above step -rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e +rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e config/deployments/integration_test.toml-e config/deployments/production.toml-e config/deployments/sandbox.toml-e temp crates/connector_configs/src/connector.rs-e crates/router/tests/connectors/main.rs-e crates/router/src/core/payments/connector_integration_v2_impls.rs-e cd $conn/ # Generate template files for the connector diff --git a/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql new file mode 100644 index 00000000000..ec6a51ca794 --- /dev/null +++ b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_attempt +DROP COLUMN IF EXISTS feature_metadata; \ No newline at end of file diff --git a/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql new file mode 100644 index 00000000000..bf06a98f7a3 --- /dev/null +++ b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_attempt +ADD COLUMN feature_metadata JSONB; \ No newline at end of file From fd2958ac9f9b2b4307573e16fe84420d77b32f50 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:43:49 +0000 Subject: [PATCH 26/26] chore: run formatter --- crates/diesel_models/src/types.rs | 2 +- .../src/payments/payment_attempt.rs | 26 +++++++++---------- .../operations/proxy_payments_intent.rs | 16 ++++-------- .../router/src/core/payments/transformers.rs | 9 ++++--- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 22d99ca2268..04bd0e925cc 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -43,7 +43,7 @@ impl masking::SerializableSecret for OrderDetailsWithAmount {} common_utils::impl_to_sql_from_sql_json!(OrderDetailsWithAmount); #[cfg(feature = "v2")] -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow,AsExpression)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] pub struct FeatureMetadata { /// Redirection response coming in request as metadata field only for redirection scenarios diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 8341c2d2fc5..8bfafc58c53 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -606,19 +606,17 @@ impl PaymentAttempt { )), }); let payment_method_type_data = payment_intent - .feature_metadata - .as_ref() - .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) - .map(|rrm| { - rrm.payment_method_type - }); + .feature_metadata + .as_ref() + .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) + .map(|rrm| rrm.payment_method_type); let payment_method_subtype_data = payment_intent - .feature_metadata - .as_ref() - .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) - .map(|rrm| rrm.payment_method_subtype); - + .feature_metadata + .as_ref() + .and_then(|fm| fm.payment_revenue_recovery_metadata.as_ref()) + .map(|rrm| rrm.payment_method_subtype); + let authentication_type = payment_intent.authentication_type.unwrap_or_default(); Ok(Self { payment_id: payment_intent.id.clone(), @@ -654,10 +652,12 @@ impl PaymentAttempt { customer_acceptance: None, profile_id: payment_intent.profile_id.clone(), organization_id: payment_intent.organization_id.clone(), - payment_method_type: payment_method_type_data.unwrap_or(common_enums::PaymentMethod::Card), + payment_method_type: payment_method_type_data + .unwrap_or(common_enums::PaymentMethod::Card), payment_method_id: None, connector_payment_id: None, - payment_method_subtype: payment_method_subtype_data.unwrap_or(common_enums::PaymentMethodType::Credit), + payment_method_subtype: payment_method_subtype_data + .unwrap_or(common_enums::PaymentMethodType::Credit), authentication_applied: None, external_reference_id: None, payment_method_billing_address, diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 22f79995342..28cb1e3b3c9 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -1,8 +1,6 @@ use api_models::{ enums::FrmSuggestion, - payments::{ - ConnectorMandateReferenceId, MandateIds, MandateReferenceId, ProxyPaymentsRequest, - }, + payments::{ConnectorMandateReferenceId, MandateIds, MandateReferenceId, ProxyPaymentsRequest}, }; // use diesel_models::payment_attempt::ConnectorMandateReferenceId; use async_trait::async_trait; @@ -76,8 +74,7 @@ impl Operation for &PaymentProx } fn to_get_tracker( &self, - ) -> RouterResult<&(dyn GetTracker + Send + Sync)> - { + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(*self) } fn to_domain(&self) -> RouterResult<&(dyn Domain)> { @@ -85,8 +82,7 @@ impl Operation for &PaymentProx } fn to_update_tracker( &self, - ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> - { + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(*self) } } @@ -102,8 +98,7 @@ impl Operation for PaymentProxy } fn to_get_tracker( &self, - ) -> RouterResult<&(dyn GetTracker + Send + Sync)> - { + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(self) } fn to_domain(&self) -> RouterResult<&dyn Domain> { @@ -111,8 +106,7 @@ impl Operation for PaymentProxy } fn to_update_tracker( &self, - ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> - { + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(self) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 8cbbafcbc5e..1a9ced82ff4 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1719,10 +1719,11 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Merchant connector id is none when constructing response")?; - let error = payment_attempt - .error - .clone() - .map(|from: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails| api_models::payments::ErrorDetails::foreign_from(&from)); + let error = payment_attempt.error.clone().map( + |from: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails| { + api_models::payments::ErrorDetails::foreign_from(&from) + }, + ); let payment_address = self.payment_address; let payment_method_data =