use std::fmt::Debug; use common_utils::{errors::ParsingError, ext_traits::ValueExt, pii}; pub use euclid::{ dssa::types::EuclidAnalysable, frontend::{ ast, dir::{DirKeyKind, EuclidDirFilter}, }, }; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::enums::{RoutableConnectors, TransactionType}; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(tag = "type", content = "data", rename_all = "snake_case")] pub enum ConnectorSelection { Priority(Vec), VolumeSplit(Vec), } impl ConnectorSelection { pub fn get_connector_list(&self) -> Vec { match self { Self::Priority(list) => list.clone(), Self::VolumeSplit(splits) => { splits.iter().map(|split| split.connector.clone()).collect() } } } } #[cfg(feature = "v2")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RoutingConfigRequest { pub name: String, pub description: String, pub algorithm: RoutingAlgorithm, #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, } #[cfg(feature = "v1")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RoutingConfigRequest { pub name: Option, pub description: Option, pub algorithm: Option, #[schema(value_type = Option)] pub profile_id: Option, } #[derive(Debug, serde::Serialize, ToSchema)] pub struct ProfileDefaultRoutingConfig { #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, pub connectors: Vec, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct RoutingRetrieveQuery { pub limit: Option, pub offset: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct RoutingRetrieveLinkQuery { pub profile_id: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct RoutingRetrieveLinkQueryWrapper { pub routing_query: RoutingRetrieveQuery, pub profile_id: common_utils::id_type::ProfileId, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] /// Response of the retrieved routing configs for a merchant account pub struct RoutingRetrieveResponse { pub algorithm: Option, } #[derive(Debug, serde::Serialize, ToSchema)] #[serde(untagged)] pub enum LinkedRoutingConfigRetrieveResponse { MerchantAccountBased(RoutingRetrieveResponse), ProfileBased(Vec), } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] /// Routing Algorithm specific to merchants pub struct MerchantRoutingAlgorithm { #[schema(value_type = String)] pub id: common_utils::id_type::RoutingId, #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, pub name: String, pub description: String, pub algorithm: RoutingAlgorithm, pub created_at: i64, pub modified_at: i64, pub algorithm_for: TransactionType, } impl EuclidDirFilter for ConnectorSelection { const ALLOWED: &'static [DirKeyKind] = &[ DirKeyKind::PaymentMethod, DirKeyKind::CardBin, DirKeyKind::CardType, DirKeyKind::CardNetwork, DirKeyKind::PayLaterType, DirKeyKind::WalletType, DirKeyKind::UpiType, DirKeyKind::BankRedirectType, DirKeyKind::BankDebitType, DirKeyKind::CryptoType, DirKeyKind::MetaData, DirKeyKind::PaymentAmount, DirKeyKind::PaymentCurrency, DirKeyKind::AuthenticationType, DirKeyKind::MandateAcceptanceType, DirKeyKind::MandateType, DirKeyKind::PaymentType, DirKeyKind::SetupFutureUsage, DirKeyKind::CaptureMethod, DirKeyKind::BillingCountry, DirKeyKind::BusinessCountry, DirKeyKind::BusinessLabel, DirKeyKind::MetaData, DirKeyKind::RewardType, DirKeyKind::VoucherType, DirKeyKind::CardRedirectType, DirKeyKind::BankTransferType, DirKeyKind::RealTimePaymentType, ]; } impl EuclidAnalysable for ConnectorSelection { fn get_dir_value_for_analysis( &self, rule_name: String, ) -> Vec<(euclid::frontend::dir::DirValue, euclid::types::Metadata)> { self.get_connector_list() .into_iter() .map(|connector_choice| { let connector_name = connector_choice.connector.to_string(); let mca_id = connector_choice.merchant_connector_id.clone(); ( euclid::frontend::dir::DirValue::Connector(Box::new(connector_choice.into())), std::collections::HashMap::from_iter([( "CONNECTOR_SELECTION".to_string(), serde_json::json!({ "rule_name": rule_name, "connector_name": connector_name, "mca_id": mca_id, }), )]), ) }) .collect() } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct ConnectorVolumeSplit { pub connector: RoutableConnectorChoice, pub split: u8, } /// Routable Connector chosen for a payment #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(from = "RoutableChoiceSerde", into = "RoutableChoiceSerde")] pub struct RoutableConnectorChoice { #[serde(skip)] pub choice_kind: RoutableChoiceKind, pub connector: RoutableConnectors, #[schema(value_type = Option)] pub merchant_connector_id: Option, } #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub enum RoutableChoiceKind { OnlyConnector, FullStruct, } #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(untagged)] pub enum RoutableChoiceSerde { OnlyConnector(Box), FullStruct { connector: RoutableConnectors, merchant_connector_id: Option, }, } impl std::fmt::Display for RoutableConnectorChoice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let base = self.connector.to_string(); if let Some(mca_id) = &self.merchant_connector_id { return write!(f, "{}:{}", base, mca_id.get_string_repr()); } write!(f, "{}", base) } } impl From for ast::ConnectorChoice { fn from(value: RoutableConnectorChoice) -> Self { Self { connector: value.connector, } } } impl PartialEq for RoutableConnectorChoice { fn eq(&self, other: &Self) -> bool { self.connector.eq(&other.connector) && self.merchant_connector_id.eq(&other.merchant_connector_id) } } impl Eq for RoutableConnectorChoice {} impl From for RoutableConnectorChoice { fn from(value: RoutableChoiceSerde) -> Self { match value { RoutableChoiceSerde::OnlyConnector(connector) => Self { choice_kind: RoutableChoiceKind::OnlyConnector, connector: *connector, merchant_connector_id: None, }, RoutableChoiceSerde::FullStruct { connector, merchant_connector_id, } => Self { choice_kind: RoutableChoiceKind::FullStruct, connector, merchant_connector_id, }, } } } impl From for RoutableChoiceSerde { fn from(value: RoutableConnectorChoice) -> Self { match value.choice_kind { RoutableChoiceKind::OnlyConnector => Self::OnlyConnector(Box::new(value.connector)), RoutableChoiceKind::FullStruct => Self::FullStruct { connector: value.connector, merchant_connector_id: value.merchant_connector_id, }, } } } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct RoutableConnectorChoiceWithStatus { pub routable_connector_choice: RoutableConnectorChoice, pub status: bool, } impl RoutableConnectorChoiceWithStatus { pub fn new(routable_connector_choice: RoutableConnectorChoice, status: bool) -> Self { Self { routable_connector_choice, status, } } } #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum RoutingAlgorithmKind { Single, Priority, VolumeSplit, Advanced, Dynamic, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RoutingPayloadWrapper { pub updated_config: Vec, pub profile_id: common_utils::id_type::ProfileId, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[serde( tag = "type", content = "data", rename_all = "snake_case", try_from = "RoutingAlgorithmSerde" )] /// Routing Algorithm kind pub enum RoutingAlgorithm { Single(Box), Priority(Vec), VolumeSplit(Vec), #[schema(value_type=ProgramConnectorSelection)] Advanced(ast::Program), } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "type", content = "data", rename_all = "snake_case")] pub enum RoutingAlgorithmSerde { Single(Box), Priority(Vec), VolumeSplit(Vec), Advanced(ast::Program), } impl TryFrom for RoutingAlgorithm { type Error = error_stack::Report; fn try_from(value: RoutingAlgorithmSerde) -> Result { match &value { RoutingAlgorithmSerde::Priority(i) if i.is_empty() => { Err(ParsingError::StructParseFailure( "Connectors list can't be empty for Priority Algorithm", ))? } RoutingAlgorithmSerde::VolumeSplit(i) if i.is_empty() => { Err(ParsingError::StructParseFailure( "Connectors list can't be empty for Volume split Algorithm", ))? } _ => {} }; Ok(match value { RoutingAlgorithmSerde::Single(i) => Self::Single(i), RoutingAlgorithmSerde::Priority(i) => Self::Priority(i), RoutingAlgorithmSerde::VolumeSplit(i) => Self::VolumeSplit(i), RoutingAlgorithmSerde::Advanced(i) => Self::Advanced(i), }) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[serde( tag = "type", content = "data", rename_all = "snake_case", try_from = "StraightThroughAlgorithmSerde", into = "StraightThroughAlgorithmSerde" )] pub enum StraightThroughAlgorithm { #[schema(title = "Single")] Single(Box), #[schema(title = "Priority")] Priority(Vec), #[schema(title = "VolumeSplit")] VolumeSplit(Vec), } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "type", content = "data", rename_all = "snake_case")] pub enum StraightThroughAlgorithmInner { Single(Box), Priority(Vec), VolumeSplit(Vec), } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(untagged)] pub enum StraightThroughAlgorithmSerde { Direct(StraightThroughAlgorithmInner), Nested { algorithm: StraightThroughAlgorithmInner, }, } impl TryFrom for StraightThroughAlgorithm { type Error = error_stack::Report; fn try_from(value: StraightThroughAlgorithmSerde) -> Result { let inner = match value { StraightThroughAlgorithmSerde::Direct(algorithm) => algorithm, StraightThroughAlgorithmSerde::Nested { algorithm } => algorithm, }; match &inner { StraightThroughAlgorithmInner::Priority(i) if i.is_empty() => { Err(ParsingError::StructParseFailure( "Connectors list can't be empty for Priority Algorithm", ))? } StraightThroughAlgorithmInner::VolumeSplit(i) if i.is_empty() => { Err(ParsingError::StructParseFailure( "Connectors list can't be empty for Volume split Algorithm", ))? } _ => {} }; Ok(match inner { StraightThroughAlgorithmInner::Single(single) => Self::Single(single), StraightThroughAlgorithmInner::Priority(plist) => Self::Priority(plist), StraightThroughAlgorithmInner::VolumeSplit(vsplit) => Self::VolumeSplit(vsplit), }) } } impl From for StraightThroughAlgorithmSerde { fn from(value: StraightThroughAlgorithm) -> Self { let inner = match value { StraightThroughAlgorithm::Single(conn) => StraightThroughAlgorithmInner::Single(conn), StraightThroughAlgorithm::Priority(plist) => { StraightThroughAlgorithmInner::Priority(plist) } StraightThroughAlgorithm::VolumeSplit(vsplit) => { StraightThroughAlgorithmInner::VolumeSplit(vsplit) } }; Self::Nested { algorithm: inner } } } impl From for RoutingAlgorithm { fn from(value: StraightThroughAlgorithm) -> Self { match value { StraightThroughAlgorithm::Single(conn) => Self::Single(conn), StraightThroughAlgorithm::Priority(conns) => Self::Priority(conns), StraightThroughAlgorithm::VolumeSplit(splits) => Self::VolumeSplit(splits), } } } impl RoutingAlgorithm { pub fn get_kind(&self) -> RoutingAlgorithmKind { match self { Self::Single(_) => RoutingAlgorithmKind::Single, Self::Priority(_) => RoutingAlgorithmKind::Priority, Self::VolumeSplit(_) => RoutingAlgorithmKind::VolumeSplit, Self::Advanced(_) => RoutingAlgorithmKind::Advanced, } } } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct RoutingAlgorithmRef { pub algorithm_id: Option, pub timestamp: i64, pub config_algo_id: Option, pub surcharge_config_algo_id: Option, } impl RoutingAlgorithmRef { pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId) { self.algorithm_id = Some(new_id); self.timestamp = common_utils::date_time::now_unix_timestamp(); } pub fn update_conditional_config_id(&mut self, ids: String) { self.config_algo_id = Some(ids); self.timestamp = common_utils::date_time::now_unix_timestamp(); } pub fn update_surcharge_config_id(&mut self, ids: String) { self.surcharge_config_algo_id = Some(ids); self.timestamp = common_utils::date_time::now_unix_timestamp(); } pub fn parse_routing_algorithm( value: Option, ) -> Result, error_stack::Report> { value .map(|val| val.parse_value::("RoutingAlgorithmRef")) .transpose() } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RoutingDictionaryRecord { #[schema(value_type = String)] pub id: common_utils::id_type::RoutingId, #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, pub name: String, pub kind: RoutingAlgorithmKind, pub description: String, pub created_at: i64, pub modified_at: i64, pub algorithm_for: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RoutingDictionary { #[schema(value_type = String)] pub merchant_id: common_utils::id_type::MerchantId, pub active_id: Option, pub records: Vec, } #[derive(serde::Serialize, serde::Deserialize, Debug, ToSchema)] #[serde(untagged)] pub enum RoutingKind { Config(RoutingDictionary), RoutingAlgorithm(Vec), } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct RoutingAlgorithmId { #[schema(value_type = String)] pub routing_algorithm_id: common_utils::id_type::RoutingId, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RoutingLinkWrapper { pub profile_id: common_utils::id_type::ProfileId, pub algorithm_id: RoutingAlgorithmId, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicAlgorithmWithTimestamp { pub algorithm_id: Option, pub timestamp: i64, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { pub success_based_algorithm: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SuccessBasedAlgorithm { pub algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp, #[serde(default)] pub enabled_feature: SuccessBasedRoutingFeatures, } impl SuccessBasedAlgorithm { pub fn update_enabled_features(&mut self, feature_to_enable: SuccessBasedRoutingFeatures) { self.enabled_feature = feature_to_enable } } impl DynamicRoutingAlgorithmRef { pub fn update_algorithm_id( &mut self, new_id: common_utils::id_type::RoutingId, enabled_feature: SuccessBasedRoutingFeatures, ) { self.success_based_algorithm = Some(SuccessBasedAlgorithm { algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { algorithm_id: Some(new_id), timestamp: common_utils::date_time::now_unix_timestamp(), }, enabled_feature, }) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct ToggleSuccessBasedRoutingQuery { pub enable: SuccessBasedRoutingFeatures, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] #[serde(rename_all = "snake_case")] pub enum SuccessBasedRoutingFeatures { Metrics, DynamicConnectorSelection, #[default] None, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct SuccessBasedRoutingUpdateConfigQuery { #[schema(value_type = String)] pub algorithm_id: common_utils::id_type::RoutingId, #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ToggleSuccessBasedRoutingWrapper { pub profile_id: common_utils::id_type::ProfileId, pub feature_to_enable: SuccessBasedRoutingFeatures, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ToggleSuccessBasedRoutingPath { #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct SuccessBasedRoutingConfig { pub params: Option>, pub config: Option, } impl Default for SuccessBasedRoutingConfig { fn default() -> Self { Self { params: Some(vec![SuccessBasedRoutingConfigParams::PaymentMethod]), config: Some(SuccessBasedRoutingConfigBody { min_aggregates_size: Some(2), default_success_rate: Some(100.0), max_aggregates_size: Some(3), current_block_threshold: Some(CurrentBlockThreshold { duration_in_mins: Some(5), max_total_count: Some(2), }), }), } } } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema, strum::Display)] pub enum SuccessBasedRoutingConfigParams { PaymentMethod, PaymentMethodType, AuthenticationType, Currency, Country, CardNetwork, CardBin, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct SuccessBasedRoutingConfigBody { pub min_aggregates_size: Option, pub default_success_rate: Option, pub max_aggregates_size: Option, pub current_block_threshold: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct CurrentBlockThreshold { pub duration_in_mins: Option, pub max_total_count: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SuccessBasedRoutingPayloadWrapper { pub updated_config: SuccessBasedRoutingConfig, pub algorithm_id: common_utils::id_type::RoutingId, pub profile_id: common_utils::id_type::ProfileId, } impl SuccessBasedRoutingConfig { pub fn update(&mut self, new: Self) { if let Some(params) = new.params { self.params = Some(params) } if let Some(new_config) = new.config { self.config.as_mut().map(|config| config.update(new_config)); } } } impl SuccessBasedRoutingConfigBody { pub fn update(&mut self, new: Self) { if let Some(min_aggregates_size) = new.min_aggregates_size { self.min_aggregates_size = Some(min_aggregates_size) } if let Some(default_success_rate) = new.default_success_rate { self.default_success_rate = Some(default_success_rate) } if let Some(max_aggregates_size) = new.max_aggregates_size { self.max_aggregates_size = Some(max_aggregates_size) } if let Some(current_block_threshold) = new.current_block_threshold { self.current_block_threshold .as_mut() .map(|threshold| threshold.update(current_block_threshold)); } } } impl CurrentBlockThreshold { pub fn update(&mut self, new: Self) { if let Some(max_total_count) = new.max_total_count { self.max_total_count = Some(max_total_count) } } }