File: crates/api_models/src/routing.rs
 Public functions: 40
 Public structs: 64

use std::fmt::Debug;

use common_types::three_ds_decision_rule_engine::{ThreeDSDecision, ThreeDSDecisionRule};
use common_utils::{
    errors::{ParsingError, ValidationError},
    ext_traits::ValueExt,
    pii,
};
use euclid::frontend::ast::Program;
pub use euclid::{
    dssa::types::EuclidAnalysable,
    frontend::{
        ast,
        dir::{DirKeyKind, EuclidDirFilter},
    },
};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::{
    enums::{RoutableConnectors, TransactionType},
    open_router,
};

// Define constants for default values
const DEFAULT_LATENCY_THRESHOLD: f64 = 90.0;
const DEFAULT_BUCKET_SIZE: i32 = 200;
const DEFAULT_HEDGING_PERCENT: f64 = 5.0;
const DEFAULT_ELIMINATION_THRESHOLD: f64 = 0.35;
const DEFAULT_PAYMENT_METHOD: &str = "CARD";

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum ConnectorSelection {
    Priority(Vec<RoutableConnectorChoice>),
    VolumeSplit(Vec<ConnectorVolumeSplit>),
}

impl ConnectorSelection {
    pub fn get_connector_list(&self) -> Vec<RoutableConnectorChoice> {
        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: StaticRoutingAlgorithm,
    #[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<String>,
    pub description: Option<String>,
    pub algorithm: Option<StaticRoutingAlgorithm>,
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    pub transaction_type: Option<TransactionType>,
}

#[derive(Debug, serde::Serialize, ToSchema)]
pub struct ProfileDefaultRoutingConfig {
    #[schema(value_type = String)]
    pub profile_id: common_utils::id_type::ProfileId,
    pub connectors: Vec<RoutableConnectorChoice>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveQuery {
    pub limit: Option<u16>,
    pub offset: Option<u8>,
    pub transaction_type: Option<TransactionType>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingActivatePayload {
    pub transaction_type: Option<TransactionType>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveLinkQuery {
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    pub transaction_type: Option<TransactionType>,
}

#[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<MerchantRoutingAlgorithm>,
}

#[derive(Debug, serde::Serialize, ToSchema)]
#[serde(untagged)]
pub enum LinkedRoutingConfigRetrieveResponse {
    MerchantAccountBased(Box<RoutingRetrieveResponse>),
    ProfileBased(Vec<RoutingDictionaryRecord>),
}

#[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: RoutingAlgorithmWrapper,
    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, PartialEq)]
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<String>)]
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, PartialEq)]
pub enum RoutableChoiceKind {
    OnlyConnector,
    FullStruct,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum RoutableChoiceSerde {
    OnlyConnector(Box<RoutableConnectors>),
    FullStruct {
        connector: RoutableConnectors,
        merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    },
}

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<RoutableConnectorChoice> 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<RoutableChoiceSerde> 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<RoutableConnectorChoice> 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, PartialEq, 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,
    ThreeDsDecisionRule,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RoutingPayloadWrapper {
    pub updated_config: Vec<RoutableConnectorChoice>,
    pub profile_id: common_utils::id_type::ProfileId,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(untagged)]
pub enum RoutingAlgorithmWrapper {
    Static(StaticRoutingAlgorithm),
    Dynamic(DynamicRoutingAlgorithm),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(untagged)]
pub enum DynamicRoutingAlgorithm {
    EliminationBasedAlgorithm(EliminationRoutingConfig),
    SuccessBasedAlgorithm(SuccessBasedRoutingConfig),
    ContractBasedAlgorithm(ContractBasedRoutingConfig),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(
    tag = "type",
    content = "data",
    rename_all = "snake_case",
    try_from = "RoutingAlgorithmSerde"
)]
pub enum StaticRoutingAlgorithm {
    Single(Box<RoutableConnectorChoice>),
    Priority(Vec<RoutableConnectorChoice>),
    VolumeSplit(Vec<ConnectorVolumeSplit>),
    #[schema(value_type=ProgramConnectorSelection)]
    Advanced(Program<ConnectorSelection>),
    #[schema(value_type=ProgramThreeDsDecisionRule)]
    ThreeDsDecisionRule(Program<ThreeDSDecisionRule>),
}

#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ProgramThreeDsDecisionRule {
    pub default_selection: ThreeDSDecisionRule,
    #[schema(value_type = RuleThreeDsDecisionRule)]
    pub rules: Vec<ast::Rule<ThreeDSDecisionRule>>,
    #[schema(value_type = HashMap<String, serde_json::Value>)]
    pub metadata: std::collections::HashMap<String, serde_json::Value>,
}

#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RuleThreeDsDecisionRule {
    pub name: String,
    pub connector_selection: ThreeDSDecision,
    #[schema(value_type = Vec<IfStatement>)]
    pub statements: Vec<ast::IfStatement>,
}

impl StaticRoutingAlgorithm {
    pub fn should_validate_connectors_in_routing_config(&self) -> bool {
        match self {
            Self::Single(_) | Self::Priority(_) | Self::VolumeSplit(_) | Self::Advanced(_) => true,
            Self::ThreeDsDecisionRule(_) => false,
        }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum RoutingAlgorithmSerde {
    Single(Box<RoutableConnectorChoice>),
    Priority(Vec<RoutableConnectorChoice>),
    VolumeSplit(Vec<ConnectorVolumeSplit>),
    Advanced(Program<ConnectorSelection>),
    ThreeDsDecisionRule(Program<ThreeDSDecisionRule>),
}

impl TryFrom<RoutingAlgorithmSerde> for StaticRoutingAlgorithm {
    type Error = error_stack::Report<ParsingError>;

    fn try_from(value: RoutingAlgorithmSerde) -> Result<Self, Self::Error> {
        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),
            RoutingAlgorithmSerde::ThreeDsDecisionRule(i) => Self::ThreeDsDecisionRule(i),
        })
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq)]
#[serde(
    tag = "type",
    content = "data",
    rename_all = "snake_case",
    try_from = "StraightThroughAlgorithmSerde",
    into = "StraightThroughAlgorithmSerde"
)]
pub enum StraightThroughAlgorithm {
    #[schema(title = "Single")]
    Single(Box<RoutableConnectorChoice>),
    #[schema(title = "Priority")]
    Priority(Vec<RoutableConnectorChoice>),
    #[schema(title = "VolumeSplit")]
    VolumeSplit(Vec<ConnectorVolumeSplit>),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum StraightThroughAlgorithmInner {
    Single(Box<RoutableConnectorChoice>),
    Priority(Vec<RoutableConnectorChoice>),
    VolumeSplit(Vec<ConnectorVolumeSplit>),
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum StraightThroughAlgorithmSerde {
    Direct(StraightThroughAlgorithmInner),
    Nested {
        algorithm: StraightThroughAlgorithmInner,
    },
}

impl TryFrom<StraightThroughAlgorithmSerde> for StraightThroughAlgorithm {
    type Error = error_stack::Report<ParsingError>;

    fn try_from(value: StraightThroughAlgorithmSerde) -> Result<Self, Self::Error> {
        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<StraightThroughAlgorithm> 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<StraightThroughAlgorithm> for StaticRoutingAlgorithm {
    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 StaticRoutingAlgorithm {
    pub fn get_kind(&self) -> RoutingAlgorithmKind {
        match self {
            Self::Single(_) => RoutingAlgorithmKind::Single,
            Self::Priority(_) => RoutingAlgorithmKind::Priority,
            Self::VolumeSplit(_) => RoutingAlgorithmKind::VolumeSplit,
            Self::Advanced(_) => RoutingAlgorithmKind::Advanced,
            Self::ThreeDsDecisionRule(_) => RoutingAlgorithmKind::ThreeDsDecisionRule,
        }
    }
}

#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct RoutingAlgorithmRef {
    pub algorithm_id: Option<common_utils::id_type::RoutingId>,
    pub timestamp: i64,
    pub config_algo_id: Option<String>,
    pub surcharge_config_algo_id: Option<String>,
}

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<pii::SecretSerdeValue>,
    ) -> Result<Option<Self>, error_stack::Report<ParsingError>> {
        value
            .map(|val| val.parse_value::<Self>("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<TransactionType>,
    pub decision_engine_routing_id: Option<String>,
}

#[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<String>,
    pub records: Vec<RoutingDictionaryRecord>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, ToSchema)]
#[serde(untagged)]
pub enum RoutingKind {
    Config(RoutingDictionary),
    RoutingAlgorithm(Vec<RoutingDictionaryRecord>),
}

#[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, Clone, serde::Serialize, serde::Deserialize)]
pub struct DynamicAlgorithmWithTimestamp<T> {
    pub algorithm_id: Option<T>,
    pub timestamp: i64,
}

impl<T> DynamicAlgorithmWithTimestamp<T> {
    pub fn new(algorithm_id: Option<T>) -> Self {
        Self {
            algorithm_id,
            timestamp: common_utils::date_time::now_unix_timestamp(),
        }
    }
}

#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct DynamicRoutingAlgorithmRef {
    pub success_based_algorithm: Option<SuccessBasedAlgorithm>,
    pub dynamic_routing_volume_split: Option<u8>,
    pub elimination_routing_algorithm: Option<EliminationRoutingAlgorithm>,
    pub contract_based_routing: Option<ContractRoutingAlgorithm>,
    #[serde(default)]
    pub is_merchant_created_in_decision_engine: bool,
}

pub trait DynamicRoutingAlgoAccessor {
    fn get_algorithm_id_with_timestamp(
        self,
    ) -> DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId>;
    fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures;
}

impl DynamicRoutingAlgoAccessor for SuccessBasedAlgorithm {
    fn get_algorithm_id_with_timestamp(
        self,
    ) -> DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId> {
        self.algorithm_id_with_timestamp
    }
    fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures {
        &mut self.enabled_feature
    }
}

impl DynamicRoutingAlgoAccessor for EliminationRoutingAlgorithm {
    fn get_algorithm_id_with_timestamp(
        self,
    ) -> DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId> {
        self.algorithm_id_with_timestamp
    }
    fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures {
        &mut self.enabled_feature
    }
}

impl DynamicRoutingAlgoAccessor for ContractRoutingAlgorithm {
    fn get_algorithm_id_with_timestamp(
        self,
    ) -> DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId> {
        self.algorithm_id_with_timestamp
    }
    fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures {
        &mut self.enabled_feature
    }
}

impl EliminationRoutingAlgorithm {
    pub fn new(
        algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp<
            common_utils::id_type::RoutingId,
        >,
    ) -> Self {
        Self {
            algorithm_id_with_timestamp,
            enabled_feature: DynamicRoutingFeatures::None,
        }
    }
}

impl SuccessBasedAlgorithm {
    pub fn new(
        algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp<
            common_utils::id_type::RoutingId,
        >,
    ) -> Self {
        Self {
            algorithm_id_with_timestamp,
            enabled_feature: DynamicRoutingFeatures::None,
        }
    }
}

impl DynamicRoutingAlgorithmRef {
    pub fn update(&mut self, new: Self) {
        if let Some(elimination_routing_algorithm) = new.elimination_routing_algorithm {
            self.elimination_routing_algorithm = Some(elimination_routing_algorithm)
        }
        if let Some(success_based_algorithm) = new.success_based_algorithm {
            self.success_based_algorithm = Some(success_based_algorithm)
        }
        if let Some(contract_based_routing) = new.contract_based_routing {
            self.contract_based_routing = Some(contract_based_routing)
        }
    }

    pub fn update_enabled_features(
        &mut self,
        algo_type: DynamicRoutingType,
        feature_to_enable: DynamicRoutingFeatures,
    ) {
        match algo_type {
            DynamicRoutingType::SuccessRateBasedRouting => {
                self.success_based_algorithm
                    .as_mut()
                    .map(|algo| algo.enabled_feature = feature_to_enable);
            }
            DynamicRoutingType::EliminationRouting => {
                self.elimination_routing_algorithm
                    .as_mut()
                    .map(|algo| algo.enabled_feature = feature_to_enable);
            }
            DynamicRoutingType::ContractBasedRouting => {
                self.contract_based_routing
                    .as_mut()
                    .map(|algo| algo.enabled_feature = feature_to_enable);
            }
        }
    }

    pub fn update_volume_split(&mut self, volume: Option<u8>) {
        self.dynamic_routing_volume_split = volume
    }

    pub fn update_merchant_creation_status_in_decision_engine(&mut self, is_created: bool) {
        self.is_merchant_created_in_decision_engine = is_created;
    }

    pub fn is_success_rate_routing_enabled(&self) -> bool {
        self.success_based_algorithm
            .as_ref()
            .map(|success_based_routing| {
                success_based_routing.enabled_feature
                    == DynamicRoutingFeatures::DynamicConnectorSelection
                    || success_based_routing.enabled_feature == DynamicRoutingFeatures::Metrics
            })
            .unwrap_or_default()
    }

    pub fn is_elimination_enabled(&self) -> bool {
        self.elimination_routing_algorithm
            .as_ref()
            .map(|elimination_routing| {
                elimination_routing.enabled_feature
                    == DynamicRoutingFeatures::DynamicConnectorSelection
                    || elimination_routing.enabled_feature == DynamicRoutingFeatures::Metrics
            })
            .unwrap_or_default()
    }
}

#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct RoutingVolumeSplit {
    pub routing_type: RoutingType,
    pub split: u8,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RoutingVolumeSplitWrapper {
    pub routing_info: RoutingVolumeSplit,
    pub profile_id: common_utils::id_type::ProfileId,
}

#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RoutingType {
    #[default]
    Static,
    Dynamic,
}

impl RoutingType {
    pub fn is_dynamic_routing(self) -> bool {
        self == Self::Dynamic
    }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SuccessBasedAlgorithm {
    pub algorithm_id_with_timestamp:
        DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId>,
    #[serde(default)]
    pub enabled_feature: DynamicRoutingFeatures,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ContractRoutingAlgorithm {
    pub algorithm_id_with_timestamp:
        DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId>,
    #[serde(default)]
    pub enabled_feature: DynamicRoutingFeatures,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EliminationRoutingAlgorithm {
    pub algorithm_id_with_timestamp:
        DynamicAlgorithmWithTimestamp<common_utils::id_type::RoutingId>,
    #[serde(default)]
    pub enabled_feature: DynamicRoutingFeatures,
}

impl EliminationRoutingAlgorithm {
    pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) {
        self.enabled_feature = feature_to_enable
    }
}

impl SuccessBasedAlgorithm {
    pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) {
        self.enabled_feature = feature_to_enable
    }
}

impl DynamicRoutingAlgorithmRef {
    pub fn update_algorithm_id(
        &mut self,
        new_id: common_utils::id_type::RoutingId,
        enabled_feature: DynamicRoutingFeatures,
        dynamic_routing_type: DynamicRoutingType,
    ) {
        match dynamic_routing_type {
            DynamicRoutingType::SuccessRateBasedRouting => {
                self.success_based_algorithm = Some(SuccessBasedAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)),
                    enabled_feature,
                })
            }
            DynamicRoutingType::EliminationRouting => {
                self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)),
                    enabled_feature,
                })
            }
            DynamicRoutingType::ContractBasedRouting => {
                self.contract_based_routing = Some(ContractRoutingAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)),
                    enabled_feature,
                })
            }
        };
    }

    pub fn update_feature(
        &mut self,
        enabled_feature: DynamicRoutingFeatures,
        dynamic_routing_type: DynamicRoutingType,
    ) {
        match dynamic_routing_type {
            DynamicRoutingType::SuccessRateBasedRouting => {
                self.success_based_algorithm = Some(SuccessBasedAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                    enabled_feature,
                })
            }
            DynamicRoutingType::EliminationRouting => {
                self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                    enabled_feature,
                })
            }
            DynamicRoutingType::ContractBasedRouting => {
                self.contract_based_routing = Some(ContractRoutingAlgorithm {
                    algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                    enabled_feature,
                })
            }
        };
    }

    pub fn disable_algorithm_id(&mut self, dynamic_routing_type: DynamicRoutingType) {
        match dynamic_routing_type {
            DynamicRoutingType::SuccessRateBasedRouting => {
                if let Some(success_based_algo) = &self.success_based_algorithm {
                    self.success_based_algorithm = Some(SuccessBasedAlgorithm {
                        algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                        enabled_feature: success_based_algo.enabled_feature,
                    });
                }
            }
            DynamicRoutingType::EliminationRouting => {
                if let Some(elimination_based_algo) = &self.elimination_routing_algorithm {
                    self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm {
                        algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                        enabled_feature: elimination_based_algo.enabled_feature,
                    });
                }
            }
            DynamicRoutingType::ContractBasedRouting => {
                if let Some(contract_based_algo) = &self.contract_based_routing {
                    self.contract_based_routing = Some(ContractRoutingAlgorithm {
                        algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None),
                        enabled_feature: contract_based_algo.enabled_feature,
                    });
                }
            }
        }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct ToggleDynamicRoutingQuery {
    pub enable: DynamicRoutingFeatures,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct CreateDynamicRoutingQuery {
    pub enable: DynamicRoutingFeatures,
}

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct DynamicRoutingVolumeSplitQuery {
    pub split: u8,
}

#[derive(
    Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum DynamicRoutingFeatures {
    Metrics,
    DynamicConnectorSelection,
    #[default]
    None,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct DynamicRoutingUpdateConfigQuery {
    #[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 ToggleDynamicRoutingWrapper {
    pub profile_id: common_utils::id_type::ProfileId,
    pub feature_to_enable: DynamicRoutingFeatures,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct ToggleDynamicRoutingPath {
    #[schema(value_type = String)]
    pub profile_id: common_utils::id_type::ProfileId,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateDynamicRoutingWrapper {
    pub profile_id: common_utils::id_type::ProfileId,
    pub feature_to_enable: DynamicRoutingFeatures,
    pub payload: DynamicRoutingPayload,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum DynamicRoutingPayload {
    SuccessBasedRoutingPayload(SuccessBasedRoutingConfig),
    EliminationRoutingPayload(EliminationRoutingConfig),
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct RoutingVolumeSplitResponse {
    pub split: u8,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct EliminationRoutingConfig {
    pub params: Option<Vec<DynamicRoutingConfigParams>>,
    pub elimination_analyser_config: Option<EliminationAnalyserConfig>,
    #[schema(value_type = DecisionEngineEliminationData)]
    pub decision_engine_configs: Option<open_router::DecisionEngineEliminationData>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct EliminationAnalyserConfig {
    pub bucket_size: Option<u64>,
    pub bucket_leak_interval_in_secs: Option<u64>,
}

impl EliminationAnalyserConfig {
    pub fn update(&mut self, new: Self) {
        if let Some(bucket_size) = new.bucket_size {
            self.bucket_size = Some(bucket_size)
        }
        if let Some(bucket_leak_interval_in_secs) = new.bucket_leak_interval_in_secs {
            self.bucket_leak_interval_in_secs = Some(bucket_leak_interval_in_secs)
        }
    }
}

impl Default for EliminationRoutingConfig {
    fn default() -> Self {
        Self {
            params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]),
            elimination_analyser_config: Some(EliminationAnalyserConfig {
                bucket_size: Some(5),
                bucket_leak_interval_in_secs: Some(60),
            }),
            decision_engine_configs: None,
        }
    }
}

impl EliminationRoutingConfig {
    pub fn update(&mut self, new: Self) {
        if let Some(params) = new.params {
            self.params = Some(params)
        }
        if let Some(new_config) = new.elimination_analyser_config {
            self.elimination_analyser_config
                .as_mut()
                .map(|config| config.update(new_config));
        }
        if let Some(new_config) = new.decision_engine_configs {
            self.decision_engine_configs
                .as_mut()
                .map(|config| config.update(new_config));
        }
    }

    pub fn open_router_config_default() -> Self {
        Self {
            elimination_analyser_config: None,
            params: None,
            decision_engine_configs: Some(open_router::DecisionEngineEliminationData {
                threshold: DEFAULT_ELIMINATION_THRESHOLD,
            }),
        }
    }

    pub fn get_decision_engine_configs(
        &self,
    ) -> Result<open_router::DecisionEngineEliminationData, error_stack::Report<ValidationError>>
    {
        self.decision_engine_configs
            .clone()
            .ok_or(error_stack::Report::new(
                ValidationError::MissingRequiredField {
                    field_name: "decision_engine_configs".to_string(),
                },
            ))
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct SuccessBasedRoutingConfig {
    pub params: Option<Vec<DynamicRoutingConfigParams>>,
    pub config: Option<SuccessBasedRoutingConfigBody>,
    #[schema(value_type = DecisionEngineSuccessRateData)]
    pub decision_engine_configs: Option<open_router::DecisionEngineSuccessRateData>,
}

impl Default for SuccessBasedRoutingConfig {
    fn default() -> Self {
        Self {
            params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]),
            config: Some(SuccessBasedRoutingConfigBody {
                min_aggregates_size: Some(5),
                default_success_rate: Some(100.0),
                max_aggregates_size: Some(8),
                current_block_threshold: Some(CurrentBlockThreshold {
                    duration_in_mins: None,
                    max_total_count: Some(5),
                }),
                specificity_level: SuccessRateSpecificityLevel::default(),
                exploration_percent: Some(20.0),
                shuffle_on_tie_during_exploitation: Some(false),
            }),
            decision_engine_configs: None,
        }
    }
}

#[derive(
    serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema, PartialEq, strum::Display,
)]
pub enum DynamicRoutingConfigParams {
    PaymentMethod,
    PaymentMethodType,
    AuthenticationType,
    Currency,
    Country,
    CardNetwork,
    CardBin,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
pub struct SuccessBasedRoutingConfigBody {
    pub min_aggregates_size: Option<u32>,
    pub default_success_rate: Option<f64>,
    pub max_aggregates_size: Option<u32>,
    pub current_block_threshold: Option<CurrentBlockThreshold>,
    #[serde(default)]
    pub specificity_level: SuccessRateSpecificityLevel,
    pub exploration_percent: Option<f64>,
    pub shuffle_on_tie_during_exploitation: Option<bool>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
pub struct CurrentBlockThreshold {
    pub duration_in_mins: Option<u64>,
    pub max_total_count: Option<u64>,
}


pub async fn fetch_consumer_tasks(
    db: &dyn ProcessTrackerInterface,
    redis_conn: &RedisConnectionPool,
    stream_name: &str,
    group_name: &str,
    consumer_name: &str,
) -> CustomResult<Vec<storage::ProcessTracker>, errors::ProcessTrackerError> {
    let batches = pt_utils::get_batches(redis_conn, stream_name, group_name, consumer_name).await?;

    // Returning early to avoid execution of database queries when `batches` is empty
    if batches.is_empty() {
        return Ok(Vec::new());
    }

    let mut tasks = batches.into_iter().fold(Vec::new(), |mut acc, batch| {
        acc.extend_from_slice(
            batch
                .trackers
                .into_iter()
                .filter(|task| task.is_valid_business_status(&valid_business_statuses()))
                .collect::<Vec<_>>()
                .as_slice(),
        );
        acc
    });
    let task_ids = tasks
        .iter()
        .map(|task| task.id.to_owned())
        .collect::<Vec<_>>();

    db.process_tracker_update_process_status_by_ids(
        task_ids,
        storage::ProcessTrackerUpdate::StatusUpdate {
            status: enums::ProcessTrackerStatus::ProcessStarted,
            business_status: None,
        },
    )
    .await
    .change_context(errors::ProcessTrackerError::ProcessFetchingFailed)?;
    tasks
        .iter_mut()
        .for_each(|x| x.status = enums::ProcessTrackerStatus::ProcessStarted);
    Ok(tasks)
}


OpenAPI Block Path: components.schemas.MerchantRecipientData
{
  "oneOf": [
    {
      "type": "object",
      "required": [
        "connector_recipient_id"
      ],
      "properties": {
        "connector_recipient_id": {
          "type": "string",
          "nullable": true
        }
      }
    },
    {
      "type": "object",
      "required": [
        "wallet_id"
      ],
      "properties": {
        "wallet_id": {
          "type": "string",
          "nullable": true
        }
      }
    },
    {
      "type": "object",
      "required": [
        "account_data"
      ],
      "properties": {
        "account_data": {
          "$ref": "#/components/schemas/MerchantAccountData"
        }
      }
    }
  ]
}


**Full Changelog:** [`2024.03.13.0...2024.03.13.1`](https://github.com/juspay/hyperswitch/compare/2024.03.13.0...2024.03.13.1)

- - -

## 2024.03.13.0

### Features

- **connector:** [AUTHORIZEDOTNET] Audit Connector ([#4035](https://github.com/juspay/hyperswitch/pull/4035)) ([`7840bdb`](https://github.com/juspay/hyperswitch/commit/7840bdb95f90065f3f6d671b07c3044e77740ed2))
- **core:** Confirm flow and authorization api changes for external authentication ([#4015](https://github.com/juspay/hyperswitch/pull/4015)) ([`ce3625c`](https://github.com/juspay/hyperswitch/commit/ce3625cb0cdccc750a073c012f0e541b014c3190))
- **global-search:** Dashboard globalsearch apis ([#3831](https://github.com/juspay/hyperswitch/pull/3831)) ([`ac8ddd4`](https://github.com/juspay/hyperswitch/commit/ac8ddd40208f3da5f65ca97bf5033cea5ca3ebe3))

### Bug Fixes

- **connector:** [Adyen] update config and add required fields ([#4046](https://github.com/juspay/hyperswitch/pull/4046)) ([`16d73cb`](https://github.com/juspay/hyperswitch/commit/16d73cb5f9f469d791f8880f3a2fd79135c821cd))
- **core:** [REFUNDS] Fix Not Supported Connector Error ([#4045](https://github.com/juspay/hyperswitch/pull/4045)) ([`7513423`](https://github.com/juspay/hyperswitch/commit/7513423631ddf0fe86ef656ec6cad76d82c807bc))

### Refactors

- **address:** Pass payment method billing to the connector module ([#3828](https://github.com/juspay/hyperswitch/pull/3828)) ([`195c700`](https://github.com/juspay/hyperswitch/commit/195c700e6c88e457cecc0722a7e5990db1379f22))
- **connector:** [Checkout] remove Paypal from wasm ([#4044](https://github.com/juspay/hyperswitch/pull/4044)) ([`3eff4eb`](https://github.com/juspay/hyperswitch/commit/3eff4ebd3a60b5831cbec0158527475c8f7d7eb4))
- **openai:** Update open-api spec to have payment changes ([#4043](https://github.com/juspay/hyperswitch/pull/4043)) ([`708cce9`](https://github.com/juspay/hyperswitch/commit/708cce926125a29b406db48cf0ebd35b217927d4))
- **payment_methods:**
  - Filter wallet payment method from mca based on customer pm ([#4038](https://github.com/juspay/hyperswitch/pull/4038)) ([`abe9c2a`](https://github.com/juspay/hyperswitch/commit/abe9c2ac17a0783f3625dd7fde5d28e285012ec3))
  - Allow deletion of default payment method for a customer if only one pm exists ([#4027](https://github.com/juspay/hyperswitch/pull/4027)) ([`45ed56f`](https://github.com/juspay/hyperswitch/commit/45ed56f16516c44acbe75b75c0621b78ccdb9894))
- [Checkout] change payment and webhooks API contract ([#4023](https://github.com/juspay/hyperswitch/pull/4023)) ([`733a560`](https://github.com/juspay/hyperswitch/commit/733a560146bb06e51fa4ee7ed9b6d1d3d9eddf12))

**Full Changelog:** [`2024.03.12.0...2024.03.13.0`](https://github.com/juspay/hyperswitch/compare/2024.03.12.0...2024.03.13.0)

- - -

## 2024.03.12.0

### Refactors

- **core:** Status handling for payment_method_status ([#3965](https://github.com/juspay/hyperswitch/pull/3965)) ([`e87f2ea`](https://github.com/juspay/hyperswitch/commit/e87f2ea8c5669473940df8bc2f5c61fdf3f218ff))

### Miscellaneous Tasks

- Add threedsecureio base url in deployment config files ([#4039](https://github.com/juspay/hyperswitch/pull/4039)) ([`d9f8423`](https://github.com/juspay/hyperswitch/commit/d9f84232a4a29814a1f9a792ebc74923862a1da6))

**Full Changelog:** [`2024.03.11.1...2024.03.12.0`](https://github.com/juspay/hyperswitch/compare/2024.03.11.1...2024.03.12.0)

- - -

## 2024.03.11.1

### Features

- **router:** Add routing support for token-based mit payments ([#4012](https://github.com/juspay/hyperswitch/pull/4012)) ([`43ebfbc`](https://github.com/juspay/hyperswitch/commit/43ebfbc47f03eaaaf274847290861dcb00db26a5))
- **users:** Implemented Set-Cookie ([#3865](https://github.com/juspay/hyperswitch/pull/3865)) ([`44eef46`](https://github.com/juspay/hyperswitch/commit/44eef46e5d7f0a198be80602ceae1c843449319c))

### Refactors

- **connector:**
  - [Multisafepay] Mask PII data ([#3869](https://github.com/juspay/hyperswitch/pull/3869)) ([`c2b1561`](https://github.com/juspay/hyperswitch/commit/c2b15615e3c61e6f497180be8fa66d008ed150bb))
  - [Globalpay] Mask PII data ([#3840](https://github.com/juspay/hyperswitch/pull/3840)) ([`13f6d6c`](https://github.com/juspay/hyperswitch/commit/13f6d6c10ce421329a7eb8b494fbb3bd31aed91f))
  - [Iatapay] Mask PII data ([#3850](https://github.com/juspay/hyperswitch/pull/3850)) ([`bd7accb`](https://github.com/juspay/hyperswitch/commit/bd7accb2c250b5f330b6bbb87f6f6edf4a479a61))
  - [Payme][Payeezy] Mask PII data ([#3926](https://github.com/juspay/hyperswitch/pull/3926)) ([`ffcb2bc`](https://github.com/juspay/hyperswitch/commit/ffcb2bcf2b7a26d8fc7fc45f9878d41ba74d2fe0))
  - [Nexinets] Mask PII data ([#3874](https://github.com/juspay/hyperswitch/pull/3874)) ([`9ea5310`](https://github.com/juspay/hyperswitch/commit/9ea531068d87b76e8f41ee7d9e9d26fd755bced4))
  - [Noon] Mask PII data ([#3879](https://github.com/juspay/hyperswitch/pull/3879)) ([`96efc2a`](https://github.com/juspay/hyperswitch/commit/96efc2abf94e3e9174f625bee2270236bad50278))
  - [stripe] capture error_code and error_message for psync ([#3771](https://github.com/juspay/hyperswitch/pull/3771)) ([`614182a`](https://github.com/juspay/hyperswitch/commit/614182ae4cdc7a762e0ce90d1336b1ff16fc9fa3))
  - [Trustpay][Volt] Mask PII data ([#3932](https://github.com/juspay/hyperswitch/pull/3932)) ([`a179b9c`](https://github.com/juspay/hyperswitch/commit/a179b9c90c2b9a419f1ce394d06158f80c29ee45))
  - [Nuvie] Mask PII data ([#3924](https://github.com/juspay/hyperswitch/pull/3924)) ([`6b2f71c`](https://github.com/juspay/hyperswitch/commit/6b2f71c850ff2ea36365375a81a7026fd8c87ebc))
  - [adyen] add more fields in the payments request ([#4010](https://github.com/juspay/hyperswitch/pull/4010)) ([`5584f11`](https://github.com/juspay/hyperswitch/commit/5584f1131ae4180020be23d4c735b8356482c22d))
- **core:** Updated payments response with payment_method_id & payment_method_status ([#3883](https://github.com/juspay/hyperswitch/pull/3883)) ([`7391416`](https://github.com/juspay/hyperswitch/commit/7391416e2473eab0474bd01bb155a9ecc96da263))

**Full Changelog:** [`2024.03.11.0...2024.03.11.1`](https://github.com/juspay/hyperswitch/compare/2024.03.11.0...2024.03.11.1)

- - -

## 2024.03.11.0

### Features

- **connector:**
  - Add threedsecureio three_ds authentication connector ([#4004](https://github.com/juspay/hyperswitch/pull/4004)) ([`06c3096`](https://github.com/juspay/hyperswitch/commit/06c30967cf626e7406aa9be8643fb73288aae383))
  - [Checkout] add support for external authentication for checkout connector ([#4006](https://github.com/juspay/hyperswitch/pull/4006)) ([`142a22c`](https://github.com/juspay/hyperswitch/commit/142a22c752a7c623cee62a6d552e6ffda73df777))
- **router:** Add payments authentication api flow ([#3996](https://github.com/juspay/hyperswitch/pull/3996)) ([`41556ba`](https://github.com/juspay/hyperswitch/commit/41556baed98c59373e0a053c023c32f2f7346b51))

**Full Changelog:** [`2024.03.09.0...2024.03.11.0`](https://github.com/juspay/hyperswitch/compare/2024.03.09.0...2024.03.11.0)

- - -

## 2024.03.09.0

### Features

- **core:** Add core functions for external authentication ([#3969](https://github.com/juspay/hyperswitch/pull/3969)) ([`897e264`](https://github.com/juspay/hyperswitch/commit/897e264ad9e26df9877a18eef26a24e05de78528))
- **payment_link:** Add shimmer page before payment_link loads starts ([#4014](https://github.com/juspay/hyperswitch/pull/4014)) ([`ba9d465`](https://github.com/juspay/hyperswitch/commit/ba9d465483edcefeacc7ace0fc8efc86ca0f813c))

### Bug Fixes

- **deserialization:** Error message is different when invalid data is passed for payment method data ([#4022](https://github.com/juspay/hyperswitch/pull/4022)) ([`f1fe295`](https://github.com/juspay/hyperswitch/commit/f1fe295475adb0e827bd713be036687da662b361))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`a7d0487`](https://github.com/juspay/hyperswitch/commit/a7d04873d63c1f007d0081f02ba9a373e24ae882))

**Full Changelog:** [`2024.03.08.0...2024.03.09.0`](https://github.com/juspay/hyperswitch/compare/2024.03.08.0...2024.03.09.0)

- - -

## 2024.03.08.0

### Features

- **router:** Add domain types, admin core changes and other prerequisites for 3ds external authentication flow ([#3962](https://github.com/juspay/hyperswitch/pull/3962)) ([`4902c40`](https://github.com/juspay/hyperswitch/commit/4902c403452500847f0395babc5fb940f4e2b755))

### Bug Fixes

- **deserialization:** Deserialize reward payment method data ([#4011](https://github.com/juspay/hyperswitch/pull/4011)) ([`f6b44f3`](https://github.com/juspay/hyperswitch/commit/f6b44f3860147a2ddc7b37123bfe064e50b7182a))
- **postman:** Fix postman collections for saving cards with customer_acceptance ([#4008](https://github.com/juspay/hyperswitch/pull/4008)) ([`deac899`](https://github.com/juspay/hyperswitch/commit/deac8991f78bd29d081088b0cf75a254eb358a2e))
- **webhooks:** Abort outgoing webhook retry task if webhook URL is not available in business profile ([#3997](https://github.com/juspay/hyperswitch/pull/3997)) ([`ce0ac3d`](https://github.com/juspay/hyperswitch/commit/ce0ac3d0297da5372772efe19167f0d2f62e82eb))

### Refactors

- **core:** Add OnSession as default for setup_future_usage ([#3990](https://github.com/juspay/hyperswitch/pull/3990)) ([`f9b6f5d`](https://github.com/juspay/hyperswitch/commit/f9b6f5da36c3a57da4b89db3151996403e2f3dfd))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`d36702d`](https://github.com/juspay/hyperswitch/commit/d36702d270be2b7e3816954fbac4a320d8224f31))

**Full Changelog:** [`2024.03.07.1...2024.03.08.0`](https://github.com/juspay/hyperswitch/compare/2024.03.07.1...2024.03.08.0)

- - -

## 2024.03.07.1

### Features

- **users:** Add new API get the user and role details of specific user ([#3988](https://github.com/juspay/hyperswitch/pull/3988)) ([`ba42fba`](https://github.com/juspay/hyperswitch/commit/ba42fbaed0adb2a3e4d9f2d07a4f0d99ba227241))

### Bug Fixes

- **users:** Revert using mget in authorization ([#3999](https://github.com/juspay/hyperswitch/pull/3999)) ([`7375b86`](https://github.com/juspay/hyperswitch/commit/7375b866a2a2767df2f213bc9eb61268392fb60d))

### Refactors

- **router:** Store `ApplepayPaymentMethod` in `payment_method_data` column of `payment_attempt` table ([#3940](https://github.com/juspay/hyperswitch/pull/3940)) ([`6671bff`](https://github.com/juspay/hyperswitch/commit/6671bff3b11e9548a0085046d2594cad9f2571e2))

**Full Changelog:** [`2024.03.07.0...2024.03.07.1`](https://github.com/juspay/hyperswitch/compare/2024.03.07.0...2024.03.07.1)

- - -

## 2024.03.07.0

### Features

- **connector:** [AUTHORIZEDOTNET] Add billing address in payments request ([#3981](https://github.com/juspay/hyperswitch/pull/3981)) ([`3806cd3`](https://github.com/juspay/hyperswitch/commit/3806cd35c763cc4517b761b4e3b0e736c60fac9f))
- **core:** Store customer_acceptance in the payment_methods table ([#3885](https://github.com/juspay/hyperswitch/pull/3885)) ([`a1fd36a`](https://github.com/juspay/hyperswitch/commit/a1fd36a1abea4d400386a00ccf182dfe9da5bcda))
- **payment_method:** Set the initial payment method as default until its explicitly set ([#3970](https://github.com/juspay/hyperswitch/pull/3970)) ([`34c1b90`](https://github.com/juspay/hyperswitch/commit/34c1b905b178973d2611bab14c7d85582ed225f0))
- **payment_methods:** Store connector_mandate_details in PaymentMethods table ([#3907](https://github.com/juspay/hyperswitch/pull/3907)) ([`d220e81`](https://github.com/juspay/hyperswitch/commit/d220e815dc81925b205fb57d5d4f05883c1a7cde))

### Bug Fixes

- **connector:**
  - [Trustpay] Add mapping to error code `100.390.105` ([#3968](https://github.com/juspay/hyperswitch/pull/3968)) ([`bf67587`](https://github.com/juspay/hyperswitch/commit/bf675878a2e36f7005468e91eefadc111ccba6b2))
  - [adyen] handle Webhook reference and object ([#3976](https://github.com/juspay/hyperswitch/pull/3976)) ([`0aa40cb`](https://github.com/juspay/hyperswitch/commit/0aa40cbae75fd4cf5b13cfc518ff761b2b673246))
- **tests/postman/adyen:** Remove enabled payment methods for payouts processor ([#3913](https://github.com/juspay/hyperswitch/pull/3913)) ([`289b20a`](https://github.com/juspay/hyperswitch/commit/289b20a82e5ee32aae6eb4e5766f9c757d26345d))
- **user:**
  - Use mget to check in blocklist ([#3945](https://github.com/juspay/hyperswitch/pull/3945)) ([`8154a61`](https://github.com/juspay/hyperswitch/commit/8154a611efcfa4bef3d5674db0574b065b55e9cd))
  - Improve role validation to prevent duplicate groups ([#3949](https://github.com/juspay/hyperswitch/pull/3949)) ([`05a4752`](https://github.com/juspay/hyperswitch/commit/05a475271a2c37ba6ced90b85e53015c47d573bc))

### Refactors

- **connector:** [Checkout] handle default cases for dispute status mapping ([#3966](https://github.com/juspay/hyperswitch/pull/3966)) ([`2cda3dd`](https://github.com/juspay/hyperswitch/commit/2cda3dd794e51f84537a89e1015ee975322a2081))
- **payment_methods:**
  - Filter applepay payment method from mca based on customer pm ([#3953](https://github.com/juspay/hyperswitch/pull/3953)) ([`2db39e8`](https://github.com/juspay/hyperswitch/commit/2db39e8bb9af3d55e3d075d77ff8616ee2e15f0a))
  - Prevent deletion of default payment method for a customer ([#3964](https://github.com/juspay/hyperswitch/pull/3964)) ([`db39bb0`](https://github.com/juspay/hyperswitch/commit/db39bb0a3cf350e8399a7f17842d9af9b2de440e))
  - Insert payment_method_id in redis for wallet tokens ([#3989](https://github.com/juspay/hyperswitch/pull/3989)) ([`d997e29`](https://github.com/juspay/hyperswitch/commit/d997e298f2614079a72a773493cd98ba4507b35a))
- Kms decrypt analytics config ([#3984](https://github.com/juspay/hyperswitch/pull/3984)) ([`cfade55`](https://github.com/juspay/hyperswitch/commit/cfade55e693594a772c18eee2c35d7b3dc03f84d))

### Miscellaneous Tasks

- **doc:** Add API ref for KV toggle ([#3784](https://github.com/juspay/hyperswitch/pull/3784)) ([`5e8fcda`](https://github.com/juspay/hyperswitch/commit/5e8fcda7d12f482e47be9ed672093cb45fac9e29))
- **postman:** Update Postman collection files ([`2db4a59`](https://github.com/juspay/hyperswitch/commit/2db4a599eae2560bfef327231f2381af74145e39))

**Full Changelog:** [`2024.03.06.0...2024.03.07.0`](https://github.com/juspay/hyperswitch/compare/2024.03.06.0...2024.03.07.0)

- - -

## 2024.03.06.0

### Features

- **api_models:** Add api_models for external 3ds authentication flow ([#3858](https://github.com/juspay/hyperswitch/pull/3858)) ([`0a43ceb`](https://github.com/juspay/hyperswitch/commit/0a43ceb14e27d998794941ecb7605b9e7175c757))
- **connector:** [Checkout] accept connector_transaction_id in 2xx and 4xx error_response of connector flows ([#3959](https://github.com/juspay/hyperswitch/pull/3959)) ([`f6f6a0c`](https://github.com/juspay/hyperswitch/commit/f6f6a0c0f727a6f367c6bafb4db9a89cb46f667a))
- **core:** External authentication related schema changes for existing tables ([#3904](https://github.com/juspay/hyperswitch/pull/3904)) ([`c09b2b3`](https://github.com/juspay/hyperswitch/commit/c09b2b3a2ae9a71d4a73063faf4796e0c8732bb4))
- **payouts:** Implement Single Connector Retry for Payouts ([#3908](https://github.com/juspay/hyperswitch/pull/3908)) ([`0cb95a4`](https://github.com/juspay/hyperswitch/commit/0cb95a4911054e089e6ed3c528645ee1b881ebc6))
- **roles:** Add caching for custom roles ([#3946](https://github.com/juspay/hyperswitch/pull/3946)) ([`19c5023`](https://github.com/juspay/hyperswitch/commit/19c502398f980d20b9e0a4fe98c33a2239c90c5b))
- **router:** Add incoming header request logs ([#3939](https://github.com/juspay/hyperswitch/pull/3939)) ([`050df50`](https://github.com/juspay/hyperswitch/commit/050df5022cd3d44db23ca75f81158fb7c2429f86))

### Bug Fixes

- **core:** Fix metadata validation for update payment connector ([#3834](https://github.com/juspay/hyperswitch/pull/3834)) ([`54938ad`](https://github.com/juspay/hyperswitch/commit/54938ad345a2b899360b608d8845fd7f885f82ba))
- **router:** [nuvei] Nuvei error handling for payment declined status and included tests ([#3832](https://github.com/juspay/hyperswitch/pull/3832)) ([`087932f`](https://github.com/juspay/hyperswitch/commit/087932f06044454570c971def0e82dc3d838598c))

### Refactors

- **connector:**
  - [Fiserv] Mask PII data ([#3821](https://github.com/juspay/hyperswitch/pull/3821)) ([`03cfb73`](https://github.com/juspay/hyperswitch/commit/03cfb735af29f00bccf729013e7e06684611b30d))
  - Remove default cases for Authorizedotnet, Braintree and Fiserv Connector ([#2796](https://github.com/juspay/hyperswitch/pull/2796)) ([`dbac556`](https://github.com/juspay/hyperswitch/commit/dbac55683a8f95e0efdbac43f8c2ae793063a032))

### Miscellaneous Tasks

- **configs:** [BOA] Add USD Currency Filter Configuration ([#3961](https://github.com/juspay/hyperswitch/pull/3961)) ([`8a0e468`](https://github.com/juspay/hyperswitch/commit/8a0e468e6a574717b29ccbdd143908727c251dfb))
- **postman:** Update Postman collection files ([`6305bb5`](https://github.com/juspay/hyperswitch/commit/6305bb57269fb5f6803edbad58d6e574ad4f6509))
- **tests:** Add unit tests for backwards compatibility ([#3822](https://github.com/juspay/hyperswitch/pull/3822)) ([`c65729a`](https://github.com/juspay/hyperswitch/commit/c65729adc9009f046398312a16841532fdc177da))

**Full Changelog:** [`2024.03.05.0...2024.03.06.0`](https://github.com/juspay/hyperswitch/compare/2024.03.05.0...2024.03.06.0)

- - -

## 2024.03.05.0

### Features

- **connector:** [PLACETOPAY] Fix refund request and status mapping ([#3894](https://github.com/juspay/hyperswitch/pull/3894)) ([`5eff9d4`](https://github.com/juspay/hyperswitch/commit/5eff9d47d3e53d380ef792a8fbdf06ecf78d3d16))
- **webhooks:** Implement automatic retries for failed webhook deliveries using scheduler ([#3842](https://github.com/juspay/hyperswitch/pull/3842)) ([`5bb67c7`](https://github.com/juspay/hyperswitch/commit/5bb67c7dcc22f9cee51adf501bdd8455b41548db))

### Bug Fixes


pub struct ThreedsecureioPreAuthenticationRequest {
    acct_number: cards::CardNumber,
    ds: Option<DirectoryServer>,
}


pub fn get_captures_count(&self) -> RouterResult<i16> {
        i16::try_from(self.all_captures.len())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while converting from usize to i16")
    }


pub struct CybersourceErrorInformationResponse {
    id: String,
    error_information: CybersourceErrorInformation,
}


pub async fn generate_jwt(
        self,
        state: &SessionState,
        next_flow: &NextFlow,
        user_role: &UserRole,
    ) -> UserResult<Secret<String>> {
        let user_id = next_flow.user.get_user_id();
        // Fetch lineage context from DB
        let lineage_context_from_db = state
            .global_store
            .find_user_by_id(user_id)
            .await
            .inspect_err(|e| {
                logger::error!(
                    "Failed to fetch lineage context from DB for user {}: {:?}",
                    user_id,
                    e
                )
            })
            .ok()
            .and_then(|user| user.lineage_context);

        let new_lineage_context = match lineage_context_from_db {
            Some(ctx) => {
                let tenant_id = ctx.tenant_id.clone();
                let user_role_match_v2 = state
                    .global_store
                    .find_user_role_by_user_id_and_lineage(
                        &ctx.user_id,
                        &tenant_id,
                        &ctx.org_id,
                        &ctx.merchant_id,
                        &ctx.profile_id,
                        UserRoleVersion::V2,
                    )
                    .await
                    .inspect_err(|e| {
                        logger::error!("Failed to validate V2 role: {e:?}");
                    })
                    .map(|role| role.role_id == ctx.role_id)
                    .unwrap_or_default();

                if user_role_match_v2 {
                    ctx
                } else {
                    let user_role_match_v1 = state
                        .global_store
                        .find_user_role_by_user_id_and_lineage(
                            &ctx.user_id,
                            &tenant_id,
                            &ctx.org_id,
                            &ctx.merchant_id,
                            &ctx.profile_id,
                            UserRoleVersion::V1,
                        )
                        .await
                        .inspect_err(|e| {
                            logger::error!("Failed to validate V1 role: {e:?}");
                        })
                        .map(|role| role.role_id == ctx.role_id)
                        .unwrap_or_default();

                    if user_role_match_v1 {
                        ctx
                    } else {
                        // fallback to default lineage if cached context is invalid
                        Self::resolve_lineage_from_user_role(state, user_role, user_id).await?
                    }
                }
            }
            None =>
            // no cached context found
            {
                Self::resolve_lineage_from_user_role(state, user_role, user_id).await?
            }
        };

        utils::user::spawn_async_lineage_context_update_to_db(
            state,
            user_id,
            new_lineage_context.clone(),
        );

        auth::AuthToken::new_token(
            new_lineage_context.user_id,
            new_lineage_context.merchant_id,
            new_lineage_context.role_id,
            &state.conf,
            new_lineage_context.org_id,
            new_lineage_context.profile_id,
            Some(new_lineage_context.tenant_id),
        )
        .await
        .map(|token| token.into())
    }


pub struct TwoFactorStatus {
    pub status: Option<TwoFactorAuthStatusResponseWithAttempts>,
    pub is_skippable: bool,
}


impl RedisConnInterface for RedisStore {
    fn get_redis_conn(
        &self,
    ) -> error_stack::Result<
        Arc<redis_interface::RedisConnectionPool>,
        redis_interface::errors::RedisError,
    > {
        if self
            .redis_conn
            .is_redis_available
            .load(atomic::Ordering::SeqCst)
        {
            Ok(self.redis_conn.clone())
        } else {
            Err(redis_interface::errors::RedisError::RedisConnectionError.into())
        }
    }
}

impl RedisConnInterface for for RedisStore


impl NewUserRole<NoLevel> {
    pub fn add_entity<T>(self, entity: T) -> NewUserRole<T>
    where
        T: Clone,
    {
        NewUserRole {
            entity,
            user_id: self.user_id,
            role_id: self.role_id,
            status: self.status,
            created_by: self.created_by,
            last_modified_by: self.last_modified_by,
            created_at: self.created_at,
            last_modified: self.last_modified,
        }
    }
}


pub struct TotpSecret {
    pub secret: Secret<String>,
    pub totp_url: Secret<String>,
}


pub fn resource(&self) -> Resource {
                match self {
                    #(#resource_impl_per),*
                }
            }


pub struct Card {
    card_holder_name: Secret<String>,
    card_number: CardNumber,
    expiry_date: Secret<String>,
}


File: crates/diesel_models/src/query/business_profile.rs
 Public functions: 7

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table};

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::business_profile::dsl::{self, profile_id as dsl_identifier};
#[cfg(feature = "v2")]
use crate::schema_v2::business_profile::dsl::{self, id as dsl_identifier};
use crate::{
    business_profile::{Profile, ProfileNew, ProfileUpdateInternal},
    errors, PgPooledConn, StorageResult,
};

impl ProfileNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Profile> {
        generics::generic_insert(conn, self).await
    }
}

impl Profile {
    pub async fn update_by_profile_id(
        self,
        conn: &PgPooledConn,
        business_profile: ProfileUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.get_id().to_owned(),
            business_profile,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn find_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl_identifier.eq(profile_id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id_profile_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl_identifier.eq(profile_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_profile_name_merchant_id(
        conn: &PgPooledConn,
        profile_name: &str,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::profile_name
                .eq(profile_name.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }

    pub async fn list_profile_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id.eq(merchant_id.to_owned()),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn delete_by_profile_id_merchant_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl_identifier
                .eq(profile_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }
}


pub struct PaymentAttemptUpdateMems {
    pub orig: PaymentAttempt,
    pub update_data: PaymentAttemptUpdateInternal,
}