mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +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 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,15 +243,26 @@ 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())) | ||||
|             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, None, None) | ||||
|             }; | ||||
|  | ||||
|             let account_details = | ||||
| @ -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); | ||||
|  | ||||
| @ -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)] | ||||
|  | ||||
| @ -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<enums::PaymentMethodType, String>, | ||||
|     > = 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 | ||||
|                 if let Some(config) = pm_auth_config { | ||||
|                     config | ||||
|                         .enabled_payment_methods | ||||
|                         .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(); | ||||
|  | ||||
|                         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(), | ||||
|                                     )]) | ||||
|                                 }; | ||||
|  | ||||
|                 if let Some(config) = matched_config { | ||||
|                                 pmt_to_auth_connector | ||||
|                         .insert(*payment_method_type, config.connector_name.clone()); | ||||
|                     val.push(config); | ||||
|                                     .insert(inner_config.payment_method, inner_map); | ||||
|                                 val.push(inner_config.clone()); | ||||
|                             } | ||||
|                         }); | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -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(), | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sarthak Soni
					Sarthak Soni