mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +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,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); | ||||||
|  | |||||||
| @ -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() |                         .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(), | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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
	 Sarthak Soni
					Sarthak Soni