diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index c821feaff575..81faeb925f04 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -234,7 +234,7 @@ impl From for PaymentMethodIntentConfirm { } } } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Debug, serde::Serialize, Clone)] /// This struct is only used by and internal api to migrate payment method pub struct PaymentMethodMigrate { /// Merchant id @@ -283,7 +283,7 @@ pub struct PaymentMethodMigrate { pub billing: Option, /// The connector mandate details of the payment method - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, // The CIT (customer initiated transaction) transaction id associated with the payment method pub network_transaction_id: Option, @@ -307,11 +307,21 @@ pub struct PaymentMethodMigrateResponse { pub network_transaction_id_migrated: Option, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentsMandateReference( pub HashMap, ); +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PayoutsMandateReference( + pub HashMap, +); + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PayoutsMandateReferenceRecord { + pub transfer_method_id: Option, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentsMandateReferenceRecord { pub connector_mandate_id: String, @@ -320,6 +330,95 @@ pub struct PaymentsMandateReferenceRecord { pub original_payment_authorized_currency: Option, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct CommonMandateReference { + pub payments: Option, + pub payouts: Option, +} + +impl<'de> serde::Deserialize<'de> for PaymentMethodMigrate { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Debug, serde::Deserialize)] + struct InnerPaymentMethodMigrate { + pub merchant_id: id_type::MerchantId, + pub payment_method: Option, + pub payment_method_type: Option, + pub payment_method_issuer: Option, + pub payment_method_issuer_code: Option, + pub card: Option, + pub network_token: Option, + pub metadata: Option, + pub customer_id: Option, + pub card_network: Option, + pub bank_transfer: Option, + pub wallet: Option, + pub payment_method_data: Option, + pub billing: Option, + pub connector_mandate_details: Option, + pub network_transaction_id: Option, + } + + let inner = InnerPaymentMethodMigrate::deserialize(deserializer)?; + + let connector_mandate_details = + if let Some(connector_mandate_value) = inner.connector_mandate_details { + if let Ok(common_mandate) = serde_json::from_value::( + connector_mandate_value.clone(), + ) { + Some(common_mandate) + } else if let Ok(payment_mandate_record) = + serde_json::from_value::(connector_mandate_value) + { + Some(CommonMandateReference { + payments: Some(payment_mandate_record), + payouts: None, + }) + } else { + return Err(de::Error::custom("Faild to deserialize PaymentMethod_V2")); + } + } else { + None + }; + + Ok(Self { + merchant_id: inner.merchant_id, + payment_method: inner.payment_method, + payment_method_type: inner.payment_method_type, + payment_method_issuer: inner.payment_method_issuer, + payment_method_issuer_code: inner.payment_method_issuer_code, + card: inner.card, + network_token: inner.network_token, + metadata: inner.metadata, + customer_id: inner.customer_id, + card_network: inner.card_network, + bank_transfer: inner.bank_transfer, + wallet: inner.wallet, + payment_method_data: inner.payment_method_data, + billing: inner.billing, + connector_mandate_details, + network_transaction_id: inner.network_transaction_id, + }) + } +} + +pub fn convert_to_payments_reference( + common_mandate: Option, +) -> Option { + common_mandate.and_then(|cm| cm.payments) +} + +pub fn convert_to_common_reference( + payments_reference: Option, +) -> Option { + payments_reference.map(|payments| CommonMandateReference { + payments: Some(payments), + payouts: None, + }) +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -353,7 +452,9 @@ impl PaymentMethodCreate { payment_method_issuer_code: payment_method_migrate.payment_method_issuer_code, metadata: payment_method_migrate.metadata.clone(), payment_method_data: payment_method_migrate.payment_method_data.clone(), - connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(), + connector_mandate_details: convert_to_payments_reference( + payment_method_migrate.connector_mandate_details.clone(), + ), client_secret: None, billing: payment_method_migrate.billing.clone(), card: card_details, @@ -2359,7 +2460,7 @@ impl }), email: record.email, }), - connector_mandate_details, + connector_mandate_details: convert_to_common_reference(connector_mandate_details), metadata: None, payment_method_issuer_code: None, card_network: None, diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs index da4046ddf01b..fe1289acba03 100644 --- a/crates/common_utils/src/macros.rs +++ b/crates/common_utils/src/macros.rs @@ -143,10 +143,6 @@ macro_rules! impl_to_sql_from_sql_json { }; } - - - - mod id_type { /// Defines an ID type. #[macro_export] diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index ca81e7d57936..00174c2e5bc1 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -3,20 +3,14 @@ use std::collections::HashMap; use common_enums::MerchantStorageScheme; use common_utils::{encryption::Encryption, errors::ParsingError, pii}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; +use error_stack::report; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") ))] use masking::{ExposeInterface, Secret}; -use serde::{de, Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use error_stack::report; -// use diesel::{ -// backend::Backend, -// deserialize, -// deserialize::FromSql, -// // serialize::{Output, ToSql}, -// }; #[cfg(all( any(feature = "v1", feature = "v2"), @@ -135,129 +129,6 @@ impl PaymentMethod { } } - - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - #[derive(serde::Deserialize, Debug)] - pub struct __InnerPaymentMethodData { - pub customer_id: common_utils::id_type::GlobalCustomerId, - pub merchant_id: common_utils::id_type::MerchantId, - pub created_at: PrimitiveDateTime, - pub last_modified: PrimitiveDateTime, - pub payment_method_data: Option, - pub locker_id: Option, - pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, - pub customer_acceptance: Option, - pub status: storage_enums::PaymentMethodStatus, - pub network_transaction_id: Option, - pub client_secret: Option, - pub payment_method_billing_address: Option, - pub updated_by: Option, - pub locker_fingerprint_id: Option, - pub payment_method_type_v2: Option, - pub payment_method_subtype: Option, - pub id: common_utils::id_type::GlobalPaymentMethodId, - pub version: common_enums::ApiVersion, - pub network_token_requestor_reference_id: Option, - pub network_token_locker_id: Option, - pub network_token_payment_method_data: Option, - pub transaction_flow: Option, - } - - let mut deserialize_to_inner = __InnerPaymentMethodData::deserialize(deserializer)?; - - if let Some(connector_mandate_details) = deserialize_to_inner.connector_mandate_details { - match serde_json::from_value::( - connector_mandate_details.clone(), - ) { - Ok(common_mandate_reference) => { - // let common_mandate_reference_value = serde_json::to_value( - // common_mandate_reference, - // ) - // .map_err(|serde_json_error| de::Error::custom(serde_json_error.to_string()))?; - - // deserialize_to_inner.connector_mandate_details = - // Some(common_mandate_reference); - - Ok(Some(Self { - customer_id: deserialize_to_inner.customer_id, - merchant_id: deserialize_to_inner.merchant_id, - created_at: deserialize_to_inner.created_at, - last_modified: deserialize_to_inner.last_modified, - payment_method_data: deserialize_to_inner.payment_method_data, - locker_id: deserialize_to_inner.locker_id, - last_used_at: deserialize_to_inner.last_used_at, - connector_mandate_details: Some(common_mandate_reference), - customer_acceptance: deserialize_to_inner.customer_acceptance, - status: deserialize_to_inner.status, - network_transaction_id: deserialize_to_inner.network_transaction_id, - client_secret: deserialize_to_inner.client_secret, - payment_method_billing_address: deserialize_to_inner.payment_method_billing_address, - updated_by: deserialize_to_inner.updated_by, - locker_fingerprint_id: deserialize_to_inner.locker_fingerprint_id, - payment_method_type_v2: deserialize_to_inner.payment_method_type_v2, - payment_method_subtype: deserialize_to_inner.payment_method_subtype, - id: deserialize_to_inner.id, - version: deserialize_to_inner.version, - network_token_requestor_reference_id: deserialize_to_inner.network_token_requestor_reference_id, - network_token_locker_id: deserialize_to_inner.network_token_locker_id, - network_token_payment_method_data: deserialize_to_inner.network_token_payment_method_data, - transaction_flow: deserialize_to_inner.transaction_flow, - })) - } - Err(_) => { - match serde_json::from_value::( - connector_mandate_details, - ) { - Ok(payment_mandate_reference) => { - let common_mandate_reference = CommonMandateReference { - payments: Some(payment_mandate_reference), - payouts: None, - }; - - // deserialize_to_inner.connector_mandate_details = - // Some(common_mandate_reference); - - Ok(Some(Self { - customer_id: deserialize_to_inner.customer_id, - merchant_id: deserialize_to_inner.merchant_id, - created_at: deserialize_to_inner.created_at, - last_modified: deserialize_to_inner.last_modified, - payment_method_data: deserialize_to_inner.payment_method_data, - locker_id: deserialize_to_inner.locker_id, - last_used_at: deserialize_to_inner.last_used_at, - connector_mandate_details: Some(common_mandate_reference), - customer_acceptance: deserialize_to_inner.customer_acceptance, - status: deserialize_to_inner.status, - network_transaction_id: deserialize_to_inner.network_transaction_id, - client_secret: deserialize_to_inner.client_secret, - payment_method_billing_address: deserialize_to_inner.payment_method_billing_address, - updated_by: deserialize_to_inner.updated_by, - locker_fingerprint_id: deserialize_to_inner.locker_fingerprint_id, - payment_method_type_v2: deserialize_to_inner.payment_method_type_v2, - payment_method_subtype: deserialize_to_inner.payment_method_subtype, - id: deserialize_to_inner.id, - version: deserialize_to_inner.version, - network_token_requestor_reference_id: deserialize_to_inner.network_token_requestor_reference_id, - network_token_locker_id: deserialize_to_inner.network_token_locker_id, - network_token_payment_method_data: deserialize_to_inner.network_token_payment_method_data, - transaction_flow: deserialize_to_inner.transaction_flow, - })) - } - Err(_) => Err(de::Error::custom("Faild to deserialize PaymentMethod_V2"))?, - } - } - } - } else { - Err(de::Error::custom("Faild to deserialize PaymentMethod_V2"))? - } - } - //*/ - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub fn get_id(&self) -> &common_utils::id_type::GlobalPaymentMethodId { &self.id @@ -1168,17 +1039,13 @@ impl std::ops::DerefMut for PayoutsMandateReference { } } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::AsExpression)] +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, diesel::AsExpression)] #[diesel(sql_type = diesel::sql_types::Jsonb)] -// #[derive(Debug, Clone, Serialize, Deserialize, diesel::AsExpression, diesel::FromSqlRow)] -// #[diesel(sql_type = Nullable)] pub struct CommonMandateReference { pub payments: Option, pub payouts: Option, } -// common_utils::impl_to_sql_from_sql_json!(CommonMandateReference); - impl diesel::serialize::ToSql for CommonMandateReference { fn to_sql<'b>( &'b self, @@ -1192,13 +1059,17 @@ impl diesel::serialize::ToSql for Comm } } -impl diesel::deserialize::FromSql for CommonMandateReference +impl diesel::deserialize::FromSql + for CommonMandateReference where serde_json::Value: diesel::deserialize::FromSql, { fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result { // Deserialize into a generic serde_json::Value first - let value = >::from_sql(bytes)?; + let value = >::from_sql(bytes)?; // Try to directly deserialize into CommonMandateReference if let Ok(common_reference) = serde_json::from_value::(value.clone()) { @@ -1212,16 +1083,19 @@ where } // If neither succeeds, return an error - Err(report!(ParsingError::StructParseFailure("CommonMandateReference")) - .attach_printable("Failed to parse JSON into CommonMandateReference or PaymentsMandateReference"))? + Err( + report!(ParsingError::StructParseFailure("CommonMandateReference")).attach_printable( + "Failed to parse JSON into CommonMandateReference or PaymentsMandateReference", + ), + )? } } impl From for CommonMandateReference { fn from(payment_reference: PaymentsMandateReference) -> Self { - Self{ + Self { payments: Some(payment_reference), payouts: None, } } -} \ No newline at end of file +} diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 200204e7e755..43b42dfeb376 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -850,9 +850,9 @@ pub async fn skip_locker_call_and_migrate_payment_method( .clone() .and_then(|val| if val == json!({}) { None } else { Some(true) }) .or_else(|| { - req.connector_mandate_details - .clone() - .and_then(|val| (!val.0.is_empty()).then_some(false)) + req.connector_mandate_details.clone().and_then(|val| { + (!val.payments.unwrap_or_default().0.is_empty()).then_some(false) + }) }), ); diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 78cfdda33b49..e02ecb90d469 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6063,7 +6063,8 @@ where pub async fn validate_merchant_connector_ids_in_connector_mandate_details( state: &SessionState, key_store: &domain::MerchantKeyStore, - connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference, + // connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference, + connector_mandate_details: &api_models::payment_methods::CommonMandateReference, merchant_id: &id_type::MerchantId, card_network: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { @@ -6091,45 +6092,48 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details( }) .collect(); - for (migrating_merchant_connector_id, migrating_connector_mandate_details) in - connector_mandate_details.0.clone() - { - match ( - card_network.clone(), - merchant_connector_account_details_hash_map.get(&migrating_merchant_connector_id), - ) { - (Some(enums::CardNetwork::Discover), Some(merchant_connector_account_details)) => { - if let ("cybersource", None) = ( - merchant_connector_account_details.connector_name.as_str(), - migrating_connector_mandate_details - .original_payment_authorized_amount - .zip( - migrating_connector_mandate_details - .original_payment_authorized_currency, - ), - ) { - Err(errors::ApiErrorResponse::MissingRequiredFields { - field_names: vec![ - "original_payment_authorized_currency", - "original_payment_authorized_amount", - ], + if let Some(payment_mandate_reference) = &connector_mandate_details.payments { + let payments_map = payment_mandate_reference.0.clone(); + for (migrating_merchant_connector_id, migrating_connector_mandate_details) in payments_map { + match ( + card_network.clone(), + merchant_connector_account_details_hash_map.get(&migrating_merchant_connector_id), + ) { + (Some(enums::CardNetwork::Discover), Some(merchant_connector_account_details)) => { + if let ("cybersource", None) = ( + merchant_connector_account_details.connector_name.as_str(), + migrating_connector_mandate_details + .original_payment_authorized_amount + .zip( + migrating_connector_mandate_details + .original_payment_authorized_currency, + ), + ) { + return Err(errors::ApiErrorResponse::MissingRequiredFields { + field_names: vec![ + "original_payment_authorized_currency", + "original_payment_authorized_amount", + ], + }) + .attach_printable(format!( + "Invalid connector_mandate_details provided for connector {:?}", + migrating_merchant_connector_id + ))?; + } + } + (_, Some(_)) => (), + (_, None) => { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "merchant_connector_id", }) - .attach_printable(format!( - "Invalid connector_mandate_details provided for connector {:?}", - migrating_merchant_connector_id - ))? + .attach_printable_lazy(|| { + format!( + "{:?} invalid merchant connector id in connector_mandate_details", + migrating_merchant_connector_id + ) + })? } } - (_, Some(_)) => (), - (_, None) => Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_connector_id", - }) - .attach_printable_lazy(|| { - format!( - "{:?} invalid merchant connector id in connector_mandate_details", - migrating_merchant_connector_id - ) - })?, } } Ok(()) diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index 43115474c06c..a1d69e113c4d 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -24,8 +24,7 @@ use common_utils::{ use diesel_models::{ enums as storage_enums, generic_link::{GenericLinkNew, PayoutLink}, - CommonMandateReference, PaymentsMandateReferenceRecord, - PayoutsMandateReference, PayoutsMandateReferenceRecord, + CommonMandateReference, PayoutsMandateReference, PayoutsMandateReferenceRecord, }; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use diesel_models::{ diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index d34e83d11655..9438e562651e 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -3,7 +3,7 @@ use common_utils::{ crypto::Encryptable, encryption::Encryption, errors::CustomResult, - ext_traits::{AsyncExt, StringExt, ValueExt}, + ext_traits::{AsyncExt, StringExt}, fp_utils, id_type, payout_method_utils as payout_additional, pii, type_name, types::{ keymanager::{Identifier, KeyManagerState}, @@ -209,7 +209,8 @@ pub fn should_create_connector_transfer_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to deserialize connector mandate details")?; - + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + let common_mandate_reference = pm.connector_mandate_details.clone().unwrap_or_default(); if let Some(merchant_connector_id) = connector_data.merchant_connector_id.as_ref() { common_mandate_reference