diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index f624b4f68dd..d365fe0586f 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -17009,6 +17009,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.", @@ -17387,6 +17393,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.", @@ -17926,6 +17938,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.", @@ -18589,6 +18612,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.", @@ -19154,6 +19183,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.", @@ -19630,6 +19670,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.", @@ -21606,6 +21652,11 @@ "description": "Maximum number of auto retries allowed for a payment", "nullable": true, "minimum": 0 + }, + "always_request_extended_authorization": { + "type": "boolean", + "description": "Bool indicating if extended authentication must be requested for all payments", + "nullable": true } }, "additionalProperties": false @@ -21835,6 +21886,11 @@ "format": "int32", "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 } } }, diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 8ba50649236..7fe9f3490a9 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::Secret; @@ -1967,6 +1970,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, } #[nutype::nutype( @@ -2203,6 +2210,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, } #[cfg(feature = "v2")] diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index a28332e7fea..3468f44b1c0 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -39,3 +39,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 c0cf816c68d..625766de8a3 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4,6 +4,7 @@ use std::{ num::NonZeroI64, }; pub mod additional_info; +pub mod trait_impls; use cards::CardNumber; use common_enums::ProductType; #[cfg(feature = "v2")] @@ -16,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}; @@ -32,7 +36,7 @@ use crate::{ disputes, enums as api_enums, ephemeral_key::EphemeralKeyCreateResponse, mandates::RecurringDetails, - refunds, + refunds, ValidateFieldAndGet, }; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -847,6 +851,12 @@ pub struct PaymentsRequest { /// Fee information to be charged on the payment being collected pub charges: 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. @@ -890,6 +900,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 @@ -4516,6 +4538,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. 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..4f3b212594a --- /dev/null +++ b/crates/api_models/src/payments/trait_impls.rs @@ -0,0 +1,24 @@ +use common_enums::enums; +use common_utils::errors; + +use crate::payments; + +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) + | 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.clone()) + } + } +} diff --git a/crates/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index 92fc2f02066..5db5393644a 100644 --- a/crates/common_enums/Cargo.toml +++ b/crates/common_enums/Cargo.toml @@ -13,7 +13,7 @@ openapi = [] payouts = [] [dependencies] -diesel = { version = "2.2.3", features = ["postgres"] } +diesel = { version = "2.2.3", features = ["postgres", "128-column-tables"] } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" strum = { version = "0.26", features = ["derive"] } diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 84a70e44a32..944b326d8d6 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..f9e9b878491 --- /dev/null +++ b/crates/common_utils/src/types/primitive_wrappers.rs @@ -0,0 +1,91 @@ +pub(crate) mod bool_wrappers { + use serde::{Deserialize, Serialize}; + + /// Bool that represents if Extended Authorization is Applied or not + #[derive( + Clone, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct ExtendedAuthorizationAppliedBool(bool); + 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, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct RequestExtendedAuthorizationBool(bool); + 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, 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/diesel_models/Cargo.toml b/crates/diesel_models/Cargo.toml index 120a606d58b..9e195bc3c00 100644 --- a/crates/diesel_models/Cargo.toml +++ b/crates/diesel_models/Cargo.toml @@ -17,7 +17,8 @@ payment_methods_v2 = [] [dependencies] async-bb8-diesel = { git = "https://github.com/jarnura/async-bb8-diesel", rev = "53b4ab901aab7635c8215fd1c2d542c8db443094" } -diesel = { version = "2.2.3", features = ["postgres", "serde_json", "time", "64-column-tables"] } +diesel = { version = "2.2.3", features = ["postgres", "serde_json", "time", "128-column-tables"] } + error-stack = "0.4.1" rustc-hash = "1.1.0" serde = { version = "1.0.197", features = ["derive"] } diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index f4c7b86850e..d6038db6f1b 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, } #[cfg(feature = "v1")] @@ -140,6 +141,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, } #[cfg(feature = "v1")] @@ -179,6 +181,7 @@ impl ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization, } = self; Profile { profile_id: source.profile_id, @@ -238,6 +241,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), } } } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 7760ea76c50..2edc6bf2888 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}; @@ -172,6 +175,9 @@ pub struct PaymentAttempt { pub order_tax_amount: Option, pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, } #[cfg(feature = "v1")] @@ -351,6 +357,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, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 7826e2dadd2..3d2a2bd2ac1 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; @@ -137,6 +141,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, } @@ -354,6 +359,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, } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index d3e560fc048..135a951ef6b 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -213,6 +213,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, } } @@ -852,6 +853,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, } } @@ -932,6 +936,7 @@ diesel::table! { organization_id -> Varchar, tax_details -> Nullable, skip_external_tax_calculation -> Nullable, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index f6bab9071cd..6746e86d64c 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -221,6 +221,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, } } @@ -822,6 +823,9 @@ diesel::table! { shipping_cost -> Nullable, order_tax_amount -> Nullable, connector_mandate_detail -> Nullable, + request_extended_authorization -> Nullable, + extended_authorization_applied -> Nullable, + capture_before -> Nullable, } } @@ -895,6 +899,7 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index cfc9e1c4c8e..53e3b3869b2 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 connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, } #[cfg(feature = "v1")] @@ -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, } } } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 3e8213a3588..8787e57f0f5 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, } #[cfg(feature = "v1")] @@ -98,6 +99,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, } #[cfg(feature = "v1")] @@ -145,6 +147,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, } } } @@ -293,6 +296,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization: None, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -332,6 +336,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, }, ProfileUpdate::DynamicRoutingAlgorithmUpdate { dynamic_routing_algorithm, @@ -369,6 +374,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, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -406,6 +412,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, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -443,6 +450,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, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -480,6 +488,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, }, } } @@ -536,6 +545,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, }) } @@ -604,6 +614,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, }) } .await diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 4cb93403219..056071449af 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -9,7 +9,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")] @@ -101,6 +101,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, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 4ca6084c958..f6c14b2190b 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::{ @@ -498,6 +499,9 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub request_extended_authentication: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, } #[cfg(feature = "v1")] @@ -743,6 +747,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, } #[cfg(feature = "v1")] @@ -1445,6 +1452,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_authentication, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, }) } @@ -1526,6 +1536,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_authentication: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, }) } .await @@ -1608,6 +1621,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_authentication, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index b0ffe519d63..1547356f55b 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1557,6 +1557,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, }) } @@ -1645,6 +1646,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, }) } @@ -1708,6 +1710,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, }) } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 6d4dde53082..57fe71a7d64 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3555,6 +3555,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, })) } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 8435f09e8f3..4d7027d3879 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3476,6 +3476,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, }; let req_cs = Some("1".to_string()); @@ -3546,6 +3547,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, }; let req_cs = Some("1".to_string()); @@ -3614,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, }; let req_cs = Some("1".to_string()); @@ -4150,6 +4153,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, } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 697c06603da..9439133b4af 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1278,6 +1278,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, }, additional_pm_data, @@ -1485,6 +1488,7 @@ impl PaymentCreate { shipping_cost: request.shipping_cost, tax_details: None, skip_external_tax_calculation, + request_extended_authorization: None, psd2_sca_exemption_type: request.psd2_sca_exemption_type, }) } diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index fed28b68011..60ba609828a 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -654,6 +654,9 @@ pub fn make_new_payment_attempt( charge_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(), } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 32697508101..fd6023ea244 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1891,6 +1891,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, }; services::ApplicationResponse::JsonWithHeaders((payments_response, headers)) @@ -2145,6 +2147,8 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay updated: None, charges: 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, diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index c81fc7ceb48..9b464c497cf 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -170,6 +170,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, }) } } @@ -363,5 +364,6 @@ 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, })) } diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 0291374d54f..453b8e08f55 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -217,6 +217,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(), }; let store = state @@ -301,6 +304,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(), }; let store = state .stores @@ -398,6 +404,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(), }; let store = state .stores diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 600d610e428..f5d43c72be7 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, }; let (connector_transaction_id, connector_transaction_data) = @@ -360,6 +361,9 @@ pub async fn generate_sample_data( order_tax_amount: None, connector_transaction_data, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 68ca08c8bd3..ea53bdaa31e 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -447,6 +447,8 @@ async fn payments_create_core() { charges: 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, @@ -709,6 +711,8 @@ async fn payments_create_core_adyen_no_redirect() { charges: 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 90fe3a1f847..90f1f5c00ac 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -208,6 +208,8 @@ async fn payments_create_core() { charges: 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, @@ -479,6 +481,8 @@ async fn payments_create_core_adyen_no_redirect() { charges: 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/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 83691f46129..4f3516769e0 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -195,6 +195,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_authentication: payment_attempt.request_extended_authorization, + extended_authorization_applied: payment_attempt.extended_authorization_applied, + capture_before: payment_attempt.capture_before, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 9b9121952b1..a4ce3260af1 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -564,6 +564,13 @@ 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_authentication: payment_attempt + .request_extended_authorization + .clone(), + extended_authorization_applied: payment_attempt + .extended_authorization_applied + .clone(), + capture_before: payment_attempt.capture_before, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1510,6 +1517,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_authentication, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, } } @@ -1586,6 +1596,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_authentication: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, } } } @@ -1669,6 +1682,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, } } @@ -1741,6 +1757,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, } } } 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;