diff --git a/Cargo.lock b/Cargo.lock index abc265ad6926..e3cc2e323879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3944,6 +3944,7 @@ dependencies = [ "masking", "serde", "serde_json", + "strum 0.26.2", "thiserror", ] diff --git a/crates/euclid/src/dssa/graph.rs b/crates/euclid/src/dssa/graph.rs index 0ffafe4d48b5..ce61fa57bf57 100644 --- a/crates/euclid/src/dssa/graph.rs +++ b/crates/euclid/src/dssa/graph.rs @@ -12,6 +12,7 @@ use crate::{ pub mod euclid_graph_prelude { pub use hyperswitch_constraint_graph as cgraph; pub use rustc_hash::{FxHashMap, FxHashSet}; + pub use strum::EnumIter; pub use crate::{ dssa::graph::*, @@ -71,6 +72,7 @@ impl AnalysisError { } } +#[derive(Debug)] pub struct AnalysisContext { keywise_values: FxHashMap>, } diff --git a/crates/euclid/src/enums.rs b/crates/euclid/src/enums.rs index f473c5980c7c..1aba6338d9d2 100644 --- a/crates/euclid/src/enums.rs +++ b/crates/euclid/src/enums.rs @@ -1,5 +1,5 @@ pub use common_enums::{ - AuthenticationType, CaptureMethod, CardNetwork, Country, Currency, + AuthenticationType, CaptureMethod, CardNetwork, Country, CountryAlpha2, Currency, FutureUsage as SetupFutureUsage, PaymentMethod, PaymentMethodType, RoutableConnectors, }; use strum::VariantNames; diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 941fc9d74656..c5f864bf7701 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -3,8 +3,8 @@ use strum::VariantNames; use crate::enums::collect_variants; pub use crate::enums::{ AuthenticationType, CaptureMethod, CardNetwork, Country, Country as BusinessCountry, - Country as BillingCountry, Currency as PaymentCurrency, MandateAcceptanceType, MandateType, - PaymentMethod, PaymentType, RoutableConnectors, SetupFutureUsage, + Country as BillingCountry, CountryAlpha2, Currency as PaymentCurrency, MandateAcceptanceType, + MandateType, PaymentMethod, PaymentType, RoutableConnectors, SetupFutureUsage, }; #[cfg(feature = "payouts")] pub use crate::enums::{PayoutBankTransferType, PayoutType, PayoutWalletType}; diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index 3668130608e4..df78786a5796 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -91,8 +91,12 @@ pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult { .collect::>() .map_err(|_| "invalid connector name received") .err_to_js()?; - - let mca_graph = kgraph_utils::mca::make_mca_graph(mcas).err_to_js()?; + let pm_filter = kgraph_utils::types::PaymentMethodFilters(HashMap::new()); + let config = kgraph_utils::types::CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: Some(pm_filter), + }; + let mca_graph = kgraph_utils::mca::make_mca_graph(mcas, &config).err_to_js()?; let analysis_graph = hyperswitch_constraint_graph::ConstraintGraph::combine(&mca_graph, &truth::ANALYSIS_GRAPH) .err_to_js()?; diff --git a/crates/hyperswitch_constraint_graph/src/builder.rs b/crates/hyperswitch_constraint_graph/src/builder.rs index c1343eff8850..fdd87eac51c6 100644 --- a/crates/hyperswitch_constraint_graph/src/builder.rs +++ b/crates/hyperswitch_constraint_graph/src/builder.rs @@ -28,7 +28,7 @@ impl From for DomainIdOrIdentifier<'_> { Self::DomainId(value) } } - +#[derive(Debug)] pub struct ConstraintGraphBuilder<'a, V: ValueNode> { domain: DenseMap>, nodes: DenseMap>, diff --git a/crates/hyperswitch_constraint_graph/src/graph.rs b/crates/hyperswitch_constraint_graph/src/graph.rs index d0a98e19520f..4a93419df575 100644 --- a/crates/hyperswitch_constraint_graph/src/graph.rs +++ b/crates/hyperswitch_constraint_graph/src/graph.rs @@ -13,6 +13,7 @@ use crate::{ }, }; +#[derive(Debug)] struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext> { ctx: &'a C, node: &'a Node, @@ -24,6 +25,7 @@ struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext> { domains: Option<&'a [DomainId]>, } +#[derive(Debug)] pub struct ConstraintGraph<'a, V: ValueNode> { pub domain: DenseMap>, pub domain_identifier_map: FxHashMap, DomainId>, @@ -139,6 +141,7 @@ where ctx, domains, }; + match &node.node_type { NodeType::AllAggregator => self.validate_all_aggregator(check_node_context), @@ -206,6 +209,7 @@ where } else { vald.memo .insert((vald.node_id, vald.relation, vald.strength), Ok(())); + Ok(()) } } diff --git a/crates/kgraph_utils/Cargo.toml b/crates/kgraph_utils/Cargo.toml index 86de6002c323..d068ee896927 100644 --- a/crates/kgraph_utils/Cargo.toml +++ b/crates/kgraph_utils/Cargo.toml @@ -21,6 +21,7 @@ masking = { version = "0.1.0", path = "../masking/" } serde = "1.0.197" serde_json = "1.0.115" thiserror = "1.0.58" +strum = { version = "0.26", features = ["derive"] } [dev-dependencies] criterion = "0.5" diff --git a/crates/kgraph_utils/benches/evaluation.rs b/crates/kgraph_utils/benches/evaluation.rs index 9921ee7af352..4cc526f973fd 100644 --- a/crates/kgraph_utils/benches/evaluation.rs +++ b/crates/kgraph_utils/benches/evaluation.rs @@ -1,6 +1,6 @@ #![allow(unused, clippy::expect_used)] -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use api_models::{ admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes, @@ -13,7 +13,7 @@ use euclid::{ types::{NumValue, NumValueRefinement}, }; use hyperswitch_constraint_graph::{CycleCheck, Memoization}; -use kgraph_utils::{error::KgraphError, transformers::IntoDirValue}; +use kgraph_utils::{error::KgraphError, transformers::IntoDirValue, types::CountryCurrencyFilter}; fn build_test_data<'a>( total_enabled: usize, @@ -71,8 +71,12 @@ fn build_test_data<'a>( pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, }; - - kgraph_utils::mca::make_mca_graph(vec![stripe_account]).expect("Failed graph construction") + let config = CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: None, + }; + kgraph_utils::mca::make_mca_graph(vec![stripe_account], &config) + .expect("Failed graph construction") } fn evaluation(c: &mut Criterion) { diff --git a/crates/kgraph_utils/src/lib.rs b/crates/kgraph_utils/src/lib.rs index eb8eef6dedb5..20c2abf0533f 100644 --- a/crates/kgraph_utils/src/lib.rs +++ b/crates/kgraph_utils/src/lib.rs @@ -1,3 +1,4 @@ pub mod error; pub mod mca; pub mod transformers; +pub mod types; diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 14a88dd1c6e6..ed96cd9b5450 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -4,15 +4,138 @@ use api_models::{ admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes, }; use euclid::{ + dirval, frontend::{ast, dir}, types::{NumValue, NumValueRefinement}, }; use hyperswitch_constraint_graph as cgraph; +use strum::IntoEnumIterator; -use crate::{error::KgraphError, transformers::IntoDirValue}; +use crate::{error::KgraphError, transformers::IntoDirValue, types as kgraph_types}; pub const DOMAIN_IDENTIFIER: &str = "payment_methods_enabled_for_merchantconnectoraccount"; +fn get_dir_value_payment_method( + from: api_enums::PaymentMethodType, +) -> Result { + match from { + api_enums::PaymentMethodType::Credit => Ok(dirval!(CardType = Credit)), + api_enums::PaymentMethodType::Debit => Ok(dirval!(CardType = Debit)), + api_enums::PaymentMethodType::Giropay => Ok(dirval!(BankRedirectType = Giropay)), + api_enums::PaymentMethodType::Ideal => Ok(dirval!(BankRedirectType = Ideal)), + api_enums::PaymentMethodType::Sofort => Ok(dirval!(BankRedirectType = Sofort)), + api_enums::PaymentMethodType::Eps => Ok(dirval!(BankRedirectType = Eps)), + api_enums::PaymentMethodType::Klarna => Ok(dirval!(PayLaterType = Klarna)), + api_enums::PaymentMethodType::Affirm => Ok(dirval!(PayLaterType = Affirm)), + api_enums::PaymentMethodType::AfterpayClearpay => { + Ok(dirval!(PayLaterType = AfterpayClearpay)) + } + api_enums::PaymentMethodType::GooglePay => Ok(dirval!(WalletType = GooglePay)), + api_enums::PaymentMethodType::ApplePay => Ok(dirval!(WalletType = ApplePay)), + api_enums::PaymentMethodType::Paypal => Ok(dirval!(WalletType = Paypal)), + api_enums::PaymentMethodType::CryptoCurrency => Ok(dirval!(CryptoType = CryptoCurrency)), + api_enums::PaymentMethodType::Ach => Ok(dirval!(BankDebitType = Ach)), + + api_enums::PaymentMethodType::Bacs => Ok(dirval!(BankDebitType = Bacs)), + + api_enums::PaymentMethodType::Becs => Ok(dirval!(BankDebitType = Becs)), + api_enums::PaymentMethodType::Sepa => Ok(dirval!(BankDebitType = Sepa)), + + api_enums::PaymentMethodType::AliPay => Ok(dirval!(WalletType = AliPay)), + api_enums::PaymentMethodType::AliPayHk => Ok(dirval!(WalletType = AliPayHk)), + api_enums::PaymentMethodType::BancontactCard => { + Ok(dirval!(BankRedirectType = BancontactCard)) + } + api_enums::PaymentMethodType::Blik => Ok(dirval!(BankRedirectType = Blik)), + api_enums::PaymentMethodType::MbWay => Ok(dirval!(WalletType = MbWay)), + api_enums::PaymentMethodType::MobilePay => Ok(dirval!(WalletType = MobilePay)), + api_enums::PaymentMethodType::Cashapp => Ok(dirval!(WalletType = Cashapp)), + api_enums::PaymentMethodType::Multibanco => Ok(dirval!(BankTransferType = Multibanco)), + api_enums::PaymentMethodType::Pix => Ok(dirval!(BankTransferType = Pix)), + api_enums::PaymentMethodType::Pse => Ok(dirval!(BankTransferType = Pse)), + api_enums::PaymentMethodType::Interac => Ok(dirval!(BankRedirectType = Interac)), + api_enums::PaymentMethodType::OnlineBankingCzechRepublic => { + Ok(dirval!(BankRedirectType = OnlineBankingCzechRepublic)) + } + api_enums::PaymentMethodType::OnlineBankingFinland => { + Ok(dirval!(BankRedirectType = OnlineBankingFinland)) + } + api_enums::PaymentMethodType::OnlineBankingPoland => { + Ok(dirval!(BankRedirectType = OnlineBankingPoland)) + } + api_enums::PaymentMethodType::OnlineBankingSlovakia => { + Ok(dirval!(BankRedirectType = OnlineBankingSlovakia)) + } + api_enums::PaymentMethodType::Swish => Ok(dirval!(WalletType = Swish)), + api_enums::PaymentMethodType::Trustly => Ok(dirval!(BankRedirectType = Trustly)), + api_enums::PaymentMethodType::Bizum => Ok(dirval!(BankRedirectType = Bizum)), + + api_enums::PaymentMethodType::PayBright => Ok(dirval!(PayLaterType = PayBright)), + api_enums::PaymentMethodType::Walley => Ok(dirval!(PayLaterType = Walley)), + api_enums::PaymentMethodType::Przelewy24 => Ok(dirval!(BankRedirectType = Przelewy24)), + api_enums::PaymentMethodType::WeChatPay => Ok(dirval!(WalletType = WeChatPay)), + + api_enums::PaymentMethodType::ClassicReward => Ok(dirval!(RewardType = ClassicReward)), + api_enums::PaymentMethodType::Evoucher => Ok(dirval!(RewardType = Evoucher)), + api_enums::PaymentMethodType::UpiCollect => Ok(dirval!(UpiType = UpiCollect)), + api_enums::PaymentMethodType::SamsungPay => Ok(dirval!(WalletType = SamsungPay)), + api_enums::PaymentMethodType::GoPay => Ok(dirval!(WalletType = GoPay)), + api_enums::PaymentMethodType::KakaoPay => Ok(dirval!(WalletType = KakaoPay)), + api_enums::PaymentMethodType::Twint => Ok(dirval!(WalletType = Twint)), + api_enums::PaymentMethodType::Gcash => Ok(dirval!(WalletType = Gcash)), + api_enums::PaymentMethodType::Vipps => Ok(dirval!(WalletType = Vipps)), + api_enums::PaymentMethodType::Momo => Ok(dirval!(WalletType = Momo)), + api_enums::PaymentMethodType::Alma => Ok(dirval!(PayLaterType = Alma)), + api_enums::PaymentMethodType::Dana => Ok(dirval!(WalletType = Dana)), + api_enums::PaymentMethodType::OnlineBankingFpx => { + Ok(dirval!(BankRedirectType = OnlineBankingFpx)) + } + api_enums::PaymentMethodType::OnlineBankingThailand => { + Ok(dirval!(BankRedirectType = OnlineBankingThailand)) + } + api_enums::PaymentMethodType::TouchNGo => Ok(dirval!(WalletType = TouchNGo)), + api_enums::PaymentMethodType::Atome => Ok(dirval!(PayLaterType = Atome)), + api_enums::PaymentMethodType::Boleto => Ok(dirval!(VoucherType = Boleto)), + api_enums::PaymentMethodType::Efecty => Ok(dirval!(VoucherType = Efecty)), + api_enums::PaymentMethodType::PagoEfectivo => Ok(dirval!(VoucherType = PagoEfectivo)), + api_enums::PaymentMethodType::RedCompra => Ok(dirval!(VoucherType = RedCompra)), + api_enums::PaymentMethodType::RedPagos => Ok(dirval!(VoucherType = RedPagos)), + api_enums::PaymentMethodType::Alfamart => Ok(dirval!(VoucherType = Alfamart)), + api_enums::PaymentMethodType::BcaBankTransfer => { + Ok(dirval!(BankTransferType = BcaBankTransfer)) + } + api_enums::PaymentMethodType::BniVa => Ok(dirval!(BankTransferType = BniVa)), + api_enums::PaymentMethodType::BriVa => Ok(dirval!(BankTransferType = BriVa)), + api_enums::PaymentMethodType::CimbVa => Ok(dirval!(BankTransferType = CimbVa)), + api_enums::PaymentMethodType::DanamonVa => Ok(dirval!(BankTransferType = DanamonVa)), + api_enums::PaymentMethodType::Indomaret => Ok(dirval!(VoucherType = Indomaret)), + api_enums::PaymentMethodType::MandiriVa => Ok(dirval!(BankTransferType = MandiriVa)), + api_enums::PaymentMethodType::LocalBankTransfer => { + Ok(dirval!(BankTransferType = LocalBankTransfer)) + } + api_enums::PaymentMethodType::PermataBankTransfer => { + Ok(dirval!(BankTransferType = PermataBankTransfer)) + } + api_enums::PaymentMethodType::PaySafeCard => Ok(dirval!(GiftCardType = PaySafeCard)), + api_enums::PaymentMethodType::SevenEleven => Ok(dirval!(VoucherType = SevenEleven)), + api_enums::PaymentMethodType::Lawson => Ok(dirval!(VoucherType = Lawson)), + api_enums::PaymentMethodType::MiniStop => Ok(dirval!(VoucherType = MiniStop)), + api_enums::PaymentMethodType::FamilyMart => Ok(dirval!(VoucherType = FamilyMart)), + api_enums::PaymentMethodType::Seicomart => Ok(dirval!(VoucherType = Seicomart)), + api_enums::PaymentMethodType::PayEasy => Ok(dirval!(VoucherType = PayEasy)), + api_enums::PaymentMethodType::Givex => Ok(dirval!(GiftCardType = Givex)), + api_enums::PaymentMethodType::Benefit => Ok(dirval!(CardRedirectType = Benefit)), + api_enums::PaymentMethodType::Knet => Ok(dirval!(CardRedirectType = Knet)), + api_enums::PaymentMethodType::OpenBankingUk => { + Ok(dirval!(BankRedirectType = OpenBankingUk)) + } + api_enums::PaymentMethodType::MomoAtm => Ok(dirval!(CardRedirectType = MomoAtm)), + api_enums::PaymentMethodType::Oxxo => Ok(dirval!(VoucherType = Oxxo)), + api_enums::PaymentMethodType::CardRedirect => Ok(dirval!(CardRedirectType = CardRedirect)), + api_enums::PaymentMethodType::Venmo => Ok(dirval!(WalletType = Venmo)), + } +} + fn compile_request_pm_types( builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, pm_types: RequestPaymentMethodTypes, @@ -258,16 +381,220 @@ fn compile_payment_method_enabled( Ok(agg_id) } +macro_rules! collect_global_variants { + ($parent_enum:ident) => { + &mut dir::enums::$parent_enum::iter() + .map(dir::DirValue::$parent_enum) + .collect::>() + }; +} +fn global_vec_pmt( + enabled_pmt: Vec, + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, +) -> Vec { + let mut global_vector: Vec = Vec::new(); + + global_vector.append(collect_global_variants!(PayLaterType)); + global_vector.append(collect_global_variants!(WalletType)); + global_vector.append(collect_global_variants!(BankRedirectType)); + global_vector.append(collect_global_variants!(BankDebitType)); + global_vector.append(collect_global_variants!(CryptoType)); + global_vector.append(collect_global_variants!(RewardType)); + global_vector.append(collect_global_variants!(UpiType)); + global_vector.append(collect_global_variants!(VoucherType)); + global_vector.append(collect_global_variants!(GiftCardType)); + global_vector.append(collect_global_variants!(BankTransferType)); + global_vector.append(collect_global_variants!(CardRedirectType)); + global_vector.push(dir::DirValue::PaymentMethod( + dir::enums::PaymentMethod::Card, + )); + let global_vector = global_vector + .into_iter() + .filter(|global_value| !enabled_pmt.contains(global_value)) + .collect::>(); + + global_vector + .into_iter() + .map(|dir_v| { + builder.make_value_node( + cgraph::NodeValue::Value(dir_v), + Some("Payment Method Type"), + None::<()>, + ) + }) + .collect::>() +} + +fn compile_graph_for_countries_and_currencies( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + config: &kgraph_types::CurrencyCountryFlowFilter, + payment_method_type_node: cgraph::NodeId, +) -> Result { + let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); + agg_nodes.push(( + payment_method_type_node, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + if let Some(country) = config.country.clone() { + let node_country = country + .into_iter() + .map(|country| dir::DirValue::BillingCountry(api_enums::Country::from_alpha2(country))) + .collect(); + let country_agg = builder + .make_in_aggregator(node_country, Some("Configs for Country"), None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + agg_nodes.push(( + country_agg, + cgraph::Relation::Positive, + cgraph::Strength::Weak, + )) + } + + if let Some(currency) = config.currency.clone() { + let node_currency = currency + .into_iter() + .map(IntoDirValue::into_dir_value) + .collect::, _>>()?; + let currency_agg = builder + .make_in_aggregator(node_currency, Some("Configs for Currency"), None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + agg_nodes.push(( + currency_agg, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )) + } + if let Some(capture_method) = config + .not_available_flows + .and_then(|naf| naf.capture_method) + { + let make_capture_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(capture_method)), + Some("Configs for CaptureMethod"), + None::<()>, + ); + agg_nodes.push(( + make_capture_node, + cgraph::Relation::Negative, + cgraph::Strength::Normal, + )) + } + + builder + .make_all_aggregator( + &agg_nodes, + Some("Country & Currency Configs With Payment Method Type"), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError) +} + +fn compile_config_graph( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + config: &kgraph_types::CountryCurrencyFilter, + connector: &api_enums::RoutableConnectors, +) -> Result { + let mut agg_node_id: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); + let mut pmt_enabled: Vec = Vec::new(); + if let Some(pmt) = config + .connector_configs + .get(connector) + .or(config.default_configs.as_ref()) + .map(|inner| inner.0.clone()) + { + for pm_filter_key in pmt { + match pm_filter_key { + (kgraph_types::PaymentMethodFilterKey::PaymentMethodType(pm), filter) => { + let dir_val_pm = get_dir_value_payment_method(pm)?; + + let pm_node = if pm == api_enums::PaymentMethodType::Credit + || pm == api_enums::PaymentMethodType::Debit + { + pmt_enabled + .push(dir::DirValue::PaymentMethod(api_enums::PaymentMethod::Card)); + builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentMethod( + dir::enums::PaymentMethod::Card, + )), + Some("PaymentMethod"), + None::<()>, + ) + } else { + pmt_enabled.push(dir_val_pm.clone()); + builder.make_value_node( + cgraph::NodeValue::Value(dir_val_pm), + Some("PaymentMethodType"), + None::<()>, + ) + }; + + let node_config = + compile_graph_for_countries_and_currencies(builder, &filter, pm_node)?; + + agg_node_id.push(( + node_config, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + } + (kgraph_types::PaymentMethodFilterKey::CardNetwork(cn), filter) => { + let dir_val_cn = cn.clone().into_dir_value()?; + pmt_enabled.push(dir_val_cn); + let cn_node = builder.make_value_node( + cn.clone().into_dir_value().map(Into::into)?, + Some("CardNetwork"), + None::<()>, + ); + let node_config = + compile_graph_for_countries_and_currencies(builder, &filter, cn_node)?; + + agg_node_id.push(( + node_config, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + } + } + } + } + let global_vector_pmt: Vec = global_vec_pmt(pmt_enabled, builder); + let any_agg_pmt: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = global_vector_pmt + .into_iter() + .map(|node| (node, cgraph::Relation::Positive, cgraph::Strength::Normal)) + .collect::>(); + let any_agg_node = builder + .make_any_aggregator( + &any_agg_pmt, + Some("Any Aggregator For Payment Method Types"), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + + agg_node_id.push(( + any_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + + builder + .make_any_aggregator(&agg_node_id, Some("Configs"), None::<()>, None) + .map_err(KgraphError::GraphConstructionError) +} + fn compile_merchant_connector_graph( builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, mca: admin_api::MerchantConnectorResponse, + config: &kgraph_types::CountryCurrencyFilter, ) -> Result<(), KgraphError> { let connector = common_enums::RoutableConnectors::from_str(&mca.connector_name) .map_err(|_| KgraphError::InvalidConnectorName(mca.connector_name.clone()))?; let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); - if let Some(pms_enabled) = mca.payment_methods_enabled { + if let Some(pms_enabled) = mca.payment_methods_enabled.clone() { for pm_enabled in pms_enabled { let maybe_pm_enabled_id = compile_payment_method_enabled(builder, pm_enabled)?; if let Some(pm_enabled_id) = maybe_pm_enabled_id { @@ -285,10 +612,33 @@ fn compile_merchant_connector_graph( .make_any_aggregator(&agg_nodes, Some(aggregator_info), None::<()>, None) .map_err(KgraphError::GraphConstructionError)?; + let config_info = "Config for respective PaymentMethodType for the connector"; + + let config_enabled_agg_id = compile_config_graph(builder, config, &connector)?; + + let domain_level_node_id = builder + .make_all_aggregator( + &[ + ( + config_enabled_agg_id, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + ), + ( + pms_enabled_agg_id, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + ), + ], + Some(config_info), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; let connector_dir_val = dir::DirValue::Connector(Box::new(ast::ConnectorChoice { connector, #[cfg(not(feature = "connector_choice_mca_id"))] - sub_label: mca.business_sub_label, + sub_label: mca.business_sub_label.clone(), })); let connector_info = "Connector"; @@ -297,7 +647,7 @@ fn compile_merchant_connector_graph( builder .make_edge( - pms_enabled_agg_id, + domain_level_node_id, connector_node_id, cgraph::Strength::Normal, cgraph::Relation::Positive, @@ -310,6 +660,7 @@ fn compile_merchant_connector_graph( pub fn make_mca_graph<'a>( accts: Vec, + config: &kgraph_types::CountryCurrencyFilter, ) -> Result, KgraphError> { let mut builder = cgraph::ConstraintGraphBuilder::new(); let _domain = builder.make_domain( @@ -317,7 +668,7 @@ pub fn make_mca_graph<'a>( "Payment methods enabled for MerchantConnectorAccount", ); for acct in accts { - compile_merchant_connector_graph(&mut builder, acct)?; + compile_merchant_connector_graph(&mut builder, acct, config)?; } Ok(builder.build()) @@ -327,6 +678,8 @@ pub fn make_mca_graph<'a>( mod tests { #![allow(clippy::expect_used)] + use std::collections::{HashMap, HashSet}; + use api_models::enums as api_enums; use euclid::{ dirval, @@ -335,6 +688,7 @@ mod tests { use hyperswitch_constraint_graph::{ConstraintGraph, CycleCheck, Memoization}; use super::*; + use crate::types as kgraph_types; fn build_test_data<'a>() -> ConstraintGraph<'a, dir::DirValue> { use api_models::{admin::*, payment_methods::*}; @@ -398,7 +752,36 @@ mod tests { status: api_enums::ConnectorStatus::Inactive, }; - make_mca_graph(vec![stripe_account]).expect("Failed graph construction") + let currency_country_flow_filter = kgraph_types::CurrencyCountryFlowFilter { + currency: Some(HashSet::from([api_enums::Currency::INR])), + country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), + not_available_flows: Some(kgraph_types::NotAvailableFlows { + capture_method: Some(api_enums::CaptureMethod::Manual), + }), + }; + + let config_map = kgraph_types::CountryCurrencyFilter { + connector_configs: HashMap::from([( + api_enums::RoutableConnectors::Stripe, + kgraph_types::PaymentMethodFilters(HashMap::from([ + ( + kgraph_types::PaymentMethodFilterKey::PaymentMethodType( + api_enums::PaymentMethodType::Credit, + ), + currency_country_flow_filter.clone(), + ), + ( + kgraph_types::PaymentMethodFilterKey::PaymentMethodType( + api_enums::PaymentMethodType::Debit, + ), + currency_country_flow_filter, + ), + ])), + )]), + default_configs: None, + }; + + make_mca_graph(vec![stripe_account], &config_map).expect("Failed graph construction") } #[test] @@ -412,8 +795,8 @@ mod tests { dirval!(PaymentMethod = Card), dirval!(CardType = Credit), dirval!(CardNetwork = Visa), - dirval!(PaymentCurrency = USD), - dirval!(PaymentAmount = 100), + dirval!(PaymentCurrency = INR), + dirval!(PaymentAmount = 101), ]), &mut Memoization::new(), &mut CycleCheck::new(), @@ -711,8 +1094,11 @@ mod tests { let data: Vec = serde_json::from_value(value).expect("data"); - - let graph = make_mca_graph(data).expect("graph"); + let config = kgraph_types::CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: None, + }; + let graph = make_mca_graph(data, &config).expect("graph"); let context = AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentAmount = 212), diff --git a/crates/kgraph_utils/src/types.rs b/crates/kgraph_utils/src/types.rs new file mode 100644 index 000000000000..26f27896e0a5 --- /dev/null +++ b/crates/kgraph_utils/src/types.rs @@ -0,0 +1,35 @@ +use std::collections::{HashMap, HashSet}; + +use api_models::enums as api_enums; +use serde::Deserialize; +#[derive(Debug, Deserialize, Clone, Default)] + +pub struct CountryCurrencyFilter { + pub connector_configs: HashMap, + pub default_configs: Option, +} + +#[derive(Debug, Deserialize, Clone, Default)] +#[serde(transparent)] +pub struct PaymentMethodFilters(pub HashMap); + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum PaymentMethodFilterKey { + PaymentMethodType(api_enums::PaymentMethodType), + CardNetwork(api_enums::CardNetwork), +} + +#[derive(Debug, Deserialize, Clone, Default)] +#[serde(default)] +pub struct CurrencyCountryFlowFilter { + pub currency: Option>, + pub country: Option>, + pub not_available_flows: Option, +} + +#[derive(Debug, Deserialize, Copy, Clone, Default)] +#[serde(default)] +pub struct NotAvailableFlows { + pub capture_method: Option, +} diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 6967c977753d..4218fe3462c8 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1,8 +1,9 @@ mod transformers; use std::{ - collections::hash_map, + collections::{hash_map, HashMap}, hash::{Hash, Hasher}, + str::FromStr, sync::Arc, }; @@ -24,6 +25,7 @@ use euclid::{ use kgraph_utils::{ mca as mca_graph, transformers::{IntoContext, IntoDirValue}, + types::CountryCurrencyFilter, }; use masking::PeekInterface; use rand::{ @@ -43,8 +45,9 @@ use crate::{ }, logger, types::{ - api, api::routing as routing_types, domain, storage as oss_storage, - transformers::ForeignInto, + api::{self, routing as routing_types}, + domain, storage as oss_storage, + transformers::{ForeignFrom, ForeignInto}, }, utils::{OptionExt, ValueExt}, AppState, @@ -104,7 +107,6 @@ impl Default for MerchantAccountRoutingAlgorithm { pub fn make_dsl_input_for_payouts( payout_data: &payouts::PayoutData, ) -> RoutingResult { - use crate::types::transformers::ForeignFrom; let mandate = dsl_inputs::MandateData { mandate_acceptance_type: None, mandate_type: None, @@ -645,8 +647,32 @@ pub async fn refresh_kgraph_cache( .map(admin_api::MerchantConnectorResponse::try_from) .collect::, _>>() .change_context(errors::RoutingError::KgraphCacheRefreshFailed)?; - - let kgraph = mca_graph::make_mca_graph(api_mcas) + let connector_configs = state + .conf + .pm_filters + .0 + .clone() + .into_iter() + .filter(|(key, _)| key != "default") + .map(|(key, value)| { + let key = api_enums::RoutableConnectors::from_str(&key) + .map_err(|_| errors::RoutingError::InvalidConnectorName(key))?; + + Ok((key, value.foreign_into())) + }) + .collect::, errors::RoutingError>>()?; + let default_configs = state + .conf + .pm_filters + .0 + .get("default") + .cloned() + .map(ForeignFrom::foreign_from); + let config_pm_filters = CountryCurrencyFilter { + connector_configs, + default_configs, + }; + let kgraph = mca_graph::make_mca_graph(api_mcas, &config_pm_filters) .change_context(errors::RoutingError::KgraphCacheRefreshFailed) .attach_printable("when construction kgraph")?; diff --git a/crates/router/src/core/payments/routing/transformers.rs b/crates/router/src/core/payments/routing/transformers.rs index b273f18f3fd8..ae779a6551f0 100644 --- a/crates/router/src/core/payments/routing/transformers.rs +++ b/crates/router/src/core/payments/routing/transformers.rs @@ -1,8 +1,14 @@ +use std::collections::HashMap; + use api_models::{self, routing as routing_types}; use diesel_models::enums as storage_enums; use euclid::{enums as dsl_enums, frontend::ast as dsl_ast}; +use kgraph_utils::types; -use crate::types::transformers::ForeignFrom; +use crate::{ + configs::settings, + types::transformers::{ForeignFrom, ForeignInto}, +}; impl ForeignFrom for dsl_ast::ConnectorChoice { fn foreign_from(from: routing_types::RoutableConnectorChoice) -> Self { @@ -52,3 +58,40 @@ impl ForeignFrom for dsl_enums::MandateType { } } } + +impl ForeignFrom for types::PaymentMethodFilterKey { + fn foreign_from(from: settings::PaymentMethodFilterKey) -> Self { + match from { + settings::PaymentMethodFilterKey::PaymentMethodType(pmt) => { + Self::PaymentMethodType(pmt) + } + settings::PaymentMethodFilterKey::CardNetwork(cn) => Self::CardNetwork(cn), + } + } +} +impl ForeignFrom for types::CurrencyCountryFlowFilter { + fn foreign_from(from: settings::CurrencyCountryFlowFilter) -> Self { + Self { + currency: from.currency, + country: from.country, + not_available_flows: from.not_available_flows.map(ForeignInto::foreign_into), + } + } +} +impl ForeignFrom for types::NotAvailableFlows { + fn foreign_from(from: settings::NotAvailableFlows) -> Self { + Self { + capture_method: from.capture_method, + } + } +} +impl ForeignFrom for types::PaymentMethodFilters { + fn foreign_from(from: settings::PaymentMethodFilters) -> Self { + let iter_map = from + .0 + .into_iter() + .map(|(key, val)| (key.foreign_into(), val.foreign_into())) + .collect::>(); + Self(iter_map) + } +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json index 273ed5ee3c68..d108e1198a7d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json @@ -41,7 +41,7 @@ "zip": "94122", "first_name": "John", "last_name": "Doe", - "country": "SE" + "country": "ES" }, "email": "narayan@example.com" }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json index 21e71ad037a9..048893f35111 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json @@ -42,7 +42,7 @@ "city": "San Fransico", "state": "California", "zip": "94122", - "country": "DE" + "country": "NL" } }, "browser_info": { diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json index 61add73d4116..c0e2a2a3d5ea 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json @@ -48,7 +48,7 @@ }, "bank_name": "hypo_oberosterreich_salzburg_steiermark", "preferred_language": "en", - "country": "DE" + "country": "AT" } } }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json index 21e71ad037a9..026af8449ce8 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json @@ -42,7 +42,7 @@ "city": "San Fransico", "state": "California", "zip": "94122", - "country": "DE" + "country": "AT" } }, "browser_info": { diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index 47f732f21537..397864885cb6 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -13677,7 +13677,7 @@ "language": "json" } }, - "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"SE\"},\"email\":\"narayan@example.com\"},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"ES\"},\"email\":\"narayan@example.com\"},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -14537,7 +14537,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"NL\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -15397,7 +15397,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"AT\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -15576,7 +15576,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"AT\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm",