mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(pm_auth): Added balance check for PM auth bank account (#5054)
This commit is contained in:
@ -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,15 +243,26 @@ 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,
|
||||||
|
available_balance,
|
||||||
|
)) = id_to_subtype.get(&ach.account_id)
|
||||||
|
{
|
||||||
|
(_type.to_owned(), Some(name.clone()), *available_balance)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let account_details =
|
let account_details =
|
||||||
@ -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);
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
@ -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()
|
||||||
.find(|config| config.payment_method_type == *payment_method_type)
|
.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();
|
.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(),
|
||||||
|
)])
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(config) = matched_config {
|
|
||||||
pmt_to_auth_connector
|
pmt_to_auth_connector
|
||||||
.insert(*payment_method_type, config.connector_name.clone());
|
.insert(inner_config.payment_method, inner_map);
|
||||||
val.push(config);
|
val.push(inner_config.clone());
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user