From f513c8e4daa95a6ceb89ce616e3d55058708fb2a Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:17:52 +0530 Subject: [PATCH] feat(pm_auth): Added balance check for PM auth bank account (#5054) --- .../src/connector/plaid/transformers.rs | 61 ++++++++++------ crates/pm_auth/src/types.rs | 3 +- .../router/src/core/payment_methods/cards.rs | 69 +++++++++++++------ crates/router/src/core/pm_auth.rs | 42 +++++++++++ 4 files changed, 130 insertions(+), 45 deletions(-) diff --git a/crates/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs index 9d90391448..8ce5aca7b8 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, } -#[derive(Debug, Deserialize, Eq, PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub struct PlaidBankAccountCredentialsResponse { pub accounts: Vec, @@ -135,19 +135,20 @@ pub struct BankAccountCredentialsOptions { account_ids: Vec, } -#[derive(Debug, Deserialize, Eq, PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub struct PlaidBankAccountCredentialsAccounts { pub account_id: String, pub name: String, pub subtype: Option, + pub balances: Option, } -#[derive(Debug, Deserialize, Eq, PartialEq)] +#[derive(Debug, Deserialize, PartialEq)] pub struct PlaidBankAccountCredentialsBalances { - pub available: Option, - pub current: Option, - pub limit: Option, + pub available: Option, + pub current: Option, + pub limit: Option, pub iso_currency_code: Option, pub unofficial_currency_code: Option, pub last_updated_datetime: Option, @@ -242,16 +243,27 @@ impl 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 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 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 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 f2a848c2f6..2537cdc6a3 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 { @@ -78,6 +78,7 @@ pub struct BankAccountDetails { pub payment_method: PaymentMethod, pub account_id: Secret, pub account_type: Option, + pub balance: Option, } #[derive(Debug, Clone)] diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 1523ac18cb..e58e16cc63 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2023,7 +2023,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, + > = HashMap::new(); if let Some((payment_attempt, payment_intent)) = payment_attempt.as_ref().zip(payment_intent.as_ref()) @@ -2197,24 +2200,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(); + 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(); - internal_config - } - None => None, + 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); - } } } @@ -2524,7 +2538,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(), }) } @@ -2561,7 +2576,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(), }) } @@ -2592,7 +2608,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(), } }) } @@ -2625,7 +2644,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(), } }) } @@ -2658,7 +2680,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 e78c2153bb..508b9e8ef8 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::>(&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::>( 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::>(&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::>( 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())