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:
Chethan Rao
2024-03-12 10:49:00 +05:30
committed by GitHub
parent abe9c2ac17
commit 45ed56f165
7 changed files with 128 additions and 5 deletions

View File

@ -52,5 +52,5 @@ pub struct CustomerUpdateInternal {
pub modified_at: Option<PrimitiveDateTime>, pub modified_at: Option<PrimitiveDateTime>,
pub connector_customer: Option<serde_json::Value>, pub connector_customer: Option<serde_json::Value>,
pub address_id: Option<String>, pub address_id: Option<String>,
pub default_payment_method_id: Option<String>, pub default_payment_method_id: Option<Option<String>>,
} }

View File

@ -40,6 +40,7 @@ pub mod db_metrics {
DeleteWithResult, DeleteWithResult,
UpdateWithResults, UpdateWithResults,
UpdateOne, UpdateOne,
Count,
} }
#[inline] #[inline]

View File

@ -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 super::generics;
use crate::{ use crate::{
@ -96,6 +101,34 @@ impl PaymentMethod {
.await .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( pub async fn find_by_customer_id_merchant_id_status(
conn: &PgPooledConn, conn: &PgPooledConn,
customer_id: &str, customer_id: &str,

View File

@ -3201,7 +3201,7 @@ pub async fn set_default_payment_method(
)?; )?;
let customer_update = CustomerUpdate::UpdateDefaultPaymentMethod { 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 // update the db with the default payment method id
@ -3450,6 +3450,16 @@ pub async fn delete_payment_method(
.await .await
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; .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 let customer = db
.find_customer_by_customer_id_merchant_id( .find_customer_by_customer_id_merchant_id(
&key.customer_id, &key.customer_id,
@ -3461,7 +3471,8 @@ pub async fn delete_payment_method(
.attach_printable("Customer not found for the payment method")?; .attach_printable("Customer not found for the payment method")?;
utils::when( 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), || Err(errors::ApiErrorResponse::PaymentMethodDeleteFailed),
)?; )?;
@ -3489,6 +3500,22 @@ pub async fn delete_payment_method(
.await .await
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; .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( Ok(services::ApplicationResponse::Json(
api::PaymentMethodDeleteResponse { api::PaymentMethodDeleteResponse {
payment_method_id: key.payment_method_id, payment_method_id: key.payment_method_id,

View File

@ -1299,6 +1299,21 @@ impl PaymentMethodInterface for KafkaStore {
.await .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( async fn find_payment_method_by_locker_id(
&self, &self,
locker_id: &str, locker_id: &str,

View File

@ -36,6 +36,13 @@ pub trait PaymentMethodInterface {
limit: Option<i64>, limit: Option<i64>,
) -> CustomResult<Vec<storage::PaymentMethod>, errors::StorageError>; ) -> 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( async fn insert_payment_method(
&self, &self,
payment_method_new: storage::PaymentMethodNew, payment_method_new: storage::PaymentMethodNew,
@ -80,6 +87,25 @@ impl PaymentMethodInterface for Store {
.into_report() .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)] #[instrument(skip_all)]
async fn insert_payment_method( async fn insert_payment_method(
&self, &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( async fn insert_payment_method(
&self, &self,
payment_method_new: storage::PaymentMethodNew, payment_method_new: storage::PaymentMethodNew,

View File

@ -118,7 +118,7 @@ pub enum CustomerUpdate {
connector_customer: Option<serde_json::Value>, connector_customer: Option<serde_json::Value>,
}, },
UpdateDefaultPaymentMethod { UpdateDefaultPaymentMethod {
default_payment_method_id: Option<String>, default_payment_method_id: Option<Option<String>>,
}, },
} }