mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +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
|
||||
nin = "nin" # National identification number, a field used by PayU connector
|
||||
substituters = "substituters" # Present in `flake.nix`
|
||||
FO = "FO" # Faroe Islands (the) country code
|
||||
|
||||
[files]
|
||||
extend-exclude = [
|
||||
|
||||
@ -140,3 +140,40 @@ stream = "SCHEDULER_STREAM"
|
||||
[scheduler.consumer]
|
||||
disabled = false
|
||||
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
|
||||
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)
|
||||
|
||||
# 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 config::{Environment, File};
|
||||
use redis_interface::RedisSettings;
|
||||
pub use router_env::config::{Log, LogConsole, LogFile, LogTelemetry};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::{
|
||||
core::errors::{ApplicationError, ApplicationResult},
|
||||
@ -51,6 +55,64 @@ pub struct Settings {
|
||||
pub drainer: DrainerSettings,
|
||||
pub jwekey: Jwekey,
|
||||
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)]
|
||||
|
||||
@ -14,6 +14,7 @@ use error_stack::{report, ResultExt};
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, StorageErrorExt},
|
||||
payment_methods::{transformers as payment_methods, vault},
|
||||
@ -348,10 +349,13 @@ pub async fn delete_card<'a>(
|
||||
}
|
||||
|
||||
pub async fn list_payment_methods(
|
||||
db: &dyn db::StorageInterface,
|
||||
state: &routes::AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
mut req: api::ListPaymentMethodRequest,
|
||||
) -> errors::RouterResponse<api::ListPaymentMethodResponse> {
|
||||
let db = &*state.store;
|
||||
let pm_config_mapping = &state.conf.pm_filters;
|
||||
|
||||
let payment_intent = helpers::verify_client_secret(
|
||||
db,
|
||||
merchant_account.storage_scheme,
|
||||
@ -410,6 +414,7 @@ pub async fn list_payment_methods(
|
||||
payment_attempt.as_ref(),
|
||||
address.as_ref(),
|
||||
mca.connector_name,
|
||||
pm_config_mapping,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@ -567,6 +572,7 @@ pub async fn list_payment_methods(
|
||||
)))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn filter_payment_methods(
|
||||
payment_methods: Vec<serde_json::Value>,
|
||||
req: &mut api::ListPaymentMethodRequest,
|
||||
@ -575,6 +581,7 @@ async fn filter_payment_methods(
|
||||
payment_attempt: Option<&storage::PaymentAttempt>,
|
||||
address: Option<&storage::Address>,
|
||||
connector: String,
|
||||
config: &settings::ConnectorFilters,
|
||||
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
|
||||
for payment_method in payment_methods.into_iter() {
|
||||
let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method);
|
||||
@ -627,6 +634,16 @@ async fn filter_payment_methods(
|
||||
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 response_pm_type = ResponsePaymentMethodIntermediate::new(
|
||||
@ -635,7 +652,7 @@ async fn filter_payment_methods(
|
||||
payment_method,
|
||||
);
|
||||
|
||||
if filter && filter2 && filter3 && filter4 {
|
||||
if filter && filter2 && filter3 && filter4 && filter5 {
|
||||
resp.push(response_pm_type);
|
||||
}
|
||||
}
|
||||
@ -645,6 +662,33 @@ async fn filter_payment_methods(
|
||||
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(
|
||||
pm_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(),
|
||||
&req,
|
||||
payload,
|
||||
|state, merchant_account, req| {
|
||||
cards::list_payment_methods(&*state.store, merchant_account, req)
|
||||
},
|
||||
cards::list_payment_methods,
|
||||
&*auth,
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user