diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 61cf7b26ac..62d6ba5000 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -19677,7 +19677,6 @@ "redirect_to_merchant_with_http_post", "is_tax_connector_enabled", "is_network_tokenization_enabled", - "should_collect_cvv_during_payment", "is_click_to_pay_enabled", "is_clear_pan_retries_enabled" ], @@ -19858,7 +19857,8 @@ }, "should_collect_cvv_during_payment": { "type": "boolean", - "description": "Indicates if CVV should be collected during payment or not." + "description": "Indicates if CVV should be collected during payment or not.", + "nullable": true }, "is_click_to_pay_enabled": { "type": "boolean", diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 5d3f7c5dc8..f27a09ffe8 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1,7 +1,6 @@ use std::collections::{HashMap, HashSet}; -#[cfg(feature = "v1")] -use common_types::primitive_wrappers::AlwaysRequestExtendedAuthorization; +use common_types::primitive_wrappers; use common_utils::{ consts, crypto::Encryptable, @@ -1966,7 +1965,8 @@ pub struct ProfileCreate { /// Bool indicating if extended authentication must be requested for all payments #[schema(value_type = Option)] - pub always_request_extended_authorization: Option, + pub always_request_extended_authorization: + Option, /// Indicates if click to pay is enabled or not. #[serde(default)] @@ -2256,7 +2256,8 @@ pub struct ProfileResponse { /// Bool indicating if extended authentication must be requested for all payments #[schema(value_type = Option)] - pub always_request_extended_authorization: Option, + pub always_request_extended_authorization: + Option, /// Indicates if click to pay is enabled or not. #[schema(default = false, example = false)] @@ -2397,7 +2398,9 @@ pub struct ProfileResponse { pub is_network_tokenization_enabled: bool, /// Indicates if CVV should be collected during payment or not. - pub should_collect_cvv_during_payment: bool, + #[schema(value_type = Option)] + pub should_collect_cvv_during_payment: + Option, /// Indicates if click to pay is enabled or not. #[schema(default = false, example = false)] diff --git a/crates/common_types/src/payment_methods.rs b/crates/common_types/src/payment_methods.rs index 0a5b79de75..4b05c3747e 100644 --- a/crates/common_types/src/payment_methods.rs +++ b/crates/common_types/src/payment_methods.rs @@ -4,6 +4,7 @@ use diesel::{ backend::Backend, deserialize, deserialize::FromSql, + serialize::ToSql, sql_types::{Json, Jsonb}, AsExpression, Queryable, }; @@ -28,6 +29,69 @@ pub struct PaymentMethodsEnabled { pub payment_method_subtypes: Option>, } +// Custom FromSql implmentation to handle deserialization of v1 data format +impl FromSql for PaymentMethodsEnabled { + fn from_sql(bytes: ::RawValue<'_>) -> deserialize::Result { + let helper: PaymentMethodsEnabledHelper = serde_json::from_slice(bytes.as_bytes()) + .map_err(|e| Box::new(diesel::result::Error::DeserializationError(Box::new(e))))?; + Ok(helper.into()) + } +} + +// In this ToSql implementation, we are directly serializing the PaymentMethodsEnabled struct to JSON +impl ToSql for PaymentMethodsEnabled { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>, + ) -> diesel::serialize::Result { + let value = serde_json::to_value(self)?; + // the function `reborrow` only works in case of `Pg` backend. But, in case of other backends + // please refer to the diesel migration blog: + // https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations + >::to_sql(&value, &mut out.reborrow()) + } +} + +// Intermediate type to handle deserialization of v1 data format of PaymentMethodsEnabled +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum PaymentMethodsEnabledHelper { + V2 { + payment_method_type: common_enums::PaymentMethod, + payment_method_subtypes: Option>, + }, + V1 { + payment_method: common_enums::PaymentMethod, + payment_method_types: Option>, + }, +} + +impl From for PaymentMethodsEnabled { + fn from(helper: PaymentMethodsEnabledHelper) -> Self { + match helper { + PaymentMethodsEnabledHelper::V2 { + payment_method_type, + payment_method_subtypes, + } => Self { + payment_method_type, + payment_method_subtypes, + }, + PaymentMethodsEnabledHelper::V1 { + payment_method, + payment_method_types, + } => Self { + payment_method_type: payment_method, + payment_method_subtypes: payment_method_types.map(|subtypes| { + subtypes + .into_iter() + .map(RequestPaymentMethodTypes::from) + .collect() + }), + }, + } + } +} + impl PaymentMethodsEnabled { /// Get payment_method_type #[cfg(feature = "v2")] @@ -92,6 +156,35 @@ pub struct RequestPaymentMethodTypes { pub installment_payment_enabled: bool, } +impl From for RequestPaymentMethodTypes { + fn from(value: RequestPaymentMethodTypesV1) -> Self { + Self { + payment_method_subtype: value.payment_method_type, + payment_experience: value.payment_experience, + card_networks: value.card_networks, + accepted_currencies: value.accepted_currencies, + accepted_countries: value.accepted_countries, + minimum_amount: value.minimum_amount, + maximum_amount: value.maximum_amount, + recurring_enabled: value.recurring_enabled, + installment_payment_enabled: value.installment_payment_enabled, + } + } +} + +#[derive(serde::Deserialize)] +struct RequestPaymentMethodTypesV1 { + pub payment_method_type: common_enums::PaymentMethodType, + pub payment_experience: Option, + pub card_networks: Option>, + pub accepted_currencies: Option, + pub accepted_countries: Option, + pub minimum_amount: Option, + pub maximum_amount: Option, + pub recurring_enabled: bool, + pub installment_payment_enabled: bool, +} + impl RequestPaymentMethodTypes { ///Get payment_method_subtype pub fn get_payment_method_type(&self) -> Option { @@ -153,8 +246,6 @@ where } } -common_utils::impl_to_sql_from_sql_json!(PaymentMethodsEnabled); - /// The network tokenization configuration for creating the payment method session #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub struct NetworkTokenization { diff --git a/crates/common_types/src/primitive_wrappers.rs b/crates/common_types/src/primitive_wrappers.rs index d86a060784..a3eee31660 100644 --- a/crates/common_types/src/primitive_wrappers.rs +++ b/crates/common_types/src/primitive_wrappers.rs @@ -128,4 +128,46 @@ mod bool_wrappers { bool::from_sql(value).map(Self) } } + + /// Bool that represents if Cvv should be collected during payment or not. Default is true + #[derive( + Clone, Copy, Debug, Eq, PartialEq, diesel::expression::AsExpression, Serialize, Deserialize, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct ShouldCollectCvvDuringPayment(bool); + impl Deref for ShouldCollectCvvDuringPayment { + type Target = bool; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + impl diesel::serialize::ToSql for ShouldCollectCvvDuringPayment + 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 ShouldCollectCvvDuringPayment + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } + + impl Default for ShouldCollectCvvDuringPayment { + /// Default for `ShouldCollectCvvDuringPayment` is `true` + fn default() -> Self { + Self(true) + } + } } diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 94da0f0438..1f7d2b0981 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_types::primitive_wrappers::AlwaysRequestExtendedAuthorization; +use common_types::primitive_wrappers; use common_utils::{encryption::Encryption, pii}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use masking::Secret; @@ -58,7 +58,8 @@ 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 always_request_extended_authorization: + Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -162,7 +163,8 @@ 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 always_request_extended_authorization: + Option, pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, @@ -341,7 +343,8 @@ 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 always_request_extended_authorization: + Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -359,7 +362,8 @@ pub struct Profile { pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, pub three_ds_decision_manager_config: Option, - pub should_collect_cvv_during_payment: bool, + pub should_collect_cvv_during_payment: + Option, } impl Profile { @@ -425,7 +429,8 @@ pub struct ProfileNew { pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, pub three_ds_decision_manager_config: Option, - pub should_collect_cvv_during_payment: bool, + pub should_collect_cvv_during_payment: + Option, pub id: common_utils::id_type::ProfileId, } @@ -477,7 +482,8 @@ pub struct ProfileUpdateInternal { pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, pub three_ds_decision_manager_config: Option, - pub should_collect_cvv_during_payment: Option, + pub should_collect_cvv_during_payment: + Option, } #[cfg(feature = "v2")] @@ -584,7 +590,7 @@ impl ProfileUpdateInternal { .or(source.payout_routing_algorithm_id), default_fallback_routing: default_fallback_routing.or(source.default_fallback_routing), should_collect_cvv_during_payment: should_collect_cvv_during_payment - .unwrap_or(source.should_collect_cvv_during_payment), + .or(source.should_collect_cvv_during_payment), version: source.version, dynamic_routing_algorithm: None, is_network_tokenization_enabled: is_network_tokenization_enabled diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index d63f6295ea..ad2766c242 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -231,7 +231,7 @@ diesel::table! { payout_routing_algorithm_id -> Nullable, default_fallback_routing -> Nullable, three_ds_decision_manager_config -> Nullable, - should_collect_cvv_during_payment -> Bool, + should_collect_cvv_during_payment -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index c9e3b4f51f..e20ff18838 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -1,5 +1,5 @@ use common_enums::enums as api_enums; -use common_types::primitive_wrappers::AlwaysRequestExtendedAuthorization; +use common_types::primitive_wrappers; use common_utils::{ crypto::{OptionalEncryptableName, OptionalEncryptableValue}, date_time, @@ -57,7 +57,8 @@ 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 always_request_extended_authorization: + Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -107,7 +108,8 @@ 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 always_request_extended_authorization: + Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -884,7 +886,8 @@ pub struct Profile { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, - pub should_collect_cvv_during_payment: bool, + pub should_collect_cvv_during_payment: + Option, pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub version: common_enums::ApiVersion, @@ -934,7 +937,8 @@ pub struct ProfileSetter { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, - pub should_collect_cvv_during_payment: bool, + pub should_collect_cvv_during_payment: + Option, pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub is_network_tokenization_enabled: bool, @@ -1083,7 +1087,7 @@ pub enum ProfileUpdate { is_network_tokenization_enabled: bool, }, CollectCvvDuringPaymentUpdate { - should_collect_cvv_during_payment: bool, + should_collect_cvv_during_payment: primitive_wrappers::ShouldCollectCvvDuringPayment, }, DecisionManagerRecordUpdate { three_ds_decision_manager_config: common_types::payments::DecisionManagerRecord, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 7bf25e689a..3dd2accbd1 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3946,7 +3946,7 @@ impl ProfileCreateBridge for api::ProfileCreate { .or(Some(common_utils::consts::DEFAULT_ORDER_FULFILLMENT_TIME)), order_fulfillment_time_origin: self.order_fulfillment_time_origin, default_fallback_routing: None, - should_collect_cvv_during_payment: false, + should_collect_cvv_during_payment: None, tax_connector_id: self.tax_connector_id, is_tax_connector_enabled: self.is_tax_connector_enabled, is_network_tokenization_enabled: self.is_network_tokenization_enabled, diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 7c3b74bda4..d98f834e7b 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -168,6 +168,7 @@ pub trait AccountsStorageInterface: + merchant_account::MerchantAccountInterface + business_profile::ProfileInterface + merchant_connector_account::MerchantConnectorAccountInterface + + merchant_key_store::MerchantKeyStoreInterface + 'static { } diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index aaeba6085a..0a6fe4c3bb 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -63,7 +63,7 @@ impl MerchantKeyStoreInterface for Store { merchant_key_store: domain::MerchantKeyStore, key: &Secret>, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_accounts_connection_write(self).await?; let merchant_id = merchant_key_store.merchant_id.clone(); merchant_key_store .construct_new() @@ -85,7 +85,7 @@ impl MerchantKeyStoreInterface for Store { key: &Secret>, ) -> CustomResult { let fetch_func = || async { - let conn = connection::pg_connection_read(self).await?; + let conn = connection::pg_accounts_connection_read(self).await?; diesel_models::merchant_key_store::MerchantKeyStore::find_by_merchant_id( &conn, @@ -127,7 +127,7 @@ impl MerchantKeyStoreInterface for Store { merchant_id: &common_utils::id_type::MerchantId, ) -> CustomResult { let delete_func = || async { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_accounts_connection_write(self).await?; diesel_models::merchant_key_store::MerchantKeyStore::delete_by_merchant_id( &conn, merchant_id, @@ -163,7 +163,7 @@ impl MerchantKeyStoreInterface for Store { key: &Secret>, ) -> CustomResult, errors::StorageError> { let fetch_func = || async { - let conn = connection::pg_connection_read(self).await?; + let conn = connection::pg_accounts_connection_read(self).await?; diesel_models::merchant_key_store::MerchantKeyStore::list_multiple_key_stores( &conn, @@ -190,7 +190,7 @@ impl MerchantKeyStoreInterface for Store { from: u32, to: u32, ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_read(self).await?; + let conn = connection::pg_accounts_connection_read(self).await?; let stores = diesel_models::merchant_key_store::MerchantKeyStore::list_all_key_stores( &conn, from, to, ) diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs index 2f580b7d3e..a47beaecad 100644 --- a/crates/router/src/types/payment_methods.rs +++ b/crates/router/src/types/payment_methods.rs @@ -9,6 +9,8 @@ use cards::CardNumber; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use cards::{CardNumber, NetworkToken}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use common_types::primitive_wrappers; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use common_utils::generate_id; use common_utils::id_type; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -123,7 +125,7 @@ impl VaultingInterface for VaultDelete { pub struct SavedPMLPaymentsInfo { pub payment_intent: storage::PaymentIntent, pub profile: domain::Profile, - pub collect_cvv_during_payment: bool, + pub collect_cvv_during_payment: Option, pub off_session_payment_flag: bool, pub is_connector_agnostic_mit_enabled: bool, } diff --git a/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/down.sql b/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/down.sql index 06b78440d1..6a10d16c5c 100644 --- a/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/down.sql +++ b/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/down.sql @@ -3,10 +3,6 @@ ALTER TABLE customers ALTER COLUMN status DROP NOT NULL, ALTER COLUMN status DROP DEFAULT; ----------------------business_profile--------------------- -ALTER TABLE business_profile ALTER COLUMN should_collect_cvv_during_payment DROP NOT NULL; - - ALTER TABLE merchant_connector_account ALTER COLUMN profile_id DROP NOT NULL; diff --git a/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/up.sql b/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/up.sql index 3641d2bdab..83b9b97cdc 100644 --- a/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081837_add_not_null_constraints_to_v2_columns/up.sql @@ -4,9 +4,6 @@ ALTER TABLE customers ALTER COLUMN status SET NOT NULL, ALTER COLUMN status SET DEFAULT 'active'; ----------------------business_profile--------------------- -ALTER TABLE business_profile ALTER COLUMN should_collect_cvv_during_payment SET NOT NULL; - -- This migration is to make profile_id mandatory in mca table ALTER TABLE merchant_connector_account ALTER COLUMN profile_id SET NOT NULL;