mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	refactor(payment_methods): allow deletion of default payment method for a customer if only one pm exists (#4027)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -52,5 +52,5 @@ pub struct CustomerUpdateInternal { | ||||
|     pub modified_at: Option<PrimitiveDateTime>, | ||||
|     pub connector_customer: Option<serde_json::Value>, | ||||
|     pub address_id: Option<String>, | ||||
|     pub default_payment_method_id: Option<String>, | ||||
|     pub default_payment_method_id: Option<Option<String>>, | ||||
| } | ||||
|  | ||||
| @ -40,6 +40,7 @@ pub mod db_metrics { | ||||
|         DeleteWithResult, | ||||
|         UpdateWithResults, | ||||
|         UpdateOne, | ||||
|         Count, | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|  | ||||
| @ -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<i64> { | ||||
|         let filter = <Self as HasTable>::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::<Pg, _>(&filter).to_string()); | ||||
|  | ||||
|         generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>( | ||||
|             filter.get_result_async::<i64>(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, | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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<i64, errors::StorageError> { | ||||
|         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, | ||||
|  | ||||
| @ -36,6 +36,13 @@ pub trait PaymentMethodInterface { | ||||
|         limit: Option<i64>, | ||||
|     ) -> CustomResult<Vec<storage::PaymentMethod>, 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<i64, errors::StorageError>; | ||||
|  | ||||
|     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<i64, errors::StorageError> { | ||||
|         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<i64, errors::StorageError> { | ||||
|         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, | ||||
|  | ||||
| @ -118,7 +118,7 @@ pub enum CustomerUpdate { | ||||
|         connector_customer: Option<serde_json::Value>, | ||||
|     }, | ||||
|     UpdateDefaultPaymentMethod { | ||||
|         default_payment_method_id: Option<String>, | ||||
|         default_payment_method_id: Option<Option<String>>, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Chethan Rao
					Chethan Rao