diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index e4434ad23e..e424af8086 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -269,14 +269,16 @@ pub struct PaymentConnectorCreate { "Discover", "Discover" ], - "accepted_currencies": [ - "AED", - "AED" - ], - "accepted_countries": [ - "in", - "us" - ], + "accepted_currencies": { + "enable_all":false, + "disable_only": ["INR", "CAD", "AED","JPY"], + "enable_only": ["EUR","USD"] + }, + "accepted_countries": { + "enable_all":false, + "disable_only": ["FR", "DE","IN"], + "enable_only": ["UK","AU"] + }, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, @@ -305,11 +307,23 @@ pub struct PaymentMethods { #[schema(example = json!(["MASTER","VISA","DINERS"]))] pub payment_schemes: Option>, /// List of currencies accepted or has the processing capabilities of the processor - #[schema(value_type = Option>,example = json!(["USD","EUR","AED"]))] - pub accepted_currencies: Option>, + #[schema(example = json!( + { + "enable_all":false, + "disable_only": ["INR", "CAD", "AED","JPY"], + "enable_only": ["EUR","USD"] + } + ))] + pub accepted_currencies: Option, /// List of Countries accepted or has the processing capabilities of the processor - #[schema(example = json!(["US","IN"]))] - pub accepted_countries: Option>, + #[schema(example = json!( + { + "enable_all":false, + "disable_only": ["FR", "DE","IN"], + "enable_only": ["UK","AU"] + } + ))] + pub accepted_countries: Option, /// Minimum amount supported by the processor. To be represented in the lowest denomination of the target currency (For example, for USD it should be in cents) #[schema(example = 1)] pub minimum_amount: Option, @@ -328,6 +342,30 @@ pub struct PaymentMethods { pub payment_experience: Option>, } +/// List of enabled and disabled currencies +#[derive(Eq, PartialEq, Hash, Debug, Clone, serde::Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct AcceptedCurrencies { + /// True in case all currencies are supported + pub enable_all: bool, + /// List of disabled currencies, provide in case only few of currencies are not supported + pub disable_only: Option>, + /// List of enable currencies, provide in case only few of currencies are supported + pub enable_only: Option>, +} + +/// List of enabled and disabled countries +#[derive(Eq, PartialEq, Hash, Debug, Clone, serde::Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct AcceptedCountries { + /// True in case all countries are supported + pub enable_all: bool, + /// List of disabled countries, provide in case only few of countries are not supported + pub disable_only: Option>, + /// List of enable countries, provide in case only few of countries are supported + pub enable_only: Option>, +} + #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct DeleteMcaResponse { /// The identifier for the Merchant Account diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index d768b52f7e..2967f736e4 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -4,7 +4,7 @@ use common_utils::pii; use serde::de; use utoipa::ToSchema; -use crate::enums as api_enums; +use crate::{admin, enums as api_enums}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] @@ -322,12 +322,24 @@ pub struct ListPaymentMethod { pub payment_schemes: Option>, /// List of Countries accepted or has the processing capabilities of the processor - #[schema(example = json!(["US", "UK", "IN"]))] - pub accepted_countries: Option>, + #[schema(example = json!( + { + "enable_all":false, + "disable_only": ["FR", "DE","IN"], + "enable_only": ["UK","AU"] + } + ))] + pub accepted_countries: Option, /// List of currencies accepted or has the processing capabilities of the processor - #[schema(value_type = Option>,example = json!(["USD", "EUR"]))] - pub accepted_currencies: Option>, + #[schema(example = json!( + { + "enable_all":false, + "disable_only": ["INR", "CAD", "AED","JPY"], + "enable_only": ["EUR","USD"] + } + ))] + pub accepted_currencies: Option, /// Minimum amount supported by the processor. To be represented in the lowest denomination of /// the target currency (For example, for USD it should be in cents) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 04b94a41f5..c1b59800f0 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1,4 +1,4 @@ -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, fp_utils::when}; use error_stack::{report, FutureExt, ResultExt}; use storage_models::{enums, merchant_account}; use uuid::Uuid; @@ -239,6 +239,38 @@ async fn validate_merchant_id>( error.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound) }) } + +fn validate_pm_enabled(pm: &api::PaymentMethods) -> RouterResult<()> { + if let Some(ac) = pm.accepted_countries.to_owned() { + when(ac.enable_all && ac.enable_only.is_some(), || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "In case all countries are enabled,provide the disable_only country" + .to_string(), + }) + })?; + when(!ac.enable_all && ac.disable_only.is_some(), || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "In case enable_all is false, provide the enable_only country".to_string(), + }) + })?; + }; + if let Some(ac) = pm.accepted_currencies.to_owned() { + when(ac.enable_all && ac.enable_only.is_some(), || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "In case all currencies are enabled, provide the disable_only currency" + .to_string(), + }) + })?; + when(!ac.enable_all && ac.disable_only.is_some(), || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "In case enable_all is false, provide the enable_only currency" + .to_string(), + }) + })?; + }; + Ok(()) +} + // Payment Connector API - Every merchant and connector can have an instance of (merchant <> connector) // with unique merchant_connector_id for Create Operation @@ -259,6 +291,7 @@ pub async fn create_payment_connector( let payment_methods_enabled = match req.payment_methods_enabled { Some(val) => { for pm in val.into_iter() { + validate_pm_enabled(&pm)?; let pm_value = utils::Encode::::encode_to_value(&pm) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( @@ -386,6 +419,9 @@ pub async fn update_payment_connector( pm_enabled .iter() .flat_map(|payment_method| { + validate_pm_enabled(payment_method) + .change_context(errors::ParsingError) + .attach_printable("Validation for accepted country and currency failed")?; utils::Encode::::encode_to_value(payment_method) }) .collect::>() diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index e004707a1d..f56d44552e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use api_models::{admin, enums as api_enums}; use common_utils::{consts, ext_traits::AsyncExt, generate_id}; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; @@ -433,17 +434,16 @@ async fn filter_payment_methods( payment_method_object.accepted_countries, req.accepted_countries, filter, - ) = filter_accepted_enum_based( + ) = filter_pm_country_based( &payment_method_object.accepted_countries, &req.accepted_countries, ); - let filter2; ( payment_method_object.accepted_currencies, req.accepted_currencies, filter2, - ) = filter_accepted_enum_based( + ) = filter_pm_currencies_based( &payment_method_object.accepted_currencies, &req.accepted_currencies, ); @@ -466,21 +466,97 @@ async fn filter_payment_methods( Ok(()) } +fn filter_pm_country_based( + accepted_countries: &Option, + req_country_list: &Option>, +) -> (Option, Option>, bool) { + match (accepted_countries, req_country_list) { + (None, None) => (None, None, true), + (None, Some(ref r)) => (None, Some(r.to_vec()), false), + (Some(l), None) => (Some(l.to_owned()), None, true), + (Some(l), Some(ref r)) => { + let enable_only = if l.enable_all { + filter_disabled_enum_based(&l.disable_only, &Some(r.to_owned())) + } else { + filter_accepted_enum_based(&l.enable_only, &Some(r.to_owned())) + }; + ( + Some(admin::AcceptedCountries { + enable_all: l.enable_all, + enable_only, + disable_only: None, + }), + Some(r.to_vec()), + true, + ) + } + } +} + +fn filter_pm_currencies_based( + accepted_currency: &Option, + req_currency_list: &Option>, +) -> ( + Option, + Option>, + bool, +) { + match (accepted_currency, req_currency_list) { + (None, None) => (None, None, true), + (None, Some(ref r)) => (None, Some(r.to_vec()), false), + (Some(l), None) => (Some(l.to_owned()), None, true), + (Some(l), Some(ref r)) => { + let enable_only = if l.enable_all { + filter_disabled_enum_based(&l.disable_only, &Some(r.to_owned())) + } else { + filter_accepted_enum_based(&l.enable_only, &Some(r.to_owned())) + }; + ( + Some(admin::AcceptedCurrencies { + enable_all: l.enable_all, + enable_only, + disable_only: None, + }), + Some(r.to_vec()), + true, + ) + } + } +} + fn filter_accepted_enum_based( left: &Option>, right: &Option>, -) -> (Option>, Option>, bool) { +) -> Option> { match (left, right) { (Some(ref l), Some(ref r)) => { let a: HashSet<&T> = HashSet::from_iter(l.iter()); let b: HashSet<&T> = HashSet::from_iter(r.iter()); let y: Vec = a.intersection(&b).map(|&i| i.to_owned()).collect(); - (Some(y), Some(r.to_vec()), true) + Some(y) } - (Some(ref l), None) => (Some(l.to_vec()), None, true), - (None, Some(ref r)) => (None, Some(r.to_vec()), false), - (None, None) => (None, None, true), + (Some(ref l), None) => Some(l.to_vec()), + (_, _) => None, + } +} + +fn filter_disabled_enum_based( + left: &Option>, + right: &Option>, +) -> Option> { + match (left, right) { + (Some(ref l), Some(ref r)) => { + let mut enabled = Vec::new(); + for element in r { + if !l.contains(element) { + enabled.push(element.to_owned()); + } + } + Some(enabled) + } + (None, Some(r)) => Some(r.to_vec()), + (_, _) => None, } } @@ -524,9 +600,17 @@ async fn filter_payment_country_based( ) -> errors::CustomResult { Ok(address.map_or(true, |address| { address.country.as_ref().map_or(true, |country| { - pm.accepted_countries - .clone() - .map_or(true, |ac| ac.contains(country)) + pm.accepted_countries.as_ref().map_or(true, |ac| { + if ac.enable_all { + ac.disable_only.as_ref().map_or(true, |disable_countries| { + disable_countries.contains(country) + }) + } else { + ac.enable_only + .as_ref() + .map_or(false, |enable_countries| enable_countries.contains(country)) + } + }) }) })) } @@ -536,9 +620,17 @@ fn filter_payment_currency_based( pm: &api::ListPaymentMethod, ) -> bool { payment_intent.currency.map_or(true, |currency| { - pm.accepted_currencies - .clone() - .map_or(true, |ac| ac.contains(¤cy.foreign_into())) + pm.accepted_currencies.as_ref().map_or(true, |ac| { + if ac.enable_all { + ac.disable_only.as_ref().map_or(true, |disable_currencies| { + disable_currencies.contains(¤cy.foreign_into()) + }) + } else { + ac.enable_only.as_ref().map_or(false, |enable_currencies| { + enable_currencies.contains(¤cy.foreign_into()) + }) + } + }) }) } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 54b50e6787..ea957422f4 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -268,14 +268,11 @@ mod tests { #[test] fn test_custom_list_deserialization() { - let dummy_data = "amount=120&recurring_enabled=true&installment_payment_enabled=true&accepted_countries=US&accepted_countries=IN"; + let dummy_data = "amount=120&recurring_enabled=true&installment_payment_enabled=true"; let de_query: web::Query = web::Query::from_query(dummy_data).unwrap(); let de_struct = de_query.into_inner(); - assert_eq!( - de_struct.accepted_countries, - Some(vec!["US".to_string(), "IN".to_string()]) - ) + assert_eq!(de_struct.installment_payment_enabled, Some(true)) } #[test]