refactor(graph): refactor the Knowledge Graph to include configs check, while eligibility analysis (#4687)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Amisha Prabhat
2024-05-21 19:44:45 +05:30
committed by GitHub
parent 70612e4c6f
commit a917776bb8
19 changed files with 541 additions and 34 deletions

1
Cargo.lock generated
View File

@ -3999,6 +3999,7 @@ dependencies = [
"masking",
"serde",
"serde_json",
"strum 0.26.2",
"thiserror",
]

View File

@ -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::*,
@ -112,6 +113,7 @@ impl<V: cgraph::ValueNode> AnalysisError<V> {
}
}
#[derive(Debug)]
pub struct AnalysisContext {
keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>>,
}

View File

@ -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;

View File

@ -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};

View File

@ -91,8 +91,12 @@ pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult {
.collect::<Result<_, _>>()
.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()?;

View File

@ -28,7 +28,7 @@ impl From<DomainId> for DomainIdOrIdentifier<'_> {
Self::DomainId(value)
}
}
#[derive(Debug)]
pub struct ConstraintGraphBuilder<'a, V: ValueNode> {
domain: DenseMap<DomainId, DomainInfo<'a>>,
nodes: DenseMap<NodeId, Node<V>>,

View File

@ -13,6 +13,7 @@ use crate::{
},
};
#[derive(Debug)]
struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext<Value = V>> {
ctx: &'a C,
node: &'a Node<V>,
@ -24,6 +25,7 @@ struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext<Value = V>> {
domains: Option<&'a [DomainId]>,
}
#[derive(Debug)]
pub struct ConstraintGraph<'a, V: ValueNode> {
pub domain: DenseMap<DomainId, DomainInfo<'a>>,
pub domain_identifier_map: FxHashMap<DomainIdentifier<'a>, 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(())
}
}

View File

@ -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"

View File

@ -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) {

View File

@ -1,3 +1,4 @@
pub mod error;
pub mod mca;
pub mod transformers;
pub mod types;

View File

@ -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<dir::DirValue, KgraphError> {
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::<Vec<_>>()
};
}
fn global_vec_pmt(
enabled_pmt: Vec<dir::DirValue>,
builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>,
) -> Vec<cgraph::NodeId> {
let mut global_vector: Vec<dir::DirValue> = 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::<Vec<_>>();
global_vector
.into_iter()
.map(|dir_v| {
builder.make_value_node(
cgraph::NodeValue::Value(dir_v),
Some("Payment Method Type"),
None::<()>,
)
})
.collect::<Vec<_>>()
}
fn compile_graph_for_countries_and_currencies(
builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>,
config: &kgraph_types::CurrencyCountryFlowFilter,
payment_method_type_node: cgraph::NodeId,
) -> Result<cgraph::NodeId, KgraphError> {
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::<Result<Vec<_>, _>>()?;
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<cgraph::NodeId, KgraphError> {
let mut agg_node_id: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
let mut pmt_enabled: Vec<dir::DirValue> = 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<cgraph::NodeId> = 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::<Vec<_>>();
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<admin_api::MerchantConnectorResponse>,
config: &kgraph_types::CountryCurrencyFilter,
) -> Result<cgraph::ConstraintGraph<'a, dir::DirValue>, 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<admin_api::MerchantConnectorResponse> =
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),

View File

@ -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<api_enums::RoutableConnectors, PaymentMethodFilters>,
pub default_configs: Option<PaymentMethodFilters>,
}
#[derive(Debug, Deserialize, Clone, Default)]
#[serde(transparent)]
pub struct PaymentMethodFilters(pub HashMap<PaymentMethodFilterKey, CurrencyCountryFlowFilter>);
#[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<HashSet<api_enums::Currency>>,
pub country: Option<HashSet<api_enums::CountryAlpha2>>,
pub not_available_flows: Option<NotAvailableFlows>,
}
#[derive(Debug, Deserialize, Copy, Clone, Default)]
#[serde(default)]
pub struct NotAvailableFlows {
pub capture_method: Option<api_enums::CaptureMethod>,
}

View File

@ -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<dsl_inputs::BackendInput> {
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::<Result<Vec<_>, _>>()
.change_context(errors::RoutingError::KgraphCacheRefreshFailed)?;
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))?;
let kgraph = mca_graph::make_mca_graph(api_mcas)
Ok((key, value.foreign_into()))
})
.collect::<Result<HashMap<_, _>, 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")?;

View File

@ -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<routing_types::RoutableConnectorChoice> for dsl_ast::ConnectorChoice {
fn foreign_from(from: routing_types::RoutableConnectorChoice) -> Self {
@ -52,3 +58,40 @@ impl ForeignFrom<storage_enums::MandateDataType> for dsl_enums::MandateType {
}
}
}
impl ForeignFrom<settings::PaymentMethodFilterKey> 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<settings::CurrencyCountryFlowFilter> 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<settings::NotAvailableFlows> for types::NotAvailableFlows {
fn foreign_from(from: settings::NotAvailableFlows) -> Self {
Self {
capture_method: from.capture_method,
}
}
}
impl ForeignFrom<settings::PaymentMethodFilters> 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::<HashMap<_, _>>();
Self(iter_map)
}
}

View File

@ -41,7 +41,7 @@
"zip": "94122",
"first_name": "John",
"last_name": "Doe",
"country": "SE"
"country": "ES"
},
"email": "narayan@example.com"
},

View File

@ -42,7 +42,7 @@
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "DE"
"country": "NL"
}
},
"browser_info": {

View File

@ -48,7 +48,7 @@
},
"bank_name": "hypo_oberosterreich_salzburg_steiermark",
"preferred_language": "en",
"country": "DE"
"country": "AT"
}
}
},

View File

@ -42,7 +42,7 @@
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "DE"
"country": "AT"
}
},
"browser_info": {

View File

@ -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",