From e4210b0eb5f938cf85d3fdce971a695f4048ce20 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:32:20 +0530 Subject: [PATCH] feat(core): api ,domain and diesel model changes for extended authorization (#6607) --- api-reference-v2/openapi_spec.json | 22 +++ api-reference/openapi_spec.json | 56 ++++++ crates/api_models/src/admin.rs | 13 +- crates/api_models/src/lib.rs | 9 + crates/api_models/src/payments.rs | 34 +++- crates/api_models/src/payments/trait_impls.rs | 29 ++++ crates/common_enums/Cargo.toml | 2 +- crates/common_utils/src/types.rs | 7 + .../src/types/primitive_wrappers.rs | 107 ++++++++++++ crates/diesel_models/Cargo.toml | 3 +- crates/diesel_models/src/business_profile.rs | 9 +- crates/diesel_models/src/payment_attempt.rs | 18 +- crates/diesel_models/src/payment_intent.rs | 9 +- crates/diesel_models/src/schema.rs | 5 + crates/diesel_models/src/schema_v2.rs | 5 + crates/diesel_models/src/user/sample_data.rs | 11 +- .../src/business_profile.rs | 14 +- .../hyperswitch_domain_models/src/payments.rs | 3 +- .../src/payments/payment_attempt.rs | 160 ++++++++++++------ .../src/payments/payment_intent.rs | 4 + crates/router/src/core/admin.rs | 1 + crates/router/src/core/payments/helpers.rs | 6 + .../payments/operations/payment_create.rs | 4 + crates/router/src/core/payments/retry.rs | 3 + .../router/src/core/payments/transformers.rs | 4 + crates/router/src/types/api/admin.rs | 2 + .../src/types/storage/payment_attempt.rs | 9 + crates/router/src/utils/user/sample_data.rs | 4 + crates/router/tests/payments.rs | 4 + crates/router/tests/payments2.rs | 4 + .../src/mock_db/payment_attempt.rs | 3 + .../src/payments/payment_attempt.rs | 15 ++ .../down.sql | 16 ++ .../up.sql | 21 +++ 34 files changed, 557 insertions(+), 59 deletions(-) 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 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 diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index ee5654cfb6b1..ed6bade898ba 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -15139,6 +15139,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.", @@ -16039,6 +16050,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.", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d7b86b05a194..8ccc891e0387 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -17246,6 +17246,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.", @@ -17632,6 +17638,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.", @@ -18179,6 +18191,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.", @@ -18842,6 +18865,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.", @@ -19415,6 +19444,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.", @@ -19891,6 +19931,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.", @@ -21876,6 +21922,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." @@ -22115,6 +22166,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.", diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index ce6025f493ef..3ea9796a6683 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}; @@ -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, + /// Indicates if click to pay is enabled or not. #[serde(default)] pub is_click_to_pay_enabled: bool, @@ -2219,6 +2226,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, diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index a28332e7fea0..3468f44b1c01 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 2464cdf62dbc..6ef01037ebc7 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)] @@ -998,6 +1002,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. @@ -1057,6 +1067,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 @@ -4678,6 +4700,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 000000000000..5df07a24a988 --- /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/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index 92fc2f02066b..5db5393644aa 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 0d39841fe09b..b67f33bdd271 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 000000000000..850f6a7cb5fb --- /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/diesel_models/Cargo.toml b/crates/diesel_models/Cargo.toml index 5da67eb30759..fba59b384134 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 bafd1897200c..2c13d8f782b1 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, } @@ -144,6 +145,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, } @@ -185,6 +187,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; @@ -246,6 +249,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 @@ -304,6 +309,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, } @@ -517,6 +523,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 diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 6ddc26d49bdb..29c65d549bfa 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}; @@ -94,6 +97,9 @@ pub struct PaymentAttempt { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, } #[cfg(feature = "v1")] @@ -172,6 +178,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")] @@ -278,6 +287,9 @@ pub struct PaymentAttemptNew { pub payment_method_subtype: storage_enums::PaymentMethodType, pub id: id_type::GlobalAttemptId, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, } #[cfg(feature = "v1")] @@ -351,6 +363,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 69743dbf1ca6..127254397982 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, } @@ -138,6 +143,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, } @@ -363,6 +369,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 split_payments: Option, } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 178f5600542a..25a4570e444a 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -214,6 +214,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, } @@ -886,6 +887,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, } } @@ -966,6 +970,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, } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index da2298d934bb..8d02c77de97f 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -222,6 +222,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, } @@ -856,6 +857,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, } } @@ -929,6 +933,7 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index cfc9e1c4c8ed..53e3b3869b2c 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 1df474f18d5f..7c27277b1193 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, } @@ -100,6 +101,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, } @@ -149,6 +151,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, } @@ -303,6 +306,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, } @@ -344,6 +348,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, }, @@ -383,6 +388,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, }, @@ -422,6 +428,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, }, @@ -461,6 +468,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, }, @@ -500,6 +508,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, }, @@ -558,6 +567,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, }) @@ -628,6 +638,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, }) @@ -1280,6 +1291,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, }) diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 075c94c2eae1..1e204c9ac2c1 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")] @@ -105,6 +105,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 591ccc4ff981..63681497cdd6 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::{ @@ -508,6 +509,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, } #[cfg(feature = "v1")] @@ -753,6 +757,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")] @@ -1455,6 +1462,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, }) } @@ -1536,6 +1546,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, }) } .await @@ -1618,6 +1631,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, }) } } @@ -1762,6 +1778,9 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_billing_address: payment_method_billing_address.map(Encryption::from), connector_payment_data, connector_mandate_detail, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, }) } @@ -1883,9 +1902,53 @@ 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, + charge_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_mandate_detail, + } = 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")) @@ -1894,69 +1957,70 @@ 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, + net_amount: 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, - charge_id: self.charge_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, + authentication_connector, + authentication_id, + fingerprint_id, + charge_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_mandate_detail: self.connector_mandate_detail, + 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, + connector_mandate_detail, + extended_authorization_applied: None, + request_extended_authorization: None, + capture_before: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 4fd861acb65c..b2902a89d970 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1338,6 +1338,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config, routing_algorithm_id, psd2_sca_exemption_type: None, + request_extended_authorization: None, split_payments: None, }) } @@ -1601,6 +1602,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, }) } @@ -1689,6 +1691,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, }) } @@ -1753,6 +1756,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 6cca6e9001e9..33c1023a08b8 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3698,6 +3698,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, })) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index fa5f5e03e478..94345d162f5e 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3501,6 +3501,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()); @@ -3571,6 +3572,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()); @@ -3639,6 +3641,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()); @@ -4172,6 +4175,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 6a676af47b72..0904962fca88 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1286,6 +1286,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, @@ -1483,6 +1486,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 7ae536a1f193..e835b453b0b6 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 92486320cdee..3d485010f46b 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2081,6 +2081,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)) @@ -2334,6 +2336,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, diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index ff72af484016..cad2277da4fd 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, }) @@ -382,6 +383,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, })) diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 0291374d54f6..453b8e08f559 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 09cd7cfa1030..d6fd52f9d032 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 37fb57e05f34..120c95a20bd0 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -447,6 +447,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, @@ -709,6 +711,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 415ea07cb9f2..b7fa76e6057d 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -208,6 +208,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, @@ -479,6 +481,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/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 0415625f7ebb..1818462bea22 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_authorization: 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 06c7fefe85fa..c6ca29d87011 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -564,6 +564,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, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1511,6 +1514,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, } } @@ -1587,6 +1593,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, } } } @@ -1670,6 +1679,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, } } @@ -1742,6 +1754,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 000000000000..a5e982f8ac3e --- /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 000000000000..ab5eac19411b --- /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;