Bug: feat: List payment method API access needs to be enabled using client secret for SDK calls



### Description

Need to enable access for list payment methods API using client secret for SDK calls. This will help SDK show only those payment methods that are enabled by the merchant on their dashboard. 

We would also need to filter the payment methods by applying the minimum/maximum amount, accepted_countries, accepted_currencies and recurring_payment_enabled conditions that the merchant has set for each of the payment methods

### Changes

a) List payment method API needs to take client secret and return a list of eligible payment methods for that payment
b) Using the client_secret, fetch the payment id and the relevant payment details (amount, currency, billing_country, mandate data) so that the above conditions can be checked for each payment methods enabled by the merchant and thereafter filter them
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 6556f043f1b..96b5bc02d04 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -71,7 +71,7 @@ pub struct ListPaymentMethodRequest {
     pub client_secret: Option<String>,
     pub accepted_countries: Option<Vec<String>>,
     pub accepted_currencies: Option<Vec<api_enums::Currency>>,
-    pub amount: Option<i32>,
+    pub amount: Option<i64>,
     pub recurring_enabled: Option<bool>,
     pub installment_payment_enabled: Option<bool>,
 }
@@ -174,8 +174,8 @@ pub struct ListPaymentMethodResponse {
     pub payment_schemes: Option<Vec<String>>,
     pub accepted_countries: Option<Vec<String>>,
     pub accepted_currencies: Option<Vec<api_enums::Currency>>,
-    pub minimum_amount: Option<i32>,
-    pub maximum_amount: Option<i32>,
+    pub minimum_amount: Option<i64>,
+    pub maximum_amount: Option<i64>,
     pub recurring_enabled: bool,
     pub installment_payment_enabled: bool,
     pub payment_experience: Option<Vec<PaymentExperience>>,
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 06ba52964df..a4d3b1cdfdd 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -1,6 +1,6 @@
 use std::collections;
 
-use common_utils::{consts, generate_id};
+use common_utils::{consts, ext_traits::AsyncExt, generate_id};
 use error_stack::{report, ResultExt};
 use router_env::{instrument, tracing};
 
@@ -355,13 +355,35 @@ pub async fn list_payment_methods(
     merchant_account: storage::MerchantAccount,
     mut req: api::ListPaymentMethodRequest,
 ) -> errors::RouterResponse<Vec<api::ListPaymentMethodResponse>> {
-    helpers::verify_client_secret(
+    let payment_intent = helpers::verify_client_secret(
         db,
         merchant_account.storage_scheme,
         req.client_secret.clone(),
         &merchant_account.merchant_id,
     )
     .await?;
+    let address = payment_intent
+        .as_ref()
+        .async_map(|pi| async {
+            helpers::get_address_by_id(db, pi.billing_address_id.clone()).await
+        })
+        .await
+        .transpose()?
+        .flatten();
+
+    let payment_attempt = payment_intent
+        .as_ref()
+        .async_map(|pi| async {
+            db.find_payment_attempt_by_payment_id_merchant_id(
+                &pi.payment_id,
+                &pi.merchant_id,
+                merchant_account.storage_scheme,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::PaymentNotFound)
+        })
+        .await
+        .transpose()?;
 
     let all_mcas = db
         .find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
@@ -378,19 +400,31 @@ pub async fn list_payment_methods(
             None => continue,
         };
 
-        filter_payment_methods(payment_methods, &mut req, &mut response);
+        filter_payment_methods(
+            payment_methods,
+            &mut req,
+            &mut response,
+            payment_intent.as_ref(),
+            payment_attempt.as_ref(),
+            address.as_ref(),
+        )
+        .await?;
     }
+
     response
         .is_empty()
         .then(|| Err(report!(errors::ApiErrorResponse::PaymentMethodNotFound)))
         .unwrap_or(Ok(services::ApplicationResponse::Json(response)))
 }
 
-fn filter_payment_methods(
+async fn filter_payment_methods(
     payment_methods: Vec<serde_json::Value>,
     req: &mut api::ListPaymentMethodRequest,
     resp: &mut Vec<api::ListPaymentMethodResponse>,
-) {
+    payment_intent: Option<&storage::PaymentIntent>,
+    payment_attempt: Option<&storage::PaymentAttempt>,
+    address: Option<&storage::Address>,
+) -> errors::CustomResult<(), errors::ApiErrorResponse> {
     for payment_method in payment_methods.into_iter() {
         if let Ok(payment_method_object) =
             serde_json::from_value::<api::ListPaymentMethodResponse>(payment_method)
@@ -420,13 +454,23 @@ fn filter_payment_methods(
                     &payment_method_object.accepted_currencies,
                     &req.accepted_currencies,
                 );
+                let filter3 = if let Some(payment_intent) = payment_intent {
+                    filter_payment_country_based(&payment_method_object, address).await?
+                        && filter_payment_currency_based(payment_intent, &payment_method_object)
+                        && filter_payment_amount_based(payment_intent, &payment_method_object)
+                        && filter_payment_mandate_based(payment_attempt, &payment_method_object)
+                            .await?
+                } else {
+                    true
+                };
 
-                if filter && filter2 {
+                if filter && filter2 && filter3 {
                     resp.push(payment_method_object);
                 }
             }
         }
     }
+    Ok(())
 }
 
 fn filter_accepted_enum_based<T: Eq + std::hash::Hash + Clone>(
@@ -449,7 +493,7 @@ fn filter_accepted_enum_based<T: Eq + std::hash::Hash + Clone>(
 
 fn filter_amount_based(
     payment_method: &api::ListPaymentMethodResponse,
-    amount: Option<i32>,
+    amount: Option<i64>,
 ) -> bool {
     let min_check = amount
         .and_then(|amt| payment_method.minimum_amount.map(|min_amt| amt >= min_amt))
@@ -484,6 +528,51 @@ fn filter_installment_based(
     })
 }
 
+async fn filter_payment_country_based(
+    pm: &api::ListPaymentMethodResponse,
+    address: Option<&storage::Address>,
+) -> errors::CustomResult<bool, errors::ApiErrorResponse> {
+    Ok(address.map_or(true, |address| {
+        address.country.as_ref().map_or(true, |country| {
+            pm.accepted_countries
+                .clone()
+                .map_or(true, |ac| ac.contains(country))
+        })
+    }))
+}
+
+fn filter_payment_currency_based(
+    payment_intent: &storage::PaymentIntent,
+    pm: &api::ListPaymentMethodResponse,
+) -> bool {
+    payment_intent.currency.map_or(true, |currency| {
+        pm.accepted_currencies
+            .clone()
+            .map_or(true, |ac| ac.contains(&currency.foreign_into()))
+    })
+}
+
+fn filter_payment_amount_based(
+    payment_intent: &storage::PaymentIntent,
+    pm: &api::ListPaymentMethodResponse,
+) -> bool {
+    let amount = payment_intent.amount;
+    pm.maximum_amount.map_or(true, |amt| amount < amt)
+        && pm.minimum_amount.map_or(true, |amt| amount > amt)
+}
+
+async fn filter_payment_mandate_based(
+    payment_attempt: Option<&storage::PaymentAttempt>,
+    pm: &api::ListPaymentMethodResponse,
+) -> errors::CustomResult<bool, errors::ApiErrorResponse> {
+    let recurring_filter = if !pm.recurring_enabled {
+        payment_attempt.map_or(true, |pa| pa.mandate_id.is_none())
+    } else {
+        true
+    };
+    Ok(recurring_filter)
+}
+
 pub async fn list_customer_payment_method(
     state: &routes::AppState,
     merchant_account: storage::MerchantAccount,
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 0b8f13656ff..d1f25cb1f5f 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -1,5 +1,6 @@
 use std::borrow::Cow;
 
+use common_utils::ext_traits::AsyncExt;
 // TODO : Evaluate all the helper functions ()
 use error_stack::{report, IntoReport, ResultExt};
 use masking::{ExposeOptionInterface, PeekInterface};
@@ -1353,11 +1354,10 @@ pub(crate) async fn verify_client_secret(
     storage_scheme: storage_enums::MerchantStorageScheme,
     client_secret: Option<String>,
     merchant_id: &str,
-) -> error_stack::Result<(), errors::ApiErrorResponse> {
-    match client_secret {
-        None => Ok(()),
-        Some(cs) => {
-            let payment_id = cs.split('_').take(2).collect::<Vec<&str>>().join("_");
+) -> error_stack::Result<Option<storage::PaymentIntent>, errors::ApiErrorResponse> {
+    client_secret
+        .async_map(|cs| async move {
+            let payment_id = get_payment_id_from_client_secret(&cs);
 
             let payment_intent = db
                 .find_payment_intent_by_payment_id_merchant_id(
@@ -1369,9 +1369,16 @@ pub(crate) async fn verify_client_secret(
                 .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
 
             authenticate_client_secret(Some(&cs), payment_intent.client_secret.as_ref())
-                .map_err(|err| err.into())
-        }
-    }
+                .map_err(errors::ApiErrorResponse::from)?;
+            Ok(payment_intent)
+        })
+        .await
+        .transpose()
+}
+
+#[inline]
+pub(crate) fn get_payment_id_from_client_secret(cs: &str) -> String {
+    cs.split('_').take(2).collect::<Vec<&str>>().join("_")
 }
 
 #[cfg(test)]
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index f7b7d868ffb..360989662f1 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -181,8 +181,7 @@ impl MerchantConnectorAccount {
                     .route(web::get().to(payment_connector_list)),
             )
             .service(
-                web::resource("/{merchant_id}/payment_methods")
-                    .route(web::get().to(list_payment_method_api)),
+                web::resource("/payment_methods").route(web::get().to(list_payment_method_api)),
             )
             .service(
                 web::resource("/{merchant_id}/connectors/{merchant_connector_id}")
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index 93da254a9e8..d21d584c1ec 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -32,7 +32,6 @@ pub async fn create_payment_method_api(
 pub async fn list_payment_method_api(
     state: web::Data<AppState>,
     req: HttpRequest,
-    _merchant_id: web::Path<String>,
     json_payload: web::Query<payment_methods::ListPaymentMethodRequest>,
 ) -> HttpResponse {
     let payload = json_payload.into_inner();
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 4df1897b99c..b05984bdf60 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -279,6 +279,11 @@ impl From<F<api_enums::Currency>> for F<storage_enums::Currency> {
         Self(frunk::labelled_convert_from(currency.0))
     }
 }
+impl From<F<storage_enums::Currency>> for F<api_enums::Currency> {
+    fn from(currency: F<storage_enums::Currency>) -> Self {
+        Self(frunk::labelled_convert_from(currency.0))
+    }
+}
 
 impl<'a> From<F<&'a api_types::Address>> for F<storage::AddressUpdate> {
     fn from(address: F<&api_types::Address>) -> Self {

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

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates

## Description
<!-- Describe your changes in detail -->
Filter payment methods based on the current payment that's happening to only show relevant payment methods. More details in #323

### Additional Changes

- [ ] 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 its an obvious bug or documentation fix
that will have little conversation).
-->
This closes #323 


## 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)?
-->
<img width="1728" alt="image" src="https://user-images.githubusercontent.com/43412619/211487264-9eebeb0f-d221-44a3-9619-a061c038f6aa.png">


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

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