From db39bb0a3cf350e8399a7f17842d9af9b2de440e Mon Sep 17 00:00:00 2001 From: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:05:28 +0530 Subject: [PATCH] refactor(payment_methods): prevent deletion of default payment method for a customer (#3964) --- crates/router/src/compatibility/stripe/errors.rs | 6 +++++- .../router/src/core/errors/api_error_response.rs | 2 ++ crates/router/src/core/errors/transformers.rs | 3 +++ crates/router/src/core/payment_methods/cards.rs | 16 ++++++++++++++++ crates/router/src/routes/payment_methods.rs | 4 +++- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index d28e988a03..c1f8b5e9c7 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -251,6 +251,8 @@ pub enum StripeErrorCode { InvalidConnectorConfiguration { config: String }, #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert currency to minor unit")] CurrencyConversionFailed, + #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] + PaymentMethodDeleteFailed, // [#216]: https://github.com/juspay/hyperswitch/issues/216 // Implement the remaining stripe error codes @@ -618,6 +620,7 @@ impl From for StripeErrorCode { Self::InvalidConnectorConfiguration { config } } errors::ApiErrorResponse::CurrencyConversionFailed => Self::CurrencyConversionFailed, + errors::ApiErrorResponse::PaymentMethodDeleteFailed => Self::PaymentMethodDeleteFailed, } } } @@ -686,7 +689,8 @@ impl actix_web::ResponseError for StripeErrorCode { | Self::DuplicateCustomer | Self::PaymentMethodUnactivated | Self::InvalidConnectorConfiguration { .. } - | Self::CurrencyConversionFailed => StatusCode::BAD_REQUEST, + | Self::CurrencyConversionFailed + | Self::PaymentMethodDeleteFailed => StatusCode::BAD_REQUEST, Self::RefundFailed | Self::PayoutFailed | Self::PaymentLinkNotFound diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs index f9ed61e0f1..46c90d776d 100644 --- a/crates/router/src/core/errors/api_error_response.rs +++ b/crates/router/src/core/errors/api_error_response.rs @@ -252,6 +252,8 @@ pub enum ApiErrorResponse { InvalidConnectorConfiguration { config: String }, #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] CurrencyConversionFailed, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] + PaymentMethodDeleteFailed, } impl PTError for ApiErrorResponse { diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index ee63074544..ce5253051e 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -281,6 +281,9 @@ impl ErrorSwitch for ApiErrorRespon Self::CurrencyConversionFailed => { AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) } + Self::PaymentMethodDeleteFailed => { + AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) + } } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index dca0d2ccb3..3f4248c4b3 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3428,6 +3428,7 @@ pub async fn delete_payment_method( state: routes::AppState, merchant_account: domain::MerchantAccount, pm_id: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, ) -> errors::RouterResponse { let db = state.store.as_ref(); let key = db @@ -3435,6 +3436,21 @@ pub async fn delete_payment_method( .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + let customer = db + .find_customer_by_customer_id_merchant_id( + &key.customer_id, + &merchant_account.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Customer not found for the payment method")?; + + utils::when( + customer.default_payment_method_id.as_ref() == Some(&pm_id.payment_method_id), + || Err(errors::ApiErrorResponse::PaymentMethodDeleteFailed), + )?; + if key.payment_method == enums::PaymentMethod::Card { let response = delete_card_from_locker( &state, diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 939cfaf93d..1e2e66d583 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -253,7 +253,9 @@ pub async fn payment_method_delete_api( state, &req, pm, - |state, auth, req| cards::delete_payment_method(state, auth.merchant_account, req), + |state, auth, req| { + cards::delete_payment_method(state, auth.merchant_account, req, auth.key_store) + }, &auth::ApiKeyAuth, api_locking::LockAction::NotApplicable, ))