feat(pm_auth): Added balance check for PM auth bank account (#5054)

This commit is contained in:
Sarthak Soni
2024-07-03 18:17:52 +05:30
committed by GitHub
parent e85407fc53
commit f513c8e4da
4 changed files with 130 additions and 45 deletions

View File

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use common_enums::{PaymentMethod, PaymentMethodType}; use common_enums::{PaymentMethod, PaymentMethodType};
use common_utils::id_type; use common_utils::{id_type, types as util_types};
use masking::{PeekInterface, Secret}; use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -120,7 +120,7 @@ pub struct PlaidBankAccountCredentialsRequest {
options: Option<BankAccountCredentialsOptions>, options: Option<BankAccountCredentialsOptions>,
} }
#[derive(Debug, Deserialize, Eq, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsResponse { pub struct PlaidBankAccountCredentialsResponse {
pub accounts: Vec<PlaidBankAccountCredentialsAccounts>, pub accounts: Vec<PlaidBankAccountCredentialsAccounts>,
@ -135,19 +135,20 @@ pub struct BankAccountCredentialsOptions {
account_ids: Vec<String>, account_ids: Vec<String>,
} }
#[derive(Debug, Deserialize, Eq, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsAccounts { pub struct PlaidBankAccountCredentialsAccounts {
pub account_id: String, pub account_id: String,
pub name: String, pub name: String,
pub subtype: Option<String>, pub subtype: Option<String>,
pub balances: Option<PlaidBankAccountCredentialsBalances>,
} }
#[derive(Debug, Deserialize, Eq, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsBalances { pub struct PlaidBankAccountCredentialsBalances {
pub available: Option<i32>, pub available: Option<util_types::FloatMajorUnit>,
pub current: Option<i32>, pub current: Option<util_types::FloatMajorUnit>,
pub limit: Option<i32>, pub limit: Option<util_types::FloatMajorUnit>,
pub iso_currency_code: Option<String>, pub iso_currency_code: Option<String>,
pub unofficial_currency_code: Option<String>, pub unofficial_currency_code: Option<String>,
pub last_updated_datetime: Option<String>, pub last_updated_datetime: Option<String>,
@ -242,16 +243,27 @@ impl<F, T>
let mut id_to_subtype = HashMap::new(); let mut id_to_subtype = HashMap::new();
accounts_info.into_iter().for_each(|acc| { 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| { account_numbers.ach.into_iter().for_each(|ach| {
let (acc_type, acc_name) = let (acc_type, acc_name, available_balance) = if let Some((
if let Some((_type, name)) = id_to_subtype.get(&ach.account_id) { _type,
(_type.to_owned(), Some(name.clone())) name,
} else { available_balance,
(None, None) )) = id_to_subtype.get(&ach.account_id)
}; {
(_type.to_owned(), Some(name.clone()), *available_balance)
} else {
(None, None, None)
};
let account_details = let account_details =
types::PaymentMethodTypeDetails::Ach(types::BankAccountDetailsAch { types::PaymentMethodTypeDetails::Ach(types::BankAccountDetailsAch {
@ -266,17 +278,19 @@ impl<F, T>
payment_method: PaymentMethod::BankDebit, payment_method: PaymentMethod::BankDebit,
account_id: ach.account_id.into(), account_id: ach.account_id.into(),
account_type: acc_type, account_type: acc_type,
balance: available_balance,
}; };
bank_account_vec.push(bank_details_new); bank_account_vec.push(bank_details_new);
}); });
account_numbers.bacs.into_iter().for_each(|bacs| { account_numbers.bacs.into_iter().for_each(|bacs| {
let (acc_type, acc_name) = let (acc_type, acc_name, available_balance) =
if let Some((_type, name)) = id_to_subtype.get(&bacs.account_id) { if let Some((_type, name, available_balance)) = id_to_subtype.get(&bacs.account_id)
(_type.to_owned(), Some(name.clone())) {
(_type.to_owned(), Some(name.clone()), *available_balance)
} else { } else {
(None, None) (None, None, None)
}; };
let account_details = let account_details =
@ -292,17 +306,19 @@ impl<F, T>
payment_method: PaymentMethod::BankDebit, payment_method: PaymentMethod::BankDebit,
account_id: bacs.account_id.into(), account_id: bacs.account_id.into(),
account_type: acc_type, account_type: acc_type,
balance: available_balance,
}; };
bank_account_vec.push(bank_details_new); bank_account_vec.push(bank_details_new);
}); });
account_numbers.international.into_iter().for_each(|sepa| { account_numbers.international.into_iter().for_each(|sepa| {
let (acc_type, acc_name) = let (acc_type, acc_name, available_balance) =
if let Some((_type, name)) = id_to_subtype.get(&sepa.account_id) { if let Some((_type, name, available_balance)) = id_to_subtype.get(&sepa.account_id)
(_type.to_owned(), Some(name.clone())) {
(_type.to_owned(), Some(name.clone()), *available_balance)
} else { } else {
(None, None) (None, None, None)
}; };
let account_details = let account_details =
@ -318,6 +334,7 @@ impl<F, T>
payment_method: PaymentMethod::BankDebit, payment_method: PaymentMethod::BankDebit,
account_id: sepa.account_id.into(), account_id: sepa.account_id.into(),
account_type: acc_type, account_type: acc_type,
balance: available_balance,
}; };
bank_account_vec.push(bank_details_new); bank_account_vec.push(bank_details_new);

View File

@ -4,7 +4,7 @@ use std::marker::PhantomData;
use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken}; use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken};
use common_enums::{PaymentMethod, PaymentMethodType}; use common_enums::{PaymentMethod, PaymentMethodType};
use common_utils::id_type; use common_utils::{id_type, types};
use masking::Secret; use masking::Secret;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PaymentAuthRouterData<F, Request, Response> { pub struct PaymentAuthRouterData<F, Request, Response> {
@ -78,6 +78,7 @@ pub struct BankAccountDetails {
pub payment_method: PaymentMethod, pub payment_method: PaymentMethod,
pub account_id: Secret<String>, pub account_id: Secret<String>,
pub account_type: Option<String>, pub account_type: Option<String>,
pub balance: Option<types::FloatMajorUnit>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -2023,7 +2023,10 @@ pub async fn list_payment_methods(
.await .await
.transpose()?; .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)) = if let Some((payment_attempt, payment_intent)) =
payment_attempt.as_ref().zip(payment_intent.as_ref()) payment_attempt.as_ref().zip(payment_intent.as_ref())
@ -2197,24 +2200,35 @@ pub async fn list_payment_methods(
None None
}); });
let matched_config = match pm_auth_config { if let Some(config) = pm_auth_config {
Some(config) => { config
let internal_config = config .enabled_payment_methods
.enabled_payment_methods .iter()
.iter() .for_each(|inner_config| {
.find(|config| config.payment_method_type == *payment_method_type) if inner_config.payment_method_type == *payment_method_type {
.cloned(); let pm = pmt_to_auth_connector
.get(&inner_config.payment_method)
.cloned();
internal_config let inner_map = if let Some(mut inner_map) = pm {
} inner_map.insert(
None => None, *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(), .cloned(),
surcharge_details: None, surcharge_details: None,
pm_auth_connector: pmt_to_auth_connector 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(), .cloned(),
}) })
} }
@ -2561,7 +2576,8 @@ pub async fn list_payment_methods(
.cloned(), .cloned(),
surcharge_details: None, surcharge_details: None,
pm_auth_connector: pmt_to_auth_connector 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(), .cloned(),
}) })
} }
@ -2592,7 +2608,10 @@ pub async fn list_payment_methods(
.and_then(|inner_hm| inner_hm.get(key.0)) .and_then(|inner_hm| inner_hm.get(key.0))
.cloned(), .cloned(),
surcharge_details: None, 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)) .and_then(|inner_hm| inner_hm.get(key.0))
.cloned(), .cloned(),
surcharge_details: None, 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)) .and_then(|inner_hm| inner_hm.get(key.0))
.cloned(), .cloned(),
surcharge_details: None, 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(),
} }
}) })
} }

