mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	fix(list): adding configuation changes for filtering pm based on countries & currencies (#669)
				
					
				
			This commit is contained in:
		| @ -12,6 +12,7 @@ aci = "aci" # Name of a connector | |||||||
| encrypter = "encrypter"       # Used by the `ring` crate | encrypter = "encrypter"       # Used by the `ring` crate | ||||||
| nin = "nin"                   # National identification number, a field used by PayU connector | nin = "nin"                   # National identification number, a field used by PayU connector | ||||||
| substituters = "substituters" # Present in `flake.nix` | substituters = "substituters" # Present in `flake.nix` | ||||||
|  | FO = "FO"                     # Faroe Islands (the) country code | ||||||
|  |  | ||||||
| [files] | [files] | ||||||
| extend-exclude = [ | extend-exclude = [ | ||||||
|  | |||||||
| @ -140,3 +140,40 @@ stream = "SCHEDULER_STREAM" | |||||||
| [scheduler.consumer] | [scheduler.consumer] | ||||||
| disabled = false | disabled = false | ||||||
| consumer_group = "SCHEDULER_GROUP" | consumer_group = "SCHEDULER_GROUP" | ||||||
|  |  | ||||||
|  | [pm_filters.stripe] | ||||||
|  | google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } | ||||||
|  | apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } | ||||||
|  | klarna = { country = "AT,BE,DK,FI,FR,DE,IE,IT,NL,NO,ES,SE,GB,US", currency = "EUR,USD,GBP,DKK,SEK,NOK" } | ||||||
|  | affirm = { country = "US", currency = "USD" } | ||||||
|  | afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "USD,CAD,GBP,AUD,NZD,EUR" } | ||||||
|  | giropay = { country = "DE", currency = "EUR" } | ||||||
|  | eps = { country = "AT", currency = "EUR" } | ||||||
|  | sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } | ||||||
|  | ideal = { country = "NL", currency = "EUR" } | ||||||
|  |  | ||||||
|  | [pm_filters.adyen] | ||||||
|  | google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } | ||||||
|  | apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } | ||||||
|  | paypal = { currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } | ||||||
|  | klarna = { country = "AT,BE,DK,FI,FR,DE,IE,IT,NL,NO,ES,SE,GB,US,CA", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } | ||||||
|  | affirm = { country = "US", currency = "USD" } | ||||||
|  | afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "GBP" } | ||||||
|  | giropay = { country = "DE", currency = "EUR" } | ||||||
|  | eps = { country = "AT", currency = "EUR" } | ||||||
|  | sofort = { country = "ES,GB,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } | ||||||
|  | ideal = { country = "NL", currency = "EUR" } | ||||||
|  |  | ||||||
|  | [pm_filters.braintree] | ||||||
|  | paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" } | ||||||
|  |  | ||||||
|  | [pm_filters.klarna] | ||||||
|  | klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,EUR,EUR,CAD,CZK,DKK,EUR,EUR,EUR,EUR,EUR,EUR,EUR,NZD,NOK,PLN,EUR,EUR,SEK,CHF,GBP,USD" } | ||||||
|  |  | ||||||
|  | [pm_filters.authorizedotnet] | ||||||
|  | google_pay = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } | ||||||
|  | paypal = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } | ||||||
|  |  | ||||||
|  | [pm_filters.worldpay] | ||||||
|  | google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } | ||||||
|  | apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } | ||||||
|  | |||||||
| @ -204,3 +204,11 @@ num_partitions = 64            # Specifies the number of partitions the stream w | |||||||
| max_read_count = 100           # Specifies the maximum number of entries that would be read from redis stream in one call | max_read_count = 100           # Specifies the maximum number of entries that would be read from redis stream in one call | ||||||
| shutdown_interval = 1000       # Specifies how much time to wait, while waiting for threads to complete execution (in milliseconds) | shutdown_interval = 1000       # Specifies how much time to wait, while waiting for threads to complete execution (in milliseconds) | ||||||
| loop_interval = 500            # Specifies how much time to wait after checking all the possible streams in completed (in milliseconds) | loop_interval = 500            # Specifies how much time to wait after checking all the possible streams in completed (in milliseconds) | ||||||
|  |  | ||||||
|  | # Filteration logic for list payment method, allowing use to limit payment methods based on the requirement country and currency | ||||||
|  | [pm_filters.stripe] | ||||||
|  | #           ^--- This can be any connector (can be multiple) | ||||||
|  | paypal = { currency = "USD,INR", country = "US" } | ||||||
|  | # ^                       ^------- comma-separated values | ||||||
|  | # ^------------------------------- any valid payment method type (can be multiple) | ||||||
|  | # If either currency or country isn't provided then, all possible values are accepted | ||||||
|  | |||||||
| @ -1,10 +1,14 @@ | |||||||
| use std::path::PathBuf; | use std::{ | ||||||
|  |     collections::{HashMap, HashSet}, | ||||||
|  |     path::PathBuf, | ||||||
|  |     str::FromStr, | ||||||
|  | }; | ||||||
|  |  | ||||||
| use common_utils::ext_traits::ConfigExt; | use common_utils::ext_traits::ConfigExt; | ||||||
| use config::{Environment, File}; | use config::{Environment, File}; | ||||||
| use redis_interface::RedisSettings; | use redis_interface::RedisSettings; | ||||||
| pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry}; | pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry}; | ||||||
| use serde::Deserialize; | use serde::{Deserialize, Deserializer}; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::errors::{ApplicationError, ApplicationResult}, |     core::errors::{ApplicationError, ApplicationResult}, | ||||||
| @ -51,6 +55,64 @@ pub struct Settings { | |||||||
|     pub drainer: DrainerSettings, |     pub drainer: DrainerSettings, | ||||||
|     pub jwekey: Jwekey, |     pub jwekey: Jwekey, | ||||||
|     pub webhooks: WebhooksSettings, |     pub webhooks: WebhooksSettings, | ||||||
|  |     pub pm_filters: ConnectorFilters, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Deserialize, Clone, Default)] | ||||||
|  | #[serde(transparent)] | ||||||
|  | pub struct ConnectorFilters(pub HashMap<String, PaymentMethodFilters>); | ||||||
|  |  | ||||||
|  | #[derive(Debug, Deserialize, Clone, Default)] | ||||||
|  | #[serde(transparent)] | ||||||
|  | pub struct PaymentMethodFilters( | ||||||
|  |     pub HashMap<api_models::enums::PaymentMethodType, CurrencyCountryFilter>, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | #[derive(Debug, Deserialize, Clone, Default)] | ||||||
|  | #[serde(default)] | ||||||
|  | pub struct CurrencyCountryFilter { | ||||||
|  |     #[serde(deserialize_with = "currency_set_deser")] | ||||||
|  |     pub currency: Option<HashSet<api_models::enums::Currency>>, | ||||||
|  |     #[serde(deserialize_with = "string_set_deser")] | ||||||
|  |     pub country: Option<HashSet<String>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn string_set_deser<'a, D>(deserializer: D) -> Result<Option<HashSet<String>>, D::Error> | ||||||
|  | where | ||||||
|  |     D: Deserializer<'a>, | ||||||
|  | { | ||||||
|  |     let value = <Option<String>>::deserialize(deserializer)?; | ||||||
|  |     Ok(value.and_then(|inner| { | ||||||
|  |         let list = inner | ||||||
|  |             .trim() | ||||||
|  |             .split(',') | ||||||
|  |             .map(|value| value.to_string()) | ||||||
|  |             .collect::<HashSet<_>>(); | ||||||
|  |         match list.len() { | ||||||
|  |             0 => None, | ||||||
|  |             _ => Some(list), | ||||||
|  |         } | ||||||
|  |     })) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn currency_set_deser<'a, D>( | ||||||
|  |     deserializer: D, | ||||||
|  | ) -> Result<Option<HashSet<api_models::enums::Currency>>, D::Error> | ||||||
|  | where | ||||||
|  |     D: Deserializer<'a>, | ||||||
|  | { | ||||||
|  |     let value = <Option<String>>::deserialize(deserializer)?; | ||||||
|  |     Ok(value.and_then(|inner| { | ||||||
|  |         let list = inner | ||||||
|  |             .trim() | ||||||
|  |             .split(',') | ||||||
|  |             .flat_map(api_models::enums::Currency::from_str) | ||||||
|  |             .collect::<HashSet<_>>(); | ||||||
|  |         match list.len() { | ||||||
|  |             0 => None, | ||||||
|  |             _ => Some(list), | ||||||
|  |         } | ||||||
|  |     })) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Deserialize, Clone)] | #[derive(Debug, Deserialize, Clone)] | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ use error_stack::{report, ResultExt}; | |||||||
| use router_env::{instrument, tracing}; | use router_env::{instrument, tracing}; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|  |     configs::settings, | ||||||
|     core::{ |     core::{ | ||||||
|         errors::{self, StorageErrorExt}, |         errors::{self, StorageErrorExt}, | ||||||
|         payment_methods::{transformers as payment_methods, vault}, |         payment_methods::{transformers as payment_methods, vault}, | ||||||
| @ -348,10 +349,13 @@ pub async fn delete_card<'a>( | |||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn list_payment_methods( | pub async fn list_payment_methods( | ||||||
|     db: &dyn db::StorageInterface, |     state: &routes::AppState, | ||||||
|     merchant_account: storage::MerchantAccount, |     merchant_account: storage::MerchantAccount, | ||||||
|     mut req: api::ListPaymentMethodRequest, |     mut req: api::ListPaymentMethodRequest, | ||||||
| ) -> errors::RouterResponse<api::ListPaymentMethodResponse> { | ) -> errors::RouterResponse<api::ListPaymentMethodResponse> { | ||||||
|  |     let db = &*state.store; | ||||||
|  |     let pm_config_mapping = &state.conf.pm_filters; | ||||||
|  |  | ||||||
|     let payment_intent = helpers::verify_client_secret( |     let payment_intent = helpers::verify_client_secret( | ||||||
|         db, |         db, | ||||||
|         merchant_account.storage_scheme, |         merchant_account.storage_scheme, | ||||||
| @ -410,6 +414,7 @@ pub async fn list_payment_methods( | |||||||
|             payment_attempt.as_ref(), |             payment_attempt.as_ref(), | ||||||
|             address.as_ref(), |             address.as_ref(), | ||||||
|             mca.connector_name, |             mca.connector_name, | ||||||
|  |             pm_config_mapping, | ||||||
|         ) |         ) | ||||||
|         .await?; |         .await?; | ||||||
|     } |     } | ||||||
| @ -567,6 +572,7 @@ pub async fn list_payment_methods( | |||||||
|         ))) |         ))) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[allow(clippy::too_many_arguments)] | ||||||
| async fn filter_payment_methods( | async fn filter_payment_methods( | ||||||
|     payment_methods: Vec<serde_json::Value>, |     payment_methods: Vec<serde_json::Value>, | ||||||
|     req: &mut api::ListPaymentMethodRequest, |     req: &mut api::ListPaymentMethodRequest, | ||||||
| @ -575,6 +581,7 @@ async fn filter_payment_methods( | |||||||
|     payment_attempt: Option<&storage::PaymentAttempt>, |     payment_attempt: Option<&storage::PaymentAttempt>, | ||||||
|     address: Option<&storage::Address>, |     address: Option<&storage::Address>, | ||||||
|     connector: String, |     connector: String, | ||||||
|  |     config: &settings::ConnectorFilters, | ||||||
| ) -> errors::CustomResult<(), errors::ApiErrorResponse> { | ) -> errors::CustomResult<(), errors::ApiErrorResponse> { | ||||||
|     for payment_method in payment_methods.into_iter() { |     for payment_method in payment_methods.into_iter() { | ||||||
|         let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method); |         let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method); | ||||||
| @ -627,6 +634,16 @@ async fn filter_payment_methods( | |||||||
|                         true |                         true | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|  |                     let filter5 = filter_pm_based_on_config( | ||||||
|  |                         config, | ||||||
|  |                         &connector, | ||||||
|  |                         &payment_method_object.payment_method_type, | ||||||
|  |                         address.and_then(|inner| inner.country.clone()), | ||||||
|  |                         payment_attempt | ||||||
|  |                             .and_then(|value| value.currency) | ||||||
|  |                             .map(|value| value.foreign_into()), | ||||||
|  |                     ); | ||||||
|  |  | ||||||
|                     let connector = connector.clone(); |                     let connector = connector.clone(); | ||||||
|  |  | ||||||
|                     let response_pm_type = ResponsePaymentMethodIntermediate::new( |                     let response_pm_type = ResponsePaymentMethodIntermediate::new( | ||||||
| @ -635,7 +652,7 @@ async fn filter_payment_methods( | |||||||
|                         payment_method, |                         payment_method, | ||||||
|                     ); |                     ); | ||||||
|  |  | ||||||
|                     if filter && filter2 && filter3 && filter4 { |                     if filter && filter2 && filter3 && filter4 && filter5 { | ||||||
|                         resp.push(response_pm_type); |                         resp.push(response_pm_type); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @ -645,6 +662,33 @@ async fn filter_payment_methods( | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn filter_pm_based_on_config<'a>( | ||||||
|  |     config: &'a crate::configs::settings::ConnectorFilters, | ||||||
|  |     connector: &'a str, | ||||||
|  |     payment_method_type: &'a api_enums::PaymentMethodType, | ||||||
|  |     country: Option<String>, | ||||||
|  |     currency: Option<api_enums::Currency>, | ||||||
|  | ) -> bool { | ||||||
|  |     config | ||||||
|  |         .0 | ||||||
|  |         .get(connector) | ||||||
|  |         .and_then(|inner| inner.0.get(payment_method_type)) | ||||||
|  |         .map(|value| { | ||||||
|  |             let condition1 = value | ||||||
|  |                 .country | ||||||
|  |                 .as_ref() | ||||||
|  |                 .zip(country) | ||||||
|  |                 .map(|(lhs, rhs)| lhs.contains(&rhs)); | ||||||
|  |             let condition2 = value | ||||||
|  |                 .currency | ||||||
|  |                 .as_ref() | ||||||
|  |                 .zip(currency) | ||||||
|  |                 .map(|(lhs, rhs)| lhs.contains(&rhs)); | ||||||
|  |             condition1.unwrap_or(true) && condition2.unwrap_or(true) | ||||||
|  |         }) | ||||||
|  |         .unwrap_or(true) | ||||||
|  | } | ||||||
|  |  | ||||||
| fn filter_pm_card_network_based( | fn filter_pm_card_network_based( | ||||||
|     pm_card_networks: Option<&Vec<api_enums::CardNetwork>>, |     pm_card_networks: Option<&Vec<api_enums::CardNetwork>>, | ||||||
|     request_card_networks: Option<&Vec<api_enums::CardNetwork>>, |     request_card_networks: Option<&Vec<api_enums::CardNetwork>>, | ||||||
|  | |||||||
| @ -82,9 +82,7 @@ pub async fn list_payment_method_api( | |||||||
|         state.get_ref(), |         state.get_ref(), | ||||||
|         &req, |         &req, | ||||||
|         payload, |         payload, | ||||||
|         |state, merchant_account, req| { |         cards::list_payment_methods, | ||||||
|             cards::list_payment_methods(&*state.store, merchant_account, req) |  | ||||||
|         }, |  | ||||||
|         &*auth, |         &*auth, | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Nishant Joshi
					Nishant Joshi