Bug: feat(router): refactor PML response



Remove duplicate responses and return bank_names
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 4cdfe9dcfe8..a70dceb962d 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -16,12 +16,11 @@ use masking::PeekInterface;
 use serde::de;
 use utoipa::{schema, ToSchema};
 
+#[cfg(feature = "v1")]
+use crate::payments::BankCodeResponse;
 #[cfg(feature = "payouts")]
 use crate::payouts;
-use crate::{
-    admin, enums as api_enums, open_router,
-    payments::{self, BankCodeResponse},
-};
+use crate::{admin, enums as api_enums, open_router, payments};
 
 #[cfg(feature = "v1")]
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
@@ -1471,7 +1470,10 @@ pub enum PaymentMethodSubtypeSpecificData {
         card_networks: Vec<CardNetworkTypes>,
     },
     #[schema(title = "bank")]
-    Bank { bank_names: Vec<BankCodeResponse> },
+    Bank {
+        #[schema(value_type = BankNames)]
+        bank_names: Vec<common_enums::BankNames>,
+    },
 }
 
 #[cfg(feature = "v2")]
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 90792e9633a..3697c555e7e 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -7727,8 +7727,8 @@ pub struct ResponsePaymentMethodTypesForPayments {
     pub payment_method_subtype: common_enums::PaymentMethodType,
 
     /// The payment experience for the payment method
-    #[schema(value_type = Option<PaymentExperience>)]
-    pub payment_experience: Option<common_enums::PaymentExperience>,
+    #[schema(value_type = Option<Vec<PaymentExperience>>)]
+    pub payment_experience: Option<Vec<common_enums::PaymentExperience>>,
 
     /// payment method subtype specific information
     #[serde(flatten)]
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 1d00a451708..893473cfbc4 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -1832,7 +1832,9 @@ pub enum SamsungPayCardBrand {
     Copy,
     Debug,
     Eq,
+    Ord,
     Hash,
+    PartialOrd,
     PartialEq,
     serde::Deserialize,
     serde::Serialize,
@@ -2081,6 +2083,8 @@ impl masking::SerializableSecret for PaymentMethodType {}
     Debug,
     Default,
     Eq,
+    PartialOrd,
+    Ord,
     Hash,
     PartialEq,
     serde::Deserialize,
diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs
index d5848c7f418..ca2d5d1015b 100644
--- a/crates/router/src/core/payment_methods/vault.rs
+++ b/crates/router/src/core/payment_methods/vault.rs
@@ -1598,7 +1598,7 @@ pub async fn retrieve_and_delete_cvc_from_payment_token(
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Failed to get redis connection")?;
 
-    let key = format!("pm_token_{payment_token}_{payment_method}_hyperswitch_cvc");
+    let key = format!("pm_token_{payment_token}_{payment_method}_hyperswitch_cvc",);
 
     let data = redis_conn
         .get_key::<bytes::Bytes>(&key.clone().into())
diff --git a/crates/router/src/core/payments/payment_methods.rs b/crates/router/src/core/payments/payment_methods.rs
index 1cf17289a24..e21b3b96105 100644
--- a/crates/router/src/core/payments/payment_methods.rs
+++ b/crates/router/src/core/payments/payment_methods.rs
@@ -1,11 +1,15 @@
 //! Contains functions of payment methods that are used in payments
 //! one of such functions is `list_payment_methods`
 
+use std::collections::{BTreeMap, HashSet};
+
 use common_utils::{ext_traits::OptionExt, id_type};
 use error_stack::ResultExt;
 
 use super::errors;
-use crate::{core::payment_methods, db::errors::StorageErrorExt, routes, types::domain};
+use crate::{
+    core::payment_methods, db::errors::StorageErrorExt, logger, routes, settings, types::domain,
+};
 
 #[cfg(feature = "v2")]
 pub async fn list_payment_methods(
@@ -55,10 +59,12 @@ pub async fn list_payment_methods(
     };
 
     let response =
-        hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts)
+    FlattenedPaymentMethodsEnabled(hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts))
             .perform_filtering()
+            .merge_and_transform()
             .get_required_fields(RequiredFieldsInput::new())
             .perform_surcharge_calculation()
+            .populate_pm_subtype_specific_data(&state.conf.bank_config)
             .generate_response(customer_payment_methods);
 
     Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
@@ -75,12 +81,79 @@ impl RequiredFieldsInput {
     }
 }
 
+struct FlattenedPaymentMethodsEnabled(
+    hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled,
+);
+
+impl FlattenedPaymentMethodsEnabled {
+    fn perform_filtering(self) -> FilteredPaymentMethodsEnabled {
+        FilteredPaymentMethodsEnabled(self.0.payment_methods_enabled)
+    }
+}
+
 /// Container for the filtered payment methods
 struct FilteredPaymentMethodsEnabled(
     Vec<hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector>,
 );
 
 impl FilteredPaymentMethodsEnabled {
+    fn merge_and_transform(self) -> MergedEnabledPaymentMethodTypes {
+        let values = self
+            .0
+            .into_iter()
+            // BTreeMap used to ensure soretd response, otherwise the response is arbitrarily ordered
+            .fold(BTreeMap::new(), |mut acc, item| {
+                let key = (
+                    item.payment_method,
+                    item.payment_methods_enabled.payment_method_subtype,
+                );
+                let (experiences, connectors) = acc
+                    .entry(key)
+                    // HashSet used to ensure payment_experience does not have duplicates, due to multiple connectors for a pm_subtype
+                    .or_insert_with(|| (HashSet::new(), Vec::new()));
+
+                if let Some(experience) = item.payment_methods_enabled.payment_experience {
+                    experiences.insert(experience);
+                }
+                connectors.push(item.connector);
+
+                acc
+            })
+            .into_iter()
+            .map(
+                |(
+                    (payment_method_type, payment_method_subtype),
+                    (payment_experience, connectors),
+                )| {
+                    MergedEnabledPaymentMethod {
+                        payment_method_type,
+                        payment_method_subtype,
+                        payment_experience: if payment_experience.is_empty() {
+                            None
+                        } else {
+                            Some(payment_experience.into_iter().collect())
+                        },
+                        connectors,
+                    }
+                },
+            )
+            .collect();
+        MergedEnabledPaymentMethodTypes(values)
+    }
+}
+
+/// Element container to hold the filtered payment methods with payment_experience and connectors merged for a pm_subtype
+struct MergedEnabledPaymentMethod {
+    payment_method_subtype: common_enums::PaymentMethodType,
+    payment_method_type: common_enums::PaymentMethod,
+    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
+    connectors: Vec<api_models::enums::Connector>,
+}
+
+/// Container to hold the filtered payment methods with payment_experience and connectors merged for a pm_subtype
+struct MergedEnabledPaymentMethodTypes(Vec<MergedEnabledPaymentMethod>);
+
+impl MergedEnabledPaymentMethodTypes {
     fn get_required_fields(
         self,
         _input: RequiredFieldsInput,
@@ -91,13 +164,10 @@ impl FilteredPaymentMethodsEnabled {
             .map(
                 |payment_methods_enabled| RequiredFieldsForEnabledPaymentMethod {
                     required_field: None,
-                    payment_method_type: payment_methods_enabled.payment_method,
-                    payment_method_subtype: payment_methods_enabled
-                        .payment_methods_enabled
-                        .payment_method_subtype,
-                    payment_experience: payment_methods_enabled
-                        .payment_methods_enabled
-                        .payment_experience,
+                    payment_method_type: payment_methods_enabled.payment_method_type,
+                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
+                    payment_experience: payment_methods_enabled.payment_experience,
+                    connectors: payment_methods_enabled.connectors,
                 },
             )
             .collect();
@@ -111,18 +181,44 @@ struct RequiredFieldsForEnabledPaymentMethod {
     required_field: Option<Vec<api_models::payment_methods::RequiredFieldInfo>>,
     payment_method_subtype: common_enums::PaymentMethodType,
     payment_method_type: common_enums::PaymentMethod,
-    payment_experience: Option<common_enums::PaymentExperience>,
+    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
+    connectors: Vec<api_models::enums::Connector>,
 }
 
 /// Container to hold the filtered payment methods enabled with required fields
 struct RequiredFieldsForEnabledPaymentMethodTypes(Vec<RequiredFieldsForEnabledPaymentMethod>);
 
+impl RequiredFieldsForEnabledPaymentMethodTypes {
+    fn perform_surcharge_calculation(
+        self,
+    ) -> RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
+        // TODO: Perform surcharge calculation
+        let details_with_surcharge = self
+            .0
+            .into_iter()
+            .map(
+                |payment_methods_enabled| RequiredFieldsAndSurchargeForEnabledPaymentMethodType {
+                    payment_method_type: payment_methods_enabled.payment_method_type,
+                    required_field: payment_methods_enabled.required_field,
+                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
+                    payment_experience: payment_methods_enabled.payment_experience,
+                    surcharge: None,
+                    connectors: payment_methods_enabled.connectors,
+                },
+            )
+            .collect();
+
+        RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes(details_with_surcharge)
+    }
+}
+
 /// Element Container to hold the filtered payment methods enabled with required fields and surcharge
 struct RequiredFieldsAndSurchargeForEnabledPaymentMethodType {
     required_field: Option<Vec<api_models::payment_methods::RequiredFieldInfo>>,
     payment_method_subtype: common_enums::PaymentMethodType,
     payment_method_type: common_enums::PaymentMethod,
-    payment_experience: Option<common_enums::PaymentExperience>,
+    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
+    connectors: Vec<api_models::enums::Connector>,
     surcharge: Option<api_models::payment_methods::SurchargeDetailsResponse>,
 }
 
@@ -131,7 +227,105 @@ struct RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes(
     Vec<RequiredFieldsAndSurchargeForEnabledPaymentMethodType>,
 );
 
+fn get_pm_subtype_specific_data(
+    bank_config: &settings::BankRedirectConfig,
+    payment_method_type: common_enums::enums::PaymentMethod,
+    payment_method_subtype: common_enums::enums::PaymentMethodType,
+    connectors: &[api_models::enums::Connector],
+) -> Option<api_models::payment_methods::PaymentMethodSubtypeSpecificData> {
+    match payment_method_type {
+        // TODO: Return card_networks
+        common_enums::PaymentMethod::Card | common_enums::PaymentMethod::CardRedirect => None,
+
+        common_enums::PaymentMethod::BankRedirect
+        | common_enums::PaymentMethod::BankTransfer
+        | common_enums::PaymentMethod::BankDebit
+        | common_enums::PaymentMethod::OpenBanking => {
+            if let Some(connector_bank_names) = bank_config.0.get(&payment_method_subtype) {
+                let bank_names = connectors
+                    .iter()
+                    .filter_map(|connector| {
+                        connector_bank_names.0.get(&connector.to_string())
+                            .map(|connector_hash_set| {
+                                connector_hash_set.banks.clone()
+                            })
+                            .or_else(|| {
+                                logger::debug!("Could not find any configured connectors for payment_method -> {payment_method_subtype} for connector -> {connector}");
+                                None
+                            })
+                    })
+                    .flatten()
+                    .collect();
+                Some(
+                    api_models::payment_methods::PaymentMethodSubtypeSpecificData::Bank {
+                        bank_names,
+                    },
+                )
+            } else {
+                logger::debug!("Could not find any configured banks for payment_method -> {payment_method_subtype}");
+                None
+            }
+        }
+
+        common_enums::PaymentMethod::PayLater
+        | common_enums::PaymentMethod::Wallet
+        | common_enums::PaymentMethod::Crypto
+        | common_enums::PaymentMethod::Reward
+        | common_enums::PaymentMethod::RealTimePayment
+        | common_enums::PaymentMethod::Upi
+        | common_enums::PaymentMethod::Voucher
+        | common_enums::PaymentMethod::GiftCard
+        | common_enums::PaymentMethod::MobilePayment => None,
+    }
+}
+
 impl RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
+    fn populate_pm_subtype_specific_data(
+        self,
+        bank_config: &settings::BankRedirectConfig,
+    ) -> RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes {
+        let response_payment_methods = self
+            .0
+            .into_iter()
+            .map(|payment_methods_enabled| {
+                RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType {
+                    payment_method_type: payment_methods_enabled.payment_method_type,
+                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
+                    payment_experience: payment_methods_enabled.payment_experience,
+                    required_field: payment_methods_enabled.required_field,
+                    surcharge: payment_methods_enabled.surcharge,
+                    pm_subtype_specific_data: get_pm_subtype_specific_data(
+                        bank_config,
+                        payment_methods_enabled.payment_method_type,
+                        payment_methods_enabled.payment_method_subtype,
+                        &payment_methods_enabled.connectors,
+                    ),
+                }
+            })
+            .collect();
+
+        RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes(
+            response_payment_methods,
+        )
+    }
+}
+
+/// Element Container to hold the filtered payment methods enabled with required fields, surcharge and subtype specific data
+struct RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType {
+    required_field: Option<Vec<api_models::payment_methods::RequiredFieldInfo>>,
+    payment_method_subtype: common_enums::PaymentMethodType,
+    payment_method_type: common_enums::PaymentMethod,
+    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
+    surcharge: Option<api_models::payment_methods::SurchargeDetailsResponse>,
+    pm_subtype_specific_data: Option<api_models::payment_methods::PaymentMethodSubtypeSpecificData>,
+}
+
+/// Container to hold the filtered payment methods enabled with required fields, surcharge and subtype specific data
+struct RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes(
+    Vec<RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType>,
+);
+
+impl RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes {
     fn generate_response(
         self,
         customer_payment_methods: Option<
@@ -148,7 +342,7 @@ impl RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
                     payment_experience: payment_methods_enabled.payment_experience,
                     required_fields: payment_methods_enabled.required_field,
                     surcharge_details: payment_methods_enabled.surcharge,
-                    extra_information: None,
+                    extra_information: payment_methods_enabled.pm_subtype_specific_data,
                 }
             })
             .collect();
@@ -160,40 +354,6 @@ impl RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
     }
 }
 
-impl RequiredFieldsForEnabledPaymentMethodTypes {
-    fn perform_surcharge_calculation(
-        self,
-    ) -> RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
-        let details_with_surcharge = self
-            .0
-            .into_iter()
-            .map(
-                |payment_methods_enabled| RequiredFieldsAndSurchargeForEnabledPaymentMethodType {
-                    payment_method_type: payment_methods_enabled.payment_method_type,
-                    required_field: payment_methods_enabled.required_field,
-                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
-                    payment_experience: payment_methods_enabled.payment_experience,
-                    surcharge: None,
-                },
-            )
-            .collect();
-
-        RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes(details_with_surcharge)
-    }
-}
-
-trait PerformFilteringOnPaymentMethodsEnabled {
-    fn perform_filtering(self) -> FilteredPaymentMethodsEnabled;
-}
-
-impl PerformFilteringOnPaymentMethodsEnabled
-    for hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled
-{
-    fn perform_filtering(self) -> FilteredPaymentMethodsEnabled {
-        FilteredPaymentMethodsEnabled(self.payment_methods_enabled)
-    }
-}
-
 /// Validate if payment methods list can be performed on the current status of payment intent
 fn validate_payment_status_for_payment_method_list(
     intent_status: common_enums::IntentStatus,

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

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

## Description
<!-- Describe your changes in detail -->

- Merge `payment_experience` for a pm_type, subtype into an array
- Return `bank_names`

### Additional Changes

- [x] 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).
-->

Closes #8582 

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

Request:

```
curl --location 'http://localhost:8080/v2/payments/12345_pay_0197ee3c52887d51b074cecb88687a33/payment-methods' \
--header 'Content-Type: application/json' \
--header 'Authorization: publishable-key=pk_dev_bef28bbe20d64d52bf4658ad5f7bc91e,client-secret=cs_0197ee3c52bd71a38bf7932a4484b345' \
--header 'X-Profile-Id: pro_eW12tyr48EHRHFF9MfLa' \
--header 'api-key: pk_dev_bef28bbe20d64d52bf4658ad5f7bc91e'
```


Response:

```json
{
    "payment_methods_enabled": [
        {
            "payment_method_type": "card",
            "payment_method_subtype": "card",
            "payment_experience": null,
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "card_redirect",
            "payment_method_subtype": "card_redirect",
            "payment_experience": [
                "redirect_to_url"
            ],
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "pay_later",
            "payment_method_subtype": "klarna",
            "payment_experience": [
                "redirect_to_url",
                "invoke_sdk_client"
            ],
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "wallet",
            "payment_method_subtype": "google_pay",
            "payment_experience": null,
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "wallet",
            "payment_method_subtype": "paypal",
            "payment_experience": null,
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "bank_redirect",
            "payment_method_subtype": "eps",
            "payment_experience": [
                "redirect_to_url"
            ],
            "bank_names": [
                "posojilnica_bank_e_gen",
                "schoellerbank_ag",
                "volksbank_gruppe",
                "hypo_tirol_bank_ag",
                "volkskreditbank_ag",
                "bawag_psk_ag",
                "easybank_ag",
                "raiffeisen_bankengruppe_osterreich",
                "sparda_bank_wien",
                "dolomitenbank",
                "erste_bank_und_sparkassen",
                "bank_austria"
            ],
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "bank_redirect",
            "payment_method_subtype": "ideal",
            "payment_experience": [
                "redirect_to_url"
            ],
            "bank_names": [
                "n26",
                "knab",
                "nationale_nederlanden",
                "ing",
                "rabobank",
                "sns_bank",
                "asn_bank",
                "bunq",
                "revolut",
                "triodos_bank",
                "regiobank",
                "abn_amro",
                "van_lanschot",
                "yoursafe"
            ],
            "required_fields": null,
            "surcharge_details": null
        },
        {
            "payment_method_type": "bank_debit",
            "payment_method_subtype": "sepa",
            "payment_experience": null,
            "bank_names": [],
            "required_fields": null,
            "surcharge_details": null
        }
    ],
    "customer_payment_methods": []
}
```

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