From 74f3721ccd0cceac6ae8e751cb83784d2f00a283 Mon Sep 17 00:00:00 2001 From: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:05:54 +0530 Subject: [PATCH] fix(core): Add column mandate_data for storing the details of a mandate in PaymentAttempt (#3606) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/data_models/src/mandates.rs | 8 --- .../src/payments/payment_attempt.rs | 12 +++- crates/diesel_models/src/enums.rs | 29 +++------ crates/diesel_models/src/payment_attempt.rs | 6 +- crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/user/sample_data.rs | 10 ++- .../router/src/core/payment_methods/cards.rs | 62 ++++++++----------- crates/router/src/core/payments/helpers.rs | 1 + .../payments/operations/payment_confirm.rs | 23 +------ .../payments/operations/payment_create.rs | 26 +++----- .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 51 +++++---------- .../down.sql | 2 + .../up.sql | 3 + 14 files changed, 91 insertions(+), 144 deletions(-) create mode 100644 migrations/2024-02-08-142804_add_mandate_data_payment_attempt/down.sql create mode 100644 migrations/2024-02-08-142804_add_mandate_data_payment_attempt/up.sql diff --git a/crates/data_models/src/mandates.rs b/crates/data_models/src/mandates.rs index 319a78cf661f..b4478f84ee94 100644 --- a/crates/data_models/src/mandates.rs +++ b/crates/data_models/src/mandates.rs @@ -13,7 +13,6 @@ use time::PrimitiveDateTime; #[serde(rename_all = "snake_case")] pub struct MandateDetails { pub update_mandate_id: Option, - pub mandate_type: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] @@ -23,13 +22,6 @@ pub enum MandateDataType { MultiUse(Option), } -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -#[serde(untagged)] -pub enum MandateTypeDetails { - MandateType(MandateDataType), - MandateDetails(MandateDetails), -} #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct MandateAmountData { pub amount: i64, diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index 2b5705c155b7..084b0ef251ec 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -4,7 +4,11 @@ use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use super::PaymentIntent; -use crate::{errors, mandates::MandateTypeDetails, ForeignIDRef}; +use crate::{ + errors, + mandates::{MandateDataType, MandateDetails}, + ForeignIDRef, +}; #[async_trait::async_trait] pub trait PaymentAttemptInterface { @@ -143,7 +147,7 @@ pub struct PaymentAttempt { pub straight_through_algorithm: Option, pub preprocessing_step_id: Option, // providing a location to store mandate details intermediately for transaction - pub mandate_details: Option, + pub mandate_details: Option, pub error_reason: Option, pub multiple_capture_count: Option, // reference to the payment at connector side @@ -155,6 +159,7 @@ pub struct PaymentAttempt { pub merchant_connector_id: Option, pub unified_code: Option, pub unified_message: Option, + pub mandate_data: Option, } impl PaymentAttempt { @@ -221,7 +226,7 @@ pub struct PaymentAttemptNew { pub business_sub_label: Option, pub straight_through_algorithm: Option, pub preprocessing_step_id: Option, - pub mandate_details: Option, + pub mandate_details: Option, pub error_reason: Option, pub connector_response_reference_id: Option, pub multiple_capture_count: Option, @@ -232,6 +237,7 @@ pub struct PaymentAttemptNew { pub merchant_connector_id: Option, pub unified_code: Option, pub unified_message: Option, + pub mandate_data: Option, } impl PaymentAttemptNew { diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index babffdbc4a86..d65f6c5efa98 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -174,20 +174,8 @@ use diesel::{ #[serde(rename_all = "snake_case")] pub struct MandateDetails { pub update_mandate_id: Option, - pub mandate_type: Option, } - -#[derive( - serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, -)] -#[diesel(sql_type = Jsonb)] -#[serde(rename_all = "snake_case")] -pub enum MandateDataType { - SingleUse(MandateAmountData), - MultiUse(Option), -} - -impl FromSql for MandateDataType +impl FromSql for MandateDetails where serde_json::Value: FromSql, { @@ -197,7 +185,7 @@ where } } -impl ToSql for MandateDataType +impl ToSql for MandateDetails where serde_json::Value: ToSql, { @@ -210,19 +198,17 @@ where >::to_sql(&value, &mut out.reborrow()) } } - #[derive( serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, )] #[diesel(sql_type = Jsonb)] -#[serde(untagged)] #[serde(rename_all = "snake_case")] -pub enum MandateTypeDetails { - MandateType(MandateDataType), - MandateDetails(MandateDetails), +pub enum MandateDataType { + SingleUse(MandateAmountData), + MultiUse(Option), } -impl FromSql for MandateTypeDetails +impl FromSql for MandateDataType where serde_json::Value: FromSql, { @@ -231,7 +217,8 @@ where Ok(serde_json::from_value(value)?) } } -impl ToSql for MandateTypeDetails + +impl ToSql for MandateDataType where serde_json::Value: ToSql, { diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index d286cc312bce..9af4595c9f46 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -51,7 +51,7 @@ pub struct PaymentAttempt { pub straight_through_algorithm: Option, pub preprocessing_step_id: Option, // providing a location to store mandate details intermediately for transaction - pub mandate_details: Option, + pub mandate_details: Option, pub error_reason: Option, pub multiple_capture_count: Option, // reference to the payment at connector side @@ -64,6 +64,7 @@ pub struct PaymentAttempt { pub unified_code: Option, pub unified_message: Option, pub net_amount: Option, + pub mandate_data: Option, } impl PaymentAttempt { @@ -126,7 +127,7 @@ pub struct PaymentAttemptNew { pub business_sub_label: Option, pub straight_through_algorithm: Option, pub preprocessing_step_id: Option, - pub mandate_details: Option, + pub mandate_details: Option, pub error_reason: Option, pub connector_response_reference_id: Option, pub multiple_capture_count: Option, @@ -138,6 +139,7 @@ pub struct PaymentAttemptNew { pub unified_code: Option, pub unified_message: Option, pub net_amount: Option, + pub mandate_data: Option, } impl PaymentAttemptNew { diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index c9887e1770fc..6c28677432b1 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -688,6 +688,7 @@ diesel::table! { #[max_length = 1024] unified_message -> Nullable, net_amount -> Nullable, + mandate_data -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 5a2226f06764..6a6d4c52c978 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -5,7 +5,11 @@ use common_enums::{ use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use crate::{enums::MandateTypeDetails, schema::payment_attempt, PaymentAttemptNew}; +use crate::{ + enums::{MandateDataType, MandateDetails}, + schema::payment_attempt, + PaymentAttemptNew, +}; #[derive( Clone, Debug, Default, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, @@ -50,7 +54,7 @@ pub struct PaymentAttemptBatchNew { pub business_sub_label: Option, pub straight_through_algorithm: Option, pub preprocessing_step_id: Option, - pub mandate_details: Option, + pub mandate_details: Option, pub error_reason: Option, pub connector_response_reference_id: Option, pub connector_transaction_id: Option, @@ -63,6 +67,7 @@ pub struct PaymentAttemptBatchNew { pub unified_code: Option, pub unified_message: Option, pub net_amount: Option, + pub mandate_data: Option, } #[allow(dead_code)] @@ -116,6 +121,7 @@ impl PaymentAttemptBatchNew { unified_code: self.unified_code, unified_message: self.unified_message, net_amount: self.net_amount, + mandate_data: self.mandate_data, } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 82427709ec2f..152232d2decb 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2000,17 +2000,8 @@ pub async fn list_payment_methods( merchant_name: merchant_account.merchant_name, payment_type, payment_methods: payment_method_responses, - mandate_payment: payment_attempt - .and_then(|inner| inner.mandate_details) - .and_then(|man_type_details| match man_type_details { - data_models::mandates::MandateTypeDetails::MandateType(mandate_type) => { - Some(mandate_type) - } - data_models::mandates::MandateTypeDetails::MandateDetails(mandate_details) => { - mandate_details.mandate_type - } - }) - .map(|d| match d { + mandate_payment: payment_attempt.and_then(|inner| inner.mandate_details).map( + |d| match d { data_models::mandates::MandateDataType::SingleUse(i) => { api::MandateType::SingleUse(api::MandateAmountData { amount: i.amount, @@ -2032,7 +2023,8 @@ pub async fn list_payment_methods( data_models::mandates::MandateDataType::MultiUse(None) => { api::MandateType::MultiUse(None) } - }), + }, + ), show_surcharge_breakup_screen: merchant_surcharge_configs .show_surcharge_breakup_screen .unwrap_or_default(), @@ -2242,28 +2234,20 @@ pub async fn filter_payment_methods( })?; let filter7 = payment_attempt .and_then(|attempt| attempt.mandate_details.as_ref()) - .map(|mandate_details| { - let (mandate_type_present, update_mandate_id_present) = - match mandate_details { - data_models::mandates::MandateTypeDetails::MandateType(_) => { - (true, false) - } - data_models::mandates::MandateTypeDetails::MandateDetails( - mand_details, - ) => ( - mand_details.mandate_type.is_some(), - mand_details.update_mandate_id.is_some(), - ), - }; - - if mandate_type_present { - filter_pm_based_on_supported_payments_for_mandate( - supported_payment_methods_for_mandate, - &payment_method, - &payment_method_object.payment_method_type, - connector_variant, - ) - } else if update_mandate_id_present { + .map(|_mandate_details| { + filter_pm_based_on_supported_payments_for_mandate( + supported_payment_methods_for_mandate, + &payment_method, + &payment_method_object.payment_method_type, + connector_variant, + ) + }) + .unwrap_or(true); + + let filter8 = payment_attempt + .and_then(|attempt| attempt.mandate_data.as_ref()) + .map(|mandate_detail| { + if mandate_detail.update_mandate_id.is_some() { filter_pm_based_on_update_mandate_support_for_connector( supported_payment_methods_for_update_mandate, &payment_method, @@ -2284,7 +2268,15 @@ pub async fn filter_payment_methods( payment_method, ); - if filter && filter2 && filter3 && filter4 && filter5 && filter6 && filter7 { + if filter + && filter2 + && filter3 + && filter4 + && filter5 + && filter6 + && filter7 + && filter8 + { resp.push(response_pm_type); } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7ec1f5e92135..c6ba4a4e9876 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3201,6 +3201,7 @@ impl AttemptType { unified_code: None, unified_message: None, net_amount: old_payment_attempt.amount, + mandate_data: old_payment_attempt.mandate_data, } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 54ec47ecea51..8349f0501351 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -441,28 +441,11 @@ impl // The operation merges mandate data from both request and payment_attempt setup_mandate = setup_mandate.map(|mut sm| { - sm.mandate_type = payment_attempt - .mandate_details - .clone() - .and_then(|mandate| match mandate { - data_models::mandates::MandateTypeDetails::MandateType(mandate_type) => { - Some(mandate_type) - } - data_models::mandates::MandateTypeDetails::MandateDetails(mandate_details) => { - mandate_details.mandate_type - } - }) - .or(sm.mandate_type); + sm.mandate_type = payment_attempt.mandate_details.clone().or(sm.mandate_type); sm.update_mandate_id = payment_attempt - .mandate_details + .mandate_data .clone() - .and_then(|mandate| match mandate { - data_models::mandates::MandateTypeDetails::MandateType(_) => None, - data_models::mandates::MandateTypeDetails::MandateDetails(update_id) => { - Some(update_id.update_mandate_id) - } - }) - .flatten() + .and_then(|mandate| mandate.update_mandate_id) .or(sm.update_mandate_id); sm }); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 8d37fc486c57..0f581be3089e 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -4,7 +4,7 @@ use api_models::enums::FrmSuggestion; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; use data_models::{ - mandates::{MandateData, MandateDetails, MandateTypeDetails}, + mandates::{MandateData, MandateDetails}, payments::payment_attempt::PaymentAttempt, }; use diesel_models::ephemeral_key; @@ -733,27 +733,17 @@ impl PaymentCreate { Err(errors::ApiErrorResponse::InvalidRequestData {message:"Only one field out of 'mandate_type' and 'update_mandate_id' was expected, found both".to_string()})? } - let mandate_details = if request.mandate_data.is_none() { - None - } else if let Some(update_id) = request + let mandate_data = if let Some(update_id) = request .mandate_data .as_ref() .and_then(|inner| inner.update_mandate_id.clone()) { - let mandate_data = MandateDetails { + let mandate_details = MandateDetails { update_mandate_id: Some(update_id), - mandate_type: None, }; - Some(MandateTypeDetails::MandateDetails(mandate_data)) + Some(mandate_details) } else { - let mandate_data = MandateDetails { - update_mandate_id: None, - mandate_type: request - .mandate_data - .as_ref() - .and_then(|inner| inner.mandate_type.clone().map(Into::into)), - }; - Some(MandateTypeDetails::MandateDetails(mandate_data)) + None }; Ok(( @@ -782,7 +772,11 @@ impl PaymentCreate { business_sub_label: request.business_sub_label.clone(), surcharge_amount, tax_amount, - mandate_details, + mandate_details: request + .mandate_data + .as_ref() + .and_then(|inner| inner.mandate_type.clone().map(Into::into)), + mandate_data, ..storage::PaymentAttemptNew::default() }, additional_pm_data, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 24863ddc568d..e60e32227d3f 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -147,6 +147,7 @@ impl PaymentAttemptInterface for MockDb { merchant_connector_id: payment_attempt.merchant_connector_id, unified_code: payment_attempt.unified_code, unified_message: payment_attempt.unified_message, + mandate_data: payment_attempt.mandate_data, }; 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 6af136c1062f..ec19e30c0ec1 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -2,7 +2,7 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMet use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; use data_models::{ errors, - mandates::{MandateAmountData, MandateDataType, MandateDetails, MandateTypeDetails}, + mandates::{MandateAmountData, MandateDataType, MandateDetails}, payments::{ payment_attempt::{ PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, @@ -14,8 +14,7 @@ use data_models::{ use diesel_models::{ enums::{ MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType, - MandateDetails as DieselMandateDetails, MandateTypeDetails as DieselMandateTypeOrDetails, - MerchantStorageScheme, + MandateDetails as DieselMandateDetails, MerchantStorageScheme, }, kv, payment_attempt::{ @@ -391,6 +390,7 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_connector_id: payment_attempt.merchant_connector_id.clone(), unified_code: payment_attempt.unified_code.clone(), unified_message: payment_attempt.unified_message.clone(), + mandate_data: payment_attempt.mandate_data.clone(), }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1016,42 +1016,11 @@ impl DataModelExt for MandateDetails { fn to_storage_model(self) -> Self::StorageModel { DieselMandateDetails { update_mandate_id: self.update_mandate_id, - mandate_type: self - .mandate_type - .map(|mand_type| mand_type.to_storage_model()), } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { update_mandate_id: storage_model.update_mandate_id, - mandate_type: storage_model - .mandate_type - .map(MandateDataType::from_storage_model), - } - } -} -impl DataModelExt for MandateTypeDetails { - type StorageModel = DieselMandateTypeOrDetails; - - fn to_storage_model(self) -> Self::StorageModel { - match self { - Self::MandateType(mandate_type) => { - DieselMandateTypeOrDetails::MandateType(mandate_type.to_storage_model()) - } - Self::MandateDetails(mandate_details) => { - DieselMandateTypeOrDetails::MandateDetails(mandate_details.to_storage_model()) - } - } - } - - fn from_storage_model(storage_model: Self::StorageModel) -> Self { - match storage_model { - DieselMandateTypeOrDetails::MandateType(data) => { - Self::MandateType(MandateDataType::from_storage_model(data)) - } - DieselMandateTypeOrDetails::MandateDetails(data) => { - Self::MandateDetails(MandateDetails::from_storage_model(data)) - } } } } @@ -1124,7 +1093,7 @@ impl DataModelExt for PaymentAttempt { business_sub_label: self.business_sub_label, straight_through_algorithm: self.straight_through_algorithm, preprocessing_step_id: self.preprocessing_step_id, - mandate_details: self.mandate_details.map(|md| md.to_storage_model()), + mandate_details: self.mandate_details.map(|d| d.to_storage_model()), error_reason: self.error_reason, multiple_capture_count: self.multiple_capture_count, connector_response_reference_id: self.connector_response_reference_id, @@ -1135,6 +1104,7 @@ impl DataModelExt for PaymentAttempt { merchant_connector_id: self.merchant_connector_id, unified_code: self.unified_code, unified_message: self.unified_message, + mandate_data: self.mandate_data.map(|d| d.to_storage_model()), } } @@ -1179,7 +1149,7 @@ impl DataModelExt for PaymentAttempt { preprocessing_step_id: storage_model.preprocessing_step_id, mandate_details: storage_model .mandate_details - .map(MandateTypeDetails::from_storage_model), + .map(MandateDataType::from_storage_model), error_reason: storage_model.error_reason, multiple_capture_count: storage_model.multiple_capture_count, connector_response_reference_id: storage_model.connector_response_reference_id, @@ -1190,6 +1160,9 @@ impl DataModelExt for PaymentAttempt { merchant_connector_id: storage_model.merchant_connector_id, unified_code: storage_model.unified_code, unified_message: storage_model.unified_message, + mandate_data: storage_model + .mandate_data + .map(MandateDetails::from_storage_model), } } } @@ -1245,6 +1218,7 @@ impl DataModelExt for PaymentAttemptNew { merchant_connector_id: self.merchant_connector_id, unified_code: self.unified_code, unified_message: self.unified_message, + mandate_data: self.mandate_data.map(|d| d.to_storage_model()), } } @@ -1287,7 +1261,7 @@ impl DataModelExt for PaymentAttemptNew { preprocessing_step_id: storage_model.preprocessing_step_id, mandate_details: storage_model .mandate_details - .map(MandateTypeDetails::from_storage_model), + .map(MandateDataType::from_storage_model), error_reason: storage_model.error_reason, connector_response_reference_id: storage_model.connector_response_reference_id, multiple_capture_count: storage_model.multiple_capture_count, @@ -1298,6 +1272,9 @@ impl DataModelExt for PaymentAttemptNew { merchant_connector_id: storage_model.merchant_connector_id, unified_code: storage_model.unified_code, unified_message: storage_model.unified_message, + mandate_data: storage_model + .mandate_data + .map(MandateDetails::from_storage_model), } } } diff --git a/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/down.sql b/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/down.sql new file mode 100644 index 000000000000..9d2071f8ddaa --- /dev/null +++ b/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS mandate_data; diff --git a/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/up.sql b/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/up.sql new file mode 100644 index 000000000000..31ed6e88fead --- /dev/null +++ b/migrations/2024-02-08-142804_add_mandate_data_payment_attempt/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS mandate_data JSONB DEFAULT NULL; \ No newline at end of file