Bug: [FEATURE] Add support for tokenising bank details and fetching masked details while listing



### Feature Description

Add support for tokenising the fetched bank details during list_customer_payment_method and return masked bank details 

### Possible Implementation

For each bank account fetched from payment_methods_table, tokenize each account and store it in redis, so that it could be fetched during /confirm

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index c40dffe4cf3..8710c69aa5c 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -811,10 +811,20 @@ pub struct CustomerPaymentMethod {
     #[schema(value_type = Option<Bank>)]
     pub bank_transfer: Option<payouts::Bank>,
 
+    /// Masked bank details from PM auth services
+    #[schema(example = json!({"mask": "0000"}))]
+    pub bank: Option<MaskedBankDetails>,
+
     /// Whether this payment method requires CVV to be collected
     #[schema(example = true)]
     pub requires_cvv: bool,
 }
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
+pub struct MaskedBankDetails {
+    pub mask: String,
+}
+
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct PaymentMethodId {
     pub payment_method_id: String,
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 60fd3f315ea..85a0ca5f244 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -7,9 +7,10 @@ use api_models::{
     admin::{self, PaymentMethodsEnabled},
     enums::{self as api_enums},
     payment_methods::{
-        CardDetailsPaymentMethod, CardNetworkTypes, PaymentExperienceTypes, PaymentMethodsData,
-        RequestPaymentMethodTypes, RequiredFieldInfo, ResponsePaymentMethodIntermediate,
-        ResponsePaymentMethodTypes, ResponsePaymentMethodsEnabled,
+        BankAccountConnectorDetails, CardDetailsPaymentMethod, CardNetworkTypes, MaskedBankDetails,
+        PaymentExperienceTypes, PaymentMethodsData, RequestPaymentMethodTypes, RequiredFieldInfo,
+        ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes,
+        ResponsePaymentMethodsEnabled,
     },
     payments::BankCodeResponse,
     surcharge_decision_configs as api_surcharge_decision_configs,
@@ -2210,6 +2211,22 @@ pub async fn list_customer_payment_method(
                 )
             }
 
+            enums::PaymentMethod::BankDebit => {
+                // Retrieve the pm_auth connector details so that it can be tokenized
+                let bank_account_connector_details = get_bank_account_connector_details(&pm, key)
+                    .await
+                    .unwrap_or_else(|err| {
+                        logger::error!(error=?err);
+                        None
+                    });
+                if let Some(connector_details) = bank_account_connector_details {
+                    let token_data = PaymentTokenData::AuthBankDebit(connector_details);
+                    (None, None, token_data)
+                } else {
+                    continue;
+                }
+            }
+
             _ => (
                 None,
                 None,
@@ -2217,6 +2234,18 @@ pub async fn list_customer_payment_method(
             ),
         };
 
+        // Retrieve the masked bank details to be sent as a response
+        let bank_details = if pm.payment_method == enums::PaymentMethod::BankDebit {
+            get_masked_bank_details(&pm, key)
+                .await
+                .unwrap_or_else(|err| {
+                    logger::error!(error=?err);
+                    None
+                })
+        } else {
+            None
+        };
+
         //Need validation for enabled payment method ,querying MCA
         let pma = api::CustomerPaymentMethod {
             payment_token: parent_payment_method_token.to_owned(),
@@ -2232,6 +2261,7 @@ pub async fn list_customer_payment_method(
             payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
             created: Some(pm.created_at),
             bank_transfer: pmd,
+            bank: bank_details,
             requires_cvv,
         };
         customer_pms.push(pma.to_owned());
@@ -2356,6 +2386,84 @@ pub async fn get_lookup_key_from_locker(
     Ok(resp)
 }
 
+async fn get_masked_bank_details(
+    pm: &payment_method::PaymentMethod,
+    key: &[u8],
+) -> errors::RouterResult<Option<MaskedBankDetails>> {
+    let payment_method_data =
+        decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key)
+            .await
+            .change_context(errors::StorageError::DecryptionError)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("unable to decrypt bank details")?
+            .map(|x| x.into_inner().expose())
+            .map(
+                |v| -> Result<PaymentMethodsData, error_stack::Report<errors::ApiErrorResponse>> {
+                    v.parse_value::<PaymentMethodsData>("PaymentMethodsData")
+                        .change_context(errors::StorageError::DeserializationFailed)
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to deserialize Payment Method Auth config")
+                },
+            )
+            .transpose()?;
+
+    match payment_method_data {
+        Some(pmd) => match pmd {
+            PaymentMethodsData::Card(_) => Ok(None),
+            PaymentMethodsData::BankDetails(bank_details) => Ok(Some(MaskedBankDetails {
+                mask: bank_details.mask,
+            })),
+        },
+        None => Err(errors::ApiErrorResponse::InternalServerError.into())
+            .attach_printable("Unable to fetch payment method data"),
+    }
+}
+
+async fn get_bank_account_connector_details(
+    pm: &payment_method::PaymentMethod,
+    key: &[u8],
+) -> errors::RouterResult<Option<BankAccountConnectorDetails>> {
+    let payment_method_data =
+        decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key)
+            .await
+            .change_context(errors::StorageError::DecryptionError)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("unable to decrypt bank details")?
+            .map(|x| x.into_inner().expose())
+            .map(
+                |v| -> Result<PaymentMethodsData, error_stack::Report<errors::ApiErrorResponse>> {
+                    v.parse_value::<PaymentMethodsData>("PaymentMethodsData")
+                        .change_context(errors::StorageError::DeserializationFailed)
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to deserialize Payment Method Auth config")
+                },
+            )
+            .transpose()?;
+
+    match payment_method_data {
+        Some(pmd) => match pmd {
+            PaymentMethodsData::Card(_) => Err(errors::ApiErrorResponse::UnprocessableEntity {
+                message: "Card is not a valid entity".to_string(),
+            })
+            .into_report(),
+            PaymentMethodsData::BankDetails(bank_details) => {
+                let connector_details = bank_details
+                    .connector_details
+                    .first()
+                    .ok_or(errors::ApiErrorResponse::InternalServerError)?;
+                Ok(Some(BankAccountConnectorDetails {
+                    connector: connector_details.connector.clone(),
+                    account_id: connector_details.account_id.clone(),
+                    mca_id: connector_details.mca_id.clone(),
+                    access_token: connector_details.access_token.clone(),
+                }))
+            }
+        },
+        None => Err(errors::ApiErrorResponse::InternalServerError.into())
+            .attach_printable("Unable to fetch payment method data"),
+    }
+}
+
 #[cfg(feature = "payouts")]
 pub async fn get_lookup_key_for_payout_method(
     state: &routes::AppState,
diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs
index 04ef90546cf..d191890b8cd 100644
--- a/crates/router/src/openapi.rs
+++ b/crates/router/src/openapi.rs
@@ -315,6 +315,7 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payments::PaymentAttemptResponse,
         api_models::payments::CaptureResponse,
         api_models::payment_methods::RequiredFieldInfo,
+        api_models::payment_methods::MaskedBankDetails,
         api_models::refunds::RefundListRequest,
         api_models::refunds::RefundListResponse,
         api_models::payments::TimeRange,
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index 9ca4dea4a1a..b26c030c367 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -4828,6 +4828,14 @@
             ],
             "nullable": true
           },
+          "bank": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/MaskedBankDetails"
+              }
+            ],
+            "nullable": true
+          },
           "requires_cvv": {
             "type": "boolean",
             "description": "Whether this payment method requires CVV to be collected",
@@ -6434,6 +6442,17 @@
           }
         ]
       },
+      "MaskedBankDetails": {
+        "type": "object",
+        "required": [
+          "mask"
+        ],
+        "properties": {
+          "mask": {
+            "type": "string"
+          }
+        }
+      },
       "MbWayRedirection": {
         "type": "object",
         "required": [

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

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

## Description
<!-- Describe your changes in detail -->
This PR includes changes for tokenising the fetched bank details and return masked bank details while fetching the customer payment methods

### 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)?
-->
List customer payment method - 

![image](https://github.com/juspay/hyperswitch/assets/70657455/a9bc01d8-aeef-4ee2-b1cb-7fd0e99df685)

Redis Entry - 

![image](https://github.com/juspay/hyperswitch/assets/70657455/5aa3deab-3489-47aa-bb71-315c07e436e2)



## 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
