Bug: Disallow deletion of default payment method for a customer



Should not allow deletion of the default_payment_method of a customer. 

Error Message: "Cannot delete the default payment method"
Merchant Error Handling: "Retry after setting another PM as default"
diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs
index d28e988a039..c1f8b5e9c75 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<errors::ApiErrorResponse> 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 f9ed61e0f1b..46c90d776d4 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 ee63074544c..ce5253051e1 100644
--- a/crates/router/src/core/errors/transformers.rs
+++ b/crates/router/src/core/errors/transformers.rs
@@ -281,6 +281,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> 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 ce7f48405e2..7344cd09a5a 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -3365,6 +3365,7 @@ pub async fn delete_payment_method(
     state: routes::AppState,
     merchant_account: domain::MerchantAccount,
     pm_id: api::PaymentMethodId,
+    key_store: domain::MerchantKeyStore,
 ) -> errors::RouterResponse<api::PaymentMethodDeleteResponse> {
     let db = state.store.as_ref();
     let key = db
@@ -3372,6 +3373,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 5469f981659..c0c79bde0cd 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,
     ))


## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
If a payment method is set as a default payment method for a customer and it is tried to be deleted, we throw a invalid request error.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
1. Store a card or any other payment method
2. Set that payment method as default

```
curl --location --request POST 'http://localhost:8080/customers/cus_aXfFGQOFT7JFU47GYhZK/payment_methods/pm_37h7qElY2aTWBEPSgovJ/default' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: abc' \
--data ''
```

3. Now try to delete that default payment method

```
curl --location --request DELETE 'http://localhost:8080/payment_methods/pm_37h7qElY2aTWBEPSgovJ' \
--header 'Accept: application/json' \
--header 'api-key: abc'
```

![image](https://github.com/juspay/hyperswitch/assets/70657455/840b23b2-ad0c-4af4-836e-11713537a220)


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