View File

@ -15,6 +15,7 @@ use common_utils::{
crypto::{HmacSha256, SignMessage}, crypto::{HmacSha256, SignMessage},
ext_traits::AsyncExt, ext_traits::AsyncExt,
generate_id, generate_id,
types::{self as util_types, AmountConvertor},
}; };
use error_stack::ResultExt; use error_stack::ResultExt;
use helpers::PaymentAuthConnectorDataExt; use helpers::PaymentAuthConnectorDataExt;
@ -66,6 +67,19 @@ pub async fn create_link_token(
let pm_auth_key = format!("pm_auth_{}", payload.payment_id); 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 let pm_auth_configs = redis_conn
.get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>( .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
pm_auth_key.as_str(), 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); 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 let pm_auth_configs = redis_conn
.get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>( .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
pm_auth_key.as_str(), pm_auth_key.as_str(),
@ -720,6 +747,21 @@ pub async fn retrieve_payment_method_from_auth_service(
.ok_or(ApiErrorResponse::InternalServerError) .ok_or(ApiErrorResponse::InternalServerError)
.attach_printable("Bank account details not found")?; .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; let mut bank_type = None;
if let Some(account_type) = bank_account.account_type.clone() { if let Some(account_type) = bank_account.account_type.clone() {
bank_type = common_enums::BankType::from_str(account_type.as_str()) bank_type = common_enums::BankType::from_str(account_type.as_str())