use std::str::FromStr; use api_models::{ admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes, refunds::MinorUnit, }; use euclid::{ dirval, frontend::{ast, dir}, types::{NumValue, NumValueRefinement}, }; use hyperswitch_constraint_graph as cgraph; use strum::IntoEnumIterator; 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::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::LocalBankRedirect => { Ok(dirval!(BankRedirectType = LocalBankRedirect)) } 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)), api_enums::PaymentMethodType::UpiIntent => Ok(dirval!(UpiType = UpiIntent)), api_enums::PaymentMethodType::UpiCollect => Ok(dirval!(UpiType = UpiCollect)), api_enums::PaymentMethodType::Mifinity => Ok(dirval!(WalletType = Mifinity)), api_enums::PaymentMethodType::Fps => Ok(dirval!(RealTimePaymentType = Fps)), api_enums::PaymentMethodType::DuitNow => Ok(dirval!(RealTimePaymentType = DuitNow)), api_enums::PaymentMethodType::PromptPay => Ok(dirval!(RealTimePaymentType = PromptPay)), api_enums::PaymentMethodType::VietQr => Ok(dirval!(RealTimePaymentType = VietQr)), api_enums::PaymentMethodType::OpenBankingPIS => { Ok(dirval!(OpenBankingType = OpenBankingPIS)) } } } fn compile_request_pm_types( builder: &mut cgraph::ConstraintGraphBuilder, pm_types: RequestPaymentMethodTypes, pm: api_enums::PaymentMethod, ) -> Result { let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); let pmt_info = "PaymentMethodType"; let pmt_id = builder.make_value_node( (pm_types.payment_method_type, pm) .into_dir_value() .map(Into::into)?, Some(pmt_info), None::<()>, ); agg_nodes.push(( pmt_id, cgraph::Relation::Positive, match pm_types.payment_method_type { api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { cgraph::Strength::Weak } _ => cgraph::Strength::Strong, }, )); if let Some(card_networks) = pm_types.card_networks { if !card_networks.is_empty() { let dir_vals: Vec = card_networks .into_iter() .map(IntoDirValue::into_dir_value) .collect::>()?; let card_network_info = "Card Networks"; let card_network_id = builder .make_in_aggregator(dir_vals, Some(card_network_info), None::<()>) .map_err(KgraphError::GraphConstructionError)?; agg_nodes.push(( card_network_id, cgraph::Relation::Positive, cgraph::Strength::Weak, )); } } let currencies_data = pm_types .accepted_currencies .and_then(|accepted_currencies| match accepted_currencies { admin_api::AcceptedCurrencies::EnableOnly(curr) if !curr.is_empty() => Some(( curr.into_iter() .map(IntoDirValue::into_dir_value) .collect::>() .ok()?, cgraph::Relation::Positive, )), admin_api::AcceptedCurrencies::DisableOnly(curr) if !curr.is_empty() => Some(( curr.into_iter() .map(IntoDirValue::into_dir_value) .collect::>() .ok()?, cgraph::Relation::Negative, )), _ => None, }); if let Some((currencies, relation)) = currencies_data { let accepted_currencies_info = "Accepted Currencies"; let accepted_currencies_id = builder .make_in_aggregator(currencies, Some(accepted_currencies_info), None::<()>) .map_err(KgraphError::GraphConstructionError)?; agg_nodes.push((accepted_currencies_id, relation, cgraph::Strength::Strong)); } let mut amount_nodes = Vec::with_capacity(2); if let Some(min_amt) = pm_types.minimum_amount { let num_val = NumValue { number: min_amt, refinement: Some(NumValueRefinement::GreaterThanEqual), }; let min_amt_info = "Minimum Amount"; let min_amt_id = builder.make_value_node( dir::DirValue::PaymentAmount(num_val).into(), Some(min_amt_info), None::<()>, ); amount_nodes.push(min_amt_id); } if let Some(max_amt) = pm_types.maximum_amount { let num_val = NumValue { number: max_amt, refinement: Some(NumValueRefinement::LessThanEqual), }; let max_amt_info = "Maximum Amount"; let max_amt_id = builder.make_value_node( dir::DirValue::PaymentAmount(num_val).into(), Some(max_amt_info), None::<()>, ); amount_nodes.push(max_amt_id); } if !amount_nodes.is_empty() { let zero_num_val = NumValue { number: MinorUnit::zero(), refinement: None, }; let zero_amt_id = builder.make_value_node( dir::DirValue::PaymentAmount(zero_num_val).into(), Some("zero_amount"), None::<()>, ); let or_node_neighbor_id = if amount_nodes.len() == 1 { amount_nodes .first() .copied() .ok_or(KgraphError::IndexingError)? } else { let nodes = amount_nodes .iter() .copied() .map(|node_id| { ( node_id, cgraph::Relation::Positive, cgraph::Strength::Strong, ) }) .collect::>(); builder .make_all_aggregator( &nodes, Some("amount_constraint_aggregator"), None::<()>, None, ) .map_err(KgraphError::GraphConstructionError)? }; let any_aggregator = builder .make_any_aggregator( &[ ( zero_amt_id, cgraph::Relation::Positive, cgraph::Strength::Strong, ), ( or_node_neighbor_id, cgraph::Relation::Positive, cgraph::Strength::Strong, ), ], Some("zero_plus_limits_amount_aggregator"), None::<()>, None, ) .map_err(KgraphError::GraphConstructionError)?; agg_nodes.push(( any_aggregator, cgraph::Relation::Positive, cgraph::Strength::Strong, )); } let pmt_all_aggregator_info = "All Aggregator for PaymentMethodType"; builder .make_all_aggregator(&agg_nodes, Some(pmt_all_aggregator_info), None::<()>, None) .map_err(KgraphError::GraphConstructionError) } fn compile_payment_method_enabled( builder: &mut cgraph::ConstraintGraphBuilder, enabled: admin_api::PaymentMethodsEnabled, ) -> Result, KgraphError> { let agg_id = if !enabled .payment_method_types .as_ref() .map(|v| v.is_empty()) .unwrap_or(true) { let pm_info = "PaymentMethod"; let pm_id = builder.make_value_node( enabled.payment_method.into_dir_value().map(Into::into)?, Some(pm_info), None::<()>, ); let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); if let Some(pm_types) = enabled.payment_method_types { for pm_type in pm_types { let node_id = compile_request_pm_types(builder, pm_type, enabled.payment_method)?; agg_nodes.push(( node_id, cgraph::Relation::Positive, cgraph::Strength::Strong, )); } } let any_aggregator_info = "Any aggregation for PaymentMethodsType"; let pm_type_agg_id = builder .make_any_aggregator(&agg_nodes, Some(any_aggregator_info), None::<()>, None) .map_err(KgraphError::GraphConstructionError)?; let all_aggregator_info = "All aggregation for PaymentMethod"; let enabled_pm_agg_id = builder .make_all_aggregator( &[ (pm_id, cgraph::Relation::Positive, cgraph::Strength::Strong), ( pm_type_agg_id, cgraph::Relation::Positive, cgraph::Strength::Strong, ), ], Some(all_aggregator_info), None::<()>, None, ) .map_err(KgraphError::GraphConstructionError)?; Some(enabled_pm_agg_id) } else { None }; 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, ) -> 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!(RealTimePaymentType)); 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.append(collect_global_variants!(OpenBankingType)); 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, 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, 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, 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.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 { agg_nodes.push(( pm_enabled_id, cgraph::Relation::Positive, cgraph::Strength::Strong, )); } } } let aggregator_info = "Available Payment methods for connector"; let pms_enabled_agg_id = builder .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 })); let connector_info = "Connector"; let connector_node_id = builder.make_value_node(connector_dir_val.into(), Some(connector_info), None::<()>); builder .make_edge( domain_level_node_id, connector_node_id, cgraph::Strength::Normal, cgraph::Relation::Positive, None::, ) .map_err(KgraphError::GraphConstructionError)?; Ok(()) } pub fn make_mca_graph( accts: Vec, config: &kgraph_types::CountryCurrencyFilter, ) -> Result, KgraphError> { let mut builder = cgraph::ConstraintGraphBuilder::new(); let _domain = builder.make_domain( DOMAIN_IDENTIFIER.to_string(), "Payment methods enabled for MerchantConnectorAccount", ); for acct in accts { compile_merchant_connector_graph(&mut builder, acct, config)?; } Ok(builder.build()) } #[cfg(test)] mod tests { #![allow(clippy::expect_used)] use std::collections::{HashMap, HashSet}; use api_models::enums as api_enums; use euclid::{ dirval, dssa::graph::{AnalysisContext, CgraphExt}, }; use hyperswitch_constraint_graph::{ConstraintGraph, CycleCheck, Memoization}; use super::*; use crate::types as kgraph_types; fn build_test_data() -> ConstraintGraph { use api_models::{admin::*, payment_methods::*}; let profile_id = common_utils::generate_profile_id_of_default_length(); #[cfg(all(feature = "v2", feature = "merchant_connector_account_v2"))] let stripe_account = MerchantConnectorResponse { connector_type: api_enums::ConnectorType::FizOperations, connector_name: "stripe".to_string(), id: common_utils::generate_merchant_connector_account_id_of_default_length(), connector_label: Some("something".to_string()), connector_account_details: masking::Secret::new(serde_json::json!({})), disabled: None, metadata: None, payment_methods_enabled: Some(vec![PaymentMethodsEnabled { payment_method: api_enums::PaymentMethod::Card, payment_method_types: Some(vec![ RequestPaymentMethodTypes { payment_method_type: api_enums::PaymentMethodType::Credit, payment_experience: None, card_networks: Some(vec![ api_enums::CardNetwork::Visa, api_enums::CardNetwork::Mastercard, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::INR, ])), accepted_countries: None, minimum_amount: Some(MinorUnit::new(10)), maximum_amount: Some(MinorUnit::new(1000)), recurring_enabled: true, installment_payment_enabled: true, }, RequestPaymentMethodTypes { payment_method_type: api_enums::PaymentMethodType::Debit, payment_experience: None, card_networks: Some(vec![ api_enums::CardNetwork::Maestro, api_enums::CardNetwork::JCB, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::GBP, ])), accepted_countries: None, minimum_amount: Some(MinorUnit::new(10)), maximum_amount: Some(MinorUnit::new(1000)), recurring_enabled: true, installment_payment_enabled: true, }, ]), }]), frm_configs: None, connector_webhook_details: None, profile_id, applepay_verified_domains: None, pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, }; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "merchant_connector_account_v2") ))] let stripe_account = MerchantConnectorResponse { connector_type: api_enums::ConnectorType::FizOperations, connector_name: "stripe".to_string(), merchant_connector_id: common_utils::generate_merchant_connector_account_id_of_default_length(), business_country: Some(api_enums::CountryAlpha2::US), connector_label: Some("something".to_string()), business_label: Some("food".to_string()), business_sub_label: None, connector_account_details: masking::Secret::new(serde_json::json!({})), test_mode: None, disabled: None, metadata: None, payment_methods_enabled: Some(vec![PaymentMethodsEnabled { payment_method: api_enums::PaymentMethod::Card, payment_method_types: Some(vec![ RequestPaymentMethodTypes { payment_method_type: api_enums::PaymentMethodType::Credit, payment_experience: None, card_networks: Some(vec![ api_enums::CardNetwork::Visa, api_enums::CardNetwork::Mastercard, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::INR, ])), accepted_countries: None, minimum_amount: Some(MinorUnit::new(10)), maximum_amount: Some(MinorUnit::new(1000)), recurring_enabled: true, installment_payment_enabled: true, }, RequestPaymentMethodTypes { payment_method_type: api_enums::PaymentMethodType::Debit, payment_experience: None, card_networks: Some(vec![ api_enums::CardNetwork::Maestro, api_enums::CardNetwork::JCB, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::GBP, ])), accepted_countries: None, minimum_amount: Some(MinorUnit::new(10)), maximum_amount: Some(MinorUnit::new(1000)), recurring_enabled: true, installment_payment_enabled: true, }, ]), }]), frm_configs: None, connector_webhook_details: None, profile_id, applepay_verified_domains: None, pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, }; 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, ), kgraph_types::CurrencyCountryFlowFilter { currency: Some(HashSet::from([ api_enums::Currency::INR, api_enums::Currency::USD, ])), country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), not_available_flows: Some(kgraph_types::NotAvailableFlows { capture_method: Some(api_enums::CaptureMethod::Manual), }), }, ), ( kgraph_types::PaymentMethodFilterKey::PaymentMethodType( api_enums::PaymentMethodType::Debit, ), kgraph_types::CurrencyCountryFlowFilter { currency: Some(HashSet::from([ api_enums::Currency::GBP, api_enums::Currency::PHP, ])), country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), not_available_flows: Some(kgraph_types::NotAvailableFlows { capture_method: Some(api_enums::CaptureMethod::Manual), }), }, ), ])), )]), default_configs: None, }; make_mca_graph(vec![stripe_account], &config_map).expect("Failed graph construction") } #[test] fn test_credit_card_success_case() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Credit), dirval!(CardNetwork = Visa), dirval!(PaymentCurrency = INR), dirval!(PaymentAmount = 101), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_ok()); } #[test] fn test_debit_card_success_case() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), dirval!(CardNetwork = Maestro), dirval!(PaymentCurrency = GBP), dirval!(PaymentAmount = 100), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_ok()); } #[test] fn test_single_mismatch_failure_case() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), dirval!(CardNetwork = Maestro), dirval!(PaymentCurrency = PHP), dirval!(PaymentAmount = 100), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_err()); } #[test] fn test_amount_mismatch_failure_case() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), dirval!(CardNetwork = Visa), dirval!(PaymentCurrency = GBP), dirval!(PaymentAmount = 7), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_err()); } #[test] fn test_incomplete_data_failure_case() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), dirval!(PaymentCurrency = GBP), dirval!(PaymentAmount = 7), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); //println!("{:#?}", result); //println!("{}", serde_json::to_string_pretty(&result).expect("Hello")); assert!(result.is_err()); } #[test] fn test_incomplete_data_failure_case2() { let graph = build_test_data(); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(CardType = Debit), dirval!(CardNetwork = Visa), dirval!(PaymentCurrency = GBP), dirval!(PaymentAmount = 100), ]), &mut Memoization::new(), &mut CycleCheck::new(), None, ); //println!("{:#?}", result); //println!("{}", serde_json::to_string_pretty(&result).expect("Hello")); assert!(result.is_err()); } #[test] fn test_sandbox_applepay_bug_usecase() { let value = serde_json::json!([ { "connector_type": "payment_processor", "connector_name": "bluesnap", "merchant_connector_id": "REDACTED", "status": "inactive", "connector_account_details": { "auth_type": "BodyKey", "api_key": "REDACTED", "key1": "REDACTED" }, "test_mode": true, "disabled": false, "payment_methods_enabled": [ { "payment_method": "card", "payment_method_types": [ { "payment_method_type": "credit", "payment_experience": null, "card_networks": [ "Mastercard", "Visa", "AmericanExpress", "JCB", "DinersClub", "Discover", "CartesBancaires", "UnionPay" ], "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true }, { "payment_method_type": "debit", "payment_experience": null, "card_networks": [ "Mastercard", "Visa", "Interac", "AmericanExpress", "JCB", "DinersClub", "Discover", "CartesBancaires", "UnionPay" ], "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true } ] }, { "payment_method": "wallet", "payment_method_types": [ { "payment_method_type": "google_pay", "payment_experience": "invoke_sdk_client", "card_networks": null, "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true } ] } ], "metadata": {}, "business_country": "US", "business_label": "default", "business_sub_label": null, "frm_configs": null }, { "connector_type": "payment_processor", "connector_name": "stripe", "merchant_connector_id": "REDACTED", "status": "inactive", "connector_account_details": { "auth_type": "HeaderKey", "api_key": "REDACTED" }, "test_mode": true, "disabled": false, "payment_methods_enabled": [ { "payment_method": "card", "payment_method_types": [ { "payment_method_type": "credit", "payment_experience": null, "card_networks": [ "Mastercard", "Visa", "AmericanExpress", "JCB", "DinersClub", "Discover", "CartesBancaires", "UnionPay" ], "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true }, { "payment_method_type": "debit", "payment_experience": null, "card_networks": [ "Mastercard", "Visa", "Interac", "AmericanExpress", "JCB", "DinersClub", "Discover", "CartesBancaires", "UnionPay" ], "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true } ] }, { "payment_method": "wallet", "payment_method_types": [ { "payment_method_type": "apple_pay", "payment_experience": "invoke_sdk_client", "card_networks": null, "accepted_currencies": null, "accepted_countries": null, "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true } ] }, { "payment_method": "pay_later", "payment_method_types": [] } ], "metadata": {}, "business_country": "US", "business_label": "default", "business_sub_label": null, "frm_configs": null } ]); let data: Vec = serde_json::from_value(value).expect("data"); 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), dirval!(PaymentCurrency = ILS), dirval!(PaymentMethod = Wallet), dirval!(WalletType = ApplePay), ]); let result = graph.key_value_analysis( dirval!(Connector = Stripe), &context, &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_ok(), "stripe validation failed"); let result = graph.key_value_analysis( dirval!(Connector = Bluesnap), &context, &mut Memoization::new(), &mut CycleCheck::new(), None, ); assert!(result.is_err(), "bluesnap validation failed"); } }