Bug: [FEATURE] Add a balance check for bank account during AIS flow



### Feature Description

For the open banking AIS flow, we need to add a check o the available balance in the selected bank account while doing a payment. If the check fails, the payment should not go through.

### Possible Implementation

Just a few type changes and a check in core should work

### 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/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs
index 9d903914483..8ce5aca7b83 100644
--- a/crates/pm_auth/src/connector/plaid/transformers.rs
+++ b/crates/pm_auth/src/connector/plaid/transformers.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
 use common_enums::{PaymentMethod, PaymentMethodType};
-use common_utils::id_type;
+use common_utils::{id_type, types as util_types};
 use masking::{PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
 
@@ -120,7 +120,7 @@ pub struct PlaidBankAccountCredentialsRequest {
     options: Option<BankAccountCredentialsOptions>,
 }
 
-#[derive(Debug, Deserialize, Eq, PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 
 pub struct PlaidBankAccountCredentialsResponse {
     pub accounts: Vec<PlaidBankAccountCredentialsAccounts>,
@@ -135,19 +135,20 @@ pub struct BankAccountCredentialsOptions {
     account_ids: Vec<String>,
 }
 
-#[derive(Debug, Deserialize, Eq, PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 
 pub struct PlaidBankAccountCredentialsAccounts {
     pub account_id: String,
     pub name: String,
     pub subtype: Option<String>,
+    pub balances: Option<PlaidBankAccountCredentialsBalances>,
 }
 
-#[derive(Debug, Deserialize, Eq, PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 pub struct PlaidBankAccountCredentialsBalances {
-    pub available: Option<i32>,
-    pub current: Option<i32>,
-    pub limit: Option<i32>,
+    pub available: Option<util_types::FloatMajorUnit>,
+    pub current: Option<util_types::FloatMajorUnit>,
+    pub limit: Option<util_types::FloatMajorUnit>,
     pub iso_currency_code: Option<String>,
     pub unofficial_currency_code: Option<String>,
     pub last_updated_datetime: Option<String>,
@@ -242,16 +243,27 @@ impl<F, T>
         let mut id_to_subtype = HashMap::new();
 
         accounts_info.into_iter().for_each(|acc| {
-            id_to_subtype.insert(acc.account_id, (acc.subtype, acc.name));
+            id_to_subtype.insert(
+                acc.account_id,
+                (
+                    acc.subtype,
+                    acc.name,
+                    acc.balances.and_then(|balance| balance.available),
+                ),
+            );
         });
 
         account_numbers.ach.into_iter().for_each(|ach| {
-            let (acc_type, acc_name) =
-                if let Some((_type, name)) = id_to_subtype.get(&ach.account_id) {
-                    (_type.to_owned(), Some(name.clone()))
-                } else {
-                    (None, None)
-                };
+            let (acc_type, acc_name, available_balance) = if let Some((
+                _type,
+                name,
+                available_balance,
+            )) = id_to_subtype.get(&ach.account_id)
+            {
+                (_type.to_owned(), Some(name.clone()), *available_balance)
+            } else {
+                (None, None, None)
+            };
 
             let account_details =
                 types::PaymentMethodTypeDetails::Ach(types::BankAccountDetailsAch {
@@ -266,17 +278,19 @@ impl<F, T>
                 payment_method: PaymentMethod::BankDebit,
                 account_id: ach.account_id.into(),
                 account_type: acc_type,
+                balance: available_balance,
             };
 
             bank_account_vec.push(bank_details_new);
         });
 
         account_numbers.bacs.into_iter().for_each(|bacs| {
-            let (acc_type, acc_name) =
-                if let Some((_type, name)) = id_to_subtype.get(&bacs.account_id) {
-                    (_type.to_owned(), Some(name.clone()))
+            let (acc_type, acc_name, available_balance) =
+                if let Some((_type, name, available_balance)) = id_to_subtype.get(&bacs.account_id)
+                {
+                    (_type.to_owned(), Some(name.clone()), *available_balance)
                 } else {
-                    (None, None)
+                    (None, None, None)
                 };
 
             let account_details =
@@ -292,17 +306,19 @@ impl<F, T>
                 payment_method: PaymentMethod::BankDebit,
                 account_id: bacs.account_id.into(),
                 account_type: acc_type,
+                balance: available_balance,
             };
 
             bank_account_vec.push(bank_details_new);
         });
 
         account_numbers.international.into_iter().for_each(|sepa| {
-            let (acc_type, acc_name) =
-                if let Some((_type, name)) = id_to_subtype.get(&sepa.account_id) {
-                    (_type.to_owned(), Some(name.clone()))
+            let (acc_type, acc_name, available_balance) =
+                if let Some((_type, name, available_balance)) = id_to_subtype.get(&sepa.account_id)
+                {
+                    (_type.to_owned(), Some(name.clone()), *available_balance)
                 } else {
-                    (None, None)
+                    (None, None, None)
                 };
 
             let account_details =
@@ -318,6 +334,7 @@ impl<F, T>
                 payment_method: PaymentMethod::BankDebit,
                 account_id: sepa.account_id.into(),
                 account_type: acc_type,
+                balance: available_balance,
             };
 
             bank_account_vec.push(bank_details_new);
diff --git a/crates/pm_auth/src/types.rs b/crates/pm_auth/src/types.rs
index f2a848c2f60..2537cdc6a36 100644
--- a/crates/pm_auth/src/types.rs
+++ b/crates/pm_auth/src/types.rs
@@ -4,7 +4,7 @@ use std::marker::PhantomData;
 
 use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken};
 use common_enums::{PaymentMethod, PaymentMethodType};
-use common_utils::id_type;
+use common_utils::{id_type, types};
 use masking::Secret;
 #[derive(Debug, Clone)]
 pub struct PaymentAuthRouterData<F, Request, Response> {
@@ -78,6 +78,7 @@ pub struct BankAccountDetails {
     pub payment_method: PaymentMethod,
     pub account_id: Secret<String>,
     pub account_type: Option<String>,
+    pub balance: Option<types::FloatMajorUnit>,
 }
 
 #[derive(Debug, Clone)]
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 41007286b83..dad7bd79248 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -2021,7 +2021,10 @@ pub async fn list_payment_methods(
         .await
         .transpose()?;
 
-    let mut pmt_to_auth_connector = HashMap::new();
+    let mut pmt_to_auth_connector: HashMap<
+        enums::PaymentMethod,
+        HashMap<enums::PaymentMethodType, String>,
+    > = HashMap::new();
 
     if let Some((payment_attempt, payment_intent)) =
         payment_attempt.as_ref().zip(payment_intent.as_ref())
@@ -2195,24 +2198,35 @@ pub async fn list_payment_methods(
                         None
                     });
 
-                let matched_config = match pm_auth_config {
-                    Some(config) => {
-                        let internal_config = config
-                            .enabled_payment_methods
-                            .iter()
-                            .find(|config| config.payment_method_type == *payment_method_type)
-                            .cloned();
-
-                        internal_config
-                    }
-                    None => None,
+                if let Some(config) = pm_auth_config {
+                    config
+                        .enabled_payment_methods
+                        .iter()
+                        .for_each(|inner_config| {
+                            if inner_config.payment_method_type == *payment_method_type {
+                                let pm = pmt_to_auth_connector
+                                    .get(&inner_config.payment_method)
+                                    .cloned();
+
+                                let inner_map = if let Some(mut inner_map) = pm {
+                                    inner_map.insert(
+                                        *payment_method_type,
+                                        inner_config.connector_name.clone(),
+                                    );
+                                    inner_map
+                                } else {
+                                    HashMap::from([(
+                                        *payment_method_type,
+                                        inner_config.connector_name.clone(),
+                                    )])
+                                };
+
+                                pmt_to_auth_connector
+                                    .insert(inner_config.payment_method, inner_map);
+                                val.push(inner_config.clone());
+                            }
+                        });
                 };
-
-                if let Some(config) = matched_config {
-                    pmt_to_auth_connector
-                        .insert(*payment_method_type, config.connector_name.clone());
-                    val.push(config);
-                }
             }
         }
 
@@ -2522,7 +2536,8 @@ pub async fn list_payment_methods(
                     .cloned(),
                 surcharge_details: None,
                 pm_auth_connector: pmt_to_auth_connector
-                    .get(payment_method_types_hm.0)
+                    .get(key.0)
+                    .and_then(|pm_map| pm_map.get(payment_method_types_hm.0))
                     .cloned(),
             })
         }
@@ -2559,7 +2574,8 @@ pub async fn list_payment_methods(
                     .cloned(),
                 surcharge_details: None,
                 pm_auth_connector: pmt_to_auth_connector
-                    .get(payment_method_types_hm.0)
+                    .get(key.0)
+                    .and_then(|pm_map| pm_map.get(payment_method_types_hm.0))
                     .cloned(),
             })
         }
@@ -2590,7 +2606,10 @@ pub async fn list_payment_methods(
                     .and_then(|inner_hm| inner_hm.get(key.0))
                     .cloned(),
                 surcharge_details: None,
-                pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(),
+                pm_auth_connector: pmt_to_auth_connector
+                    .get(&enums::PaymentMethod::BankRedirect)
+                    .and_then(|pm_map| pm_map.get(key.0))
+                    .cloned(),
             }
         })
     }
@@ -2623,7 +2642,10 @@ pub async fn list_payment_methods(
                     .and_then(|inner_hm| inner_hm.get(key.0))
                     .cloned(),
                 surcharge_details: None,
-                pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(),
+                pm_auth_connector: pmt_to_auth_connector
+                    .get(&enums::PaymentMethod::BankDebit)
+                    .and_then(|pm_map| pm_map.get(key.0))
+                    .cloned(),
             }
         })
     }
@@ -2656,7 +2678,10 @@ pub async fn list_payment_methods(
                     .and_then(|inner_hm| inner_hm.get(key.0))
                     .cloned(),
                 surcharge_details: None,
-                pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(),
+                pm_auth_connector: pmt_to_auth_connector
+                    .get(&enums::PaymentMethod::BankTransfer)
+                    .and_then(|pm_map| pm_map.get(key.0))
+                    .cloned(),
             }
         })
     }
diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs
index e78c2153bb7..508b9e8ef8b 100644
--- a/crates/router/src/core/pm_auth.rs
+++ b/crates/router/src/core/pm_auth.rs
@@ -15,6 +15,7 @@ use common_utils::{
     crypto::{HmacSha256, SignMessage},
     ext_traits::AsyncExt,
     generate_id,
+    types::{self as util_types, AmountConvertor},
 };
 use error_stack::ResultExt;
 use helpers::PaymentAuthConnectorDataExt;
@@ -66,6 +67,19 @@ pub async fn create_link_token(
 
     let pm_auth_key = format!("pm_auth_{}", payload.payment_id);
 
+    redis_conn
+        .exists::<Vec<u8>>(&pm_auth_key)
+        .await
+        .change_context(ApiErrorResponse::InvalidRequestData {
+            message: "Incorrect payment_id provided in request".to_string(),
+        })
+        .attach_printable("Corresponding pm_auth_key does not exist in redis")?
+        .then_some(())
+        .ok_or(ApiErrorResponse::InvalidRequestData {
+            message: "Incorrect payment_id provided in request".to_string(),
+        })
+        .attach_printable("Corresponding pm_auth_key does not exist in redis")?;
+
     let pm_auth_configs = redis_conn
         .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
             pm_auth_key.as_str(),
@@ -637,6 +651,19 @@ async fn get_selected_config_from_redis(
 
     let pm_auth_key = format!("pm_auth_{}", payload.payment_id);
 
+    redis_conn
+        .exists::<Vec<u8>>(&pm_auth_key)
+        .await
+        .change_context(ApiErrorResponse::InvalidRequestData {
+            message: "Incorrect payment_id provided in request".to_string(),
+        })
+        .attach_printable("Corresponding pm_auth_key does not exist in redis")?
+        .then_some(())
+        .ok_or(ApiErrorResponse::InvalidRequestData {
+            message: "Incorrect payment_id provided in request".to_string(),
+        })
+        .attach_printable("Corresponding pm_auth_key does not exist in redis")?;
+
     let pm_auth_configs = redis_conn
         .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
             pm_auth_key.as_str(),
@@ -720,6 +747,21 @@ pub async fn retrieve_payment_method_from_auth_service(
         .ok_or(ApiErrorResponse::InternalServerError)
         .attach_printable("Bank account details not found")?;
 
+    if let (Some(balance), Some(currency)) = (bank_account.balance, payment_intent.currency) {
+        let required_conversion = util_types::FloatMajorUnitForConnector;
+        let converted_amount = required_conversion
+            .convert_back(balance, currency)
+            .change_context(ApiErrorResponse::InternalServerError)
+            .attach_printable("Could not convert FloatMajorUnit to MinorUnit")?;
+
+        if converted_amount < payment_intent.amount {
+            return Err((ApiErrorResponse::PreconditionFailed {
+                message: "selected bank account has insufficient balance".to_string(),
+            })
+            .into());
+        }
+    }
+
     let mut bank_type = None;
     if let Some(account_type) = bank_account.account_type.clone() {
         bank_type = common_enums::BankType::from_str(account_type.as_str())

## 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 -->
have added a balance check that would enable a check on the available amount in the selected bank account while doing a payment.
If the found balance is less than the intent amount, the payment would not go through.


### 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)?
-->
Follow this for TCs - https://github.com/juspay/hyperswitch/pull/3047
In the payment create, use amount greater then 150 USD for the check to work

- Payment create - 
```
curl --location --request POST 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_0u355Qt5nbq8BhTZtWmbkwzQJ4JY77u0jOpjyIi3LRqIFziLTFKI8E6QwgFmaFc5' \
--data-raw '{
    "amount": 65400,
    "currency": "USD",
    "confirm": false,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 65400,
    "customer_id": "StripeCustomer",
    "business_country": "US",
    "business_label": "default",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594430",
            "country_code": "+91"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
- Payment confirm 
```
curl --location --request POST 'http://localhost:8080/payments/pay_FKpvykZd46XL9grQwPlC/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_9cdfbdb024614790a4bf730b19a8c375' \
--data-raw '{
    "payment_token": "token_Yxogj2YxdeHjC316ZtM9",
    "client_secret": "pay_FKpvykZd46XL9grQwPlC_secret_KQLB47ADsiyrIhIpOvxv",
    "payment_method": "bank_debit",
    "payment_method_type": "ach"
}'
```
Response
```
{
    "error": {
        "type": "invalid_request",
        "message": "Payment amount is greater than the available amount in selected bank account",
        "code": "IR_06"
    }
}
```

## 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`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
