mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 12:15:40 +08:00
refactor: Pass country and currency as json format in MCA (#523)
This commit is contained in:
@ -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<Vec<String>>,
|
||||
/// List of currencies accepted or has the processing capabilities of the processor
|
||||
#[schema(value_type = Option<Vec<Currency>>,example = json!(["USD","EUR","AED"]))]
|
||||
pub accepted_currencies: Option<Vec<api_enums::Currency>>,
|
||||
#[schema(example = json!(
|
||||
{
|
||||
"enable_all":false,
|
||||
"disable_only": ["INR", "CAD", "AED","JPY"],
|
||||
"enable_only": ["EUR","USD"]
|
||||
}
|
||||
))]
|
||||
pub accepted_currencies: Option<AcceptedCurrencies>,
|
||||
/// List of Countries accepted or has the processing capabilities of the processor
|
||||
#[schema(example = json!(["US","IN"]))]
|
||||
pub accepted_countries: Option<Vec<String>>,
|
||||
#[schema(example = json!(
|
||||
{
|
||||
"enable_all":false,
|
||||
"disable_only": ["FR", "DE","IN"],
|
||||
"enable_only": ["UK","AU"]
|
||||
}
|
||||
))]
|
||||
pub accepted_countries: Option<AcceptedCountries>,
|
||||
/// 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<i32>,
|
||||
@ -328,6 +342,30 @@ pub struct PaymentMethods {
|
||||
pub payment_experience: Option<Vec<api_enums::PaymentExperience>>,
|
||||
}
|
||||
|
||||
/// 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<Vec<api_enums::Currency>>,
|
||||
/// List of enable currencies, provide in case only few of currencies are supported
|
||||
pub enable_only: Option<Vec<api_enums::Currency>>,
|
||||
}
|
||||
|
||||
/// 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<Vec<String>>,
|
||||
/// List of enable countries, provide in case only few of countries are supported
|
||||
pub enable_only: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct DeleteMcaResponse {
|
||||
/// The identifier for the Merchant Account
|
||||
|
||||
@ -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<Vec<String>>,
|
||||
|
||||
/// List of Countries accepted or has the processing capabilities of the processor
|
||||
#[schema(example = json!(["US", "UK", "IN"]))]
|
||||
pub accepted_countries: Option<Vec<String>>,
|
||||
#[schema(example = json!(
|
||||
{
|
||||
"enable_all":false,
|
||||
"disable_only": ["FR", "DE","IN"],
|
||||
"enable_only": ["UK","AU"]
|
||||
}
|
||||
))]
|
||||
pub accepted_countries: Option<admin::AcceptedCountries>,
|
||||
|
||||
/// List of currencies accepted or has the processing capabilities of the processor
|
||||
#[schema(value_type = Option<Vec<Currency>>,example = json!(["USD", "EUR"]))]
|
||||
pub accepted_currencies: Option<Vec<api_enums::Currency>>,
|
||||
#[schema(example = json!(
|
||||
{
|
||||
"enable_all":false,
|
||||
"disable_only": ["INR", "CAD", "AED","JPY"],
|
||||
"enable_only": ["EUR","USD"]
|
||||
}
|
||||
))]
|
||||
pub accepted_currencies: Option<admin::AcceptedCurrencies>,
|
||||
|
||||
/// 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)
|
||||
|
||||
@ -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<S: Into<String>>(
|
||||
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::<api::PaymentMethods>::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::<api::PaymentMethods>::encode_to_value(payment_method)
|
||||
})
|
||||
.collect::<Vec<serde_json::Value>>()
|
||||
|
||||
@ -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<admin::AcceptedCountries>,
|
||||
req_country_list: &Option<Vec<String>>,
|
||||
) -> (Option<admin::AcceptedCountries>, Option<Vec<String>>, 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<admin::AcceptedCurrencies>,
|
||||
req_currency_list: &Option<Vec<api_enums::Currency>>,
|
||||
) -> (
|
||||
Option<admin::AcceptedCurrencies>,
|
||||
Option<Vec<api_enums::Currency>>,
|
||||
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<T: Eq + std::hash::Hash + Clone>(
|
||||
left: &Option<Vec<T>>,
|
||||
right: &Option<Vec<T>>,
|
||||
) -> (Option<Vec<T>>, Option<Vec<T>>, bool) {
|
||||
) -> Option<Vec<T>> {
|
||||
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<T> = 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<T: Eq + std::hash::Hash + Clone>(
|
||||
left: &Option<Vec<T>>,
|
||||
right: &Option<Vec<T>>,
|
||||
) -> Option<Vec<T>> {
|
||||
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<bool, errors::ApiErrorResponse> {
|
||||
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())
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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<ListPaymentMethodRequest> =
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user