diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index cefb0c240e..bda7af157d 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -52,5 +52,5 @@ pub struct CustomerUpdateInternal { pub modified_at: Option, pub connector_customer: Option, pub address_id: Option, - pub default_payment_method_id: Option, + pub default_payment_method_id: Option>, } diff --git a/crates/diesel_models/src/query/generics.rs b/crates/diesel_models/src/query/generics.rs index 8f2e391df6..a1dd40cd40 100644 --- a/crates/diesel_models/src/query/generics.rs +++ b/crates/diesel_models/src/query/generics.rs @@ -40,6 +40,7 @@ pub mod db_metrics { DeleteWithResult, UpdateWithResults, UpdateOne, + Count, } #[inline] diff --git a/crates/diesel_models/src/query/payment_method.rs b/crates/diesel_models/src/query/payment_method.rs index bed4d07901..a27a2ae895 100644 --- a/crates/diesel_models/src/query/payment_method.rs +++ b/crates/diesel_models/src/query/payment_method.rs @@ -1,4 +1,9 @@ -use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table}; +use async_bb8_diesel::AsyncRunQueryDsl; +use diesel::{ + associations::HasTable, debug_query, pg::Pg, BoolExpressionMethods, ExpressionMethods, + QueryDsl, Table, +}; +use error_stack::{IntoReport, ResultExt}; use super::generics; use crate::{ @@ -96,6 +101,34 @@ impl PaymentMethod { .await } + pub async fn get_count_by_customer_id_merchant_id_status( + conn: &PgPooledConn, + customer_id: &str, + merchant_id: &str, + status: common_enums::PaymentMethodStatus, + ) -> StorageResult { + let filter = ::table() + .count() + .filter( + dsl::customer_id + .eq(customer_id.to_owned()) + .and(dsl::merchant_id.eq(merchant_id.to_owned())) + .and(dsl::status.eq(status.to_owned())), + ) + .into_boxed(); + + router_env::logger::debug!(query = %debug_query::(&filter).to_string()); + + generics::db_metrics::track_database_call::<::Table, _, _>( + filter.get_result_async::(conn), + generics::db_metrics::DatabaseOperation::Count, + ) + .await + .into_report() + .change_context(errors::DatabaseError::Others) + .attach_printable("Failed to get a count of payment methods") + } + pub async fn find_by_customer_id_merchant_id_status( conn: &PgPooledConn, customer_id: &str, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 985ddcfe84..847950d4f4 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3201,7 +3201,7 @@ pub async fn set_default_payment_method( )?; let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod { - default_payment_method_id: Some(payment_method_id.to_owned()), + default_payment_method_id: Some(Some(payment_method_id.to_owned())), }; // update the db with the default payment method id @@ -3450,6 +3450,16 @@ pub async fn delete_payment_method( .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + let payment_methods_count = db + .get_payment_method_count_by_customer_id_merchant_id_status( + &key.customer_id, + &merchant_account.merchant_id, + api_enums::PaymentMethodStatus::Active, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get a count of payment methods for a customer")?; + let customer = db .find_customer_by_customer_id_merchant_id( &key.customer_id, @@ -3461,7 +3471,8 @@ pub async fn delete_payment_method( .attach_printable("Customer not found for the payment method")?; utils::when( - customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id), + customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id) + && payment_methods_count > 1, || Err(errors::ApiErrorResponse::PaymentMethodDeleteFailed), )?; @@ -3489,6 +3500,22 @@ pub async fn delete_payment_method( .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + if customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id) { + let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod { + default_payment_method_id: Some(None), + }; + + db.update_customer_by_customer_id_merchant_id( + key.customer_id, + key.merchant_id, + customer_update, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update the default payment method id for the customer")?; + }; + Ok(services::ApplicationResponse::Json( api::PaymentMethodDeleteResponse { payment_method_id: key.payment_method_id, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 4509da8f0f..ff03632009 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1299,6 +1299,21 @@ impl PaymentMethodInterface for KafkaStore { .await } + async fn get_payment_method_count_by_customer_id_merchant_id_status( + &self, + customer_id: &str, + merchant_id: &str, + status: common_enums::PaymentMethodStatus, + ) -> CustomResult { + self.diesel_store + .get_payment_method_count_by_customer_id_merchant_id_status( + customer_id, + merchant_id, + status, + ) + .await + } + async fn find_payment_method_by_locker_id( &self, locker_id: &str, diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs index ef471fbd24..94ebee1789 100644 --- a/crates/router/src/db/payment_method.rs +++ b/crates/router/src/db/payment_method.rs @@ -36,6 +36,13 @@ pub trait PaymentMethodInterface { limit: Option, ) -> CustomResult, errors::StorageError>; + async fn get_payment_method_count_by_customer_id_merchant_id_status( + &self, + customer_id: &str, + merchant_id: &str, + status: common_enums::PaymentMethodStatus, + ) -> CustomResult; + async fn insert_payment_method( &self, payment_method_new: storage::PaymentMethodNew, @@ -80,6 +87,25 @@ impl PaymentMethodInterface for Store { .into_report() } + #[instrument(skip_all)] + async fn get_payment_method_count_by_customer_id_merchant_id_status( + &self, + customer_id: &str, + merchant_id: &str, + status: common_enums::PaymentMethodStatus, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + storage::PaymentMethod::get_count_by_customer_id_merchant_id_status( + &conn, + customer_id, + merchant_id, + status, + ) + .await + .map_err(Into::into) + .into_report() + } + #[instrument(skip_all)] async fn insert_payment_method( &self, @@ -204,6 +230,27 @@ impl PaymentMethodInterface for MockDb { } } + async fn get_payment_method_count_by_customer_id_merchant_id_status( + &self, + customer_id: &str, + merchant_id: &str, + status: common_enums::PaymentMethodStatus, + ) -> CustomResult { + let payment_methods = self.payment_methods.lock().await; + let count = payment_methods + .iter() + .filter(|pm| { + pm.customer_id == customer_id + && pm.merchant_id == merchant_id + && pm.status == status + }) + .count(); + count + .try_into() + .into_report() + .change_context(errors::StorageError::MockDbError) + } + async fn insert_payment_method( &self, payment_method_new: storage::PaymentMethodNew, diff --git a/crates/router/src/types/domain/customer.rs b/crates/router/src/types/domain/customer.rs index 5437d06a2e..d5f05944d6 100644 --- a/crates/router/src/types/domain/customer.rs +++ b/crates/router/src/types/domain/customer.rs @@ -118,7 +118,7 @@ pub enum CustomerUpdate { connector_customer: Option, }, UpdateDefaultPaymentMethod { - default_payment_method_id: Option, + default_payment_method_id: Option>, }, }