File: crates/router/src/types/storage/revenue_recovery_redis_operation.rs
 Public functions: 15
 Public structs: 5

use std::collections::HashMap;

use common_enums::enums::CardNetwork;
use common_utils::{date_time, errors::CustomResult, id_type};
use error_stack::ResultExt;
use masking::Secret;
use redis_interface::{DelReply, SetnxReply};
use router_env::{instrument, logger, tracing};
use serde::{Deserialize, Serialize};
use time::{Date, Duration, OffsetDateTime, PrimitiveDateTime};

use crate::{db::errors, types::storage::enums::RevenueRecoveryAlgorithmType, SessionState};

// Constants for retry window management
const RETRY_WINDOW_DAYS: i32 = 30;
const INITIAL_RETRY_COUNT: i32 = 0;

/// Payment processor token details including card information
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct PaymentProcessorTokenDetails {
    pub payment_processor_token: String,
    pub expiry_month: Option<Secret<String>>,
    pub expiry_year: Option<Secret<String>>,
    pub card_issuer: Option<String>,
    pub last_four_digits: Option<String>,
    pub card_network: Option<CardNetwork>,
    pub card_type: Option<String>,
}

/// Represents the status and retry history of a payment processor token
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentProcessorTokenStatus {
    /// Payment processor token details including card information and token ID
    pub payment_processor_token_details: PaymentProcessorTokenDetails,
    /// Payment intent ID that originally inserted this token
    pub inserted_by_attempt_id: id_type::GlobalAttemptId,
    /// Error code associated with the token failure
    pub error_code: Option<String>,
    /// Daily retry count history for the last 30 days (date -> retry_count)
    pub daily_retry_history: HashMap<Date, i32>,
    /// Scheduled time for the next retry attempt
    pub scheduled_at: Option<PrimitiveDateTime>,
    /// Indicates if the token is a hard decline (no retries allowed)
    pub is_hard_decline: Option<bool>,
}

/// Token retry availability information with detailed wait times
#[derive(Debug, Clone)]
pub struct TokenRetryInfo {
    pub monthly_wait_hours: i64,   // Hours to wait for 30-day limit reset
    pub daily_wait_hours: i64,     // Hours to wait for daily limit reset
    pub total_30_day_retries: i32, // Current total retry count in 30-day window
}

/// Complete token information with retry limits and wait times
#[derive(Debug, Clone)]
pub struct PaymentProcessorTokenWithRetryInfo {
    /// The complete token status information
    pub token_status: PaymentProcessorTokenStatus,
    /// Hours to wait before next retry attempt (max of daily/monthly wait)
    pub retry_wait_time_hours: i64,
    /// Number of retries remaining in the 30-day rolling window
    pub monthly_retry_remaining: i32,
}

/// Redis-based token management struct
pub struct RedisTokenManager;

impl RedisTokenManager {
    /// Lock connector customer
    #[instrument(skip_all)]
    pub async fn lock_connector_customer_status(
        state: &SessionState,
        connector_customer_id: &str,
        payment_id: &id_type::GlobalPaymentId,
    ) -> CustomResult<bool, errors::StorageError> {
        let redis_conn =
            state
                .store
                .get_redis_conn()
                .change_context(errors::StorageError::RedisError(
                    errors::RedisError::RedisConnectionError.into(),
                ))?;

        let lock_key = format!("customer:{connector_customer_id}:status");
        let seconds = &state.conf.revenue_recovery.redis_ttl_in_seconds;

        let result: bool = match redis_conn
            .set_key_if_not_exists_with_expiry(
                &lock_key.into(),
                payment_id.get_string_repr(),
                Some(*seconds),
            )
            .await
        {
            Ok(resp) => resp == SetnxReply::KeySet,
            Err(error) => {
                tracing::error!(operation = "lock_stream", err = ?error);
                false
            }
        };

        tracing::debug!(
            connector_customer_id = connector_customer_id,
            payment_id = payment_id.get_string_repr(),
            lock_acquired = %result,
            "Connector customer lock attempt"
        );

        Ok(result)
    }

    /// Unlock connector customer status
    #[instrument(skip_all)]
    pub async fn unlock_connector_customer_status(
        state: &SessionState,
        connector_customer_id: &str,
    ) -> CustomResult<bool, errors::StorageError> {
        let redis_conn =
            state
                .store
                .get_redis_conn()
                .change_context(errors::StorageError::RedisError(
                    errors::RedisError::RedisConnectionError.into(),
                ))?;

        let lock_key = format!("customer:{connector_customer_id}:status");

        match redis_conn.delete_key(&lock_key.into()).await {
            Ok(DelReply::KeyDeleted) => {
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "Connector customer unlocked"
                );
                Ok(true)
            }
            Ok(DelReply::KeyNotDeleted) => {
                tracing::debug!("Tried to unlock a stream which is already unlocked");
                Ok(false)
            }
            Err(err) => {
                tracing::error!(?err, "Failed to delete lock key");
                Ok(false)
            }
        }
    }

    /// Get all payment processor tokens for a connector customer
    #[instrument(skip_all)]
    pub async fn get_connector_customer_payment_processor_tokens(
        state: &SessionState,
        connector_customer_id: &str,
    ) -> CustomResult<HashMap<String, PaymentProcessorTokenStatus>, errors::StorageError> {
        let redis_conn =
            state
                .store
                .get_redis_conn()
                .change_context(errors::StorageError::RedisError(
                    errors::RedisError::RedisConnectionError.into(),
                ))?;
        let tokens_key = format!("customer:{connector_customer_id}:tokens");

        let get_hash_err =
            errors::StorageError::RedisError(errors::RedisError::GetHashFieldFailed.into());

        let payment_processor_tokens: HashMap<String, String> = redis_conn
            .get_hash_fields(&tokens_key.into())
            .await
            .change_context(get_hash_err)?;

        // build the result map using iterator adapters (explicit match preserved for logging)
        let payment_processor_token_info_map: HashMap<String, PaymentProcessorTokenStatus> =
            payment_processor_tokens
                .into_iter()
                .filter_map(|(token_id, token_data)| {
                    match serde_json::from_str::<PaymentProcessorTokenStatus>(&token_data) {
                        Ok(token_status) => Some((token_id, token_status)),
                        Err(err) => {
                            tracing::warn!(
                                connector_customer_id = %connector_customer_id,
                                token_id = %token_id,
                                error = %err,
                                "Failed to deserialize token data, skipping",
                            );
                            None
                        }
                    }
                })
                .collect();
        tracing::debug!(
            connector_customer_id = connector_customer_id,
            "Fetched payment processor tokens",
        );

        Ok(payment_processor_token_info_map)
    }

    /// Update connector customer payment processor tokens or add if doesn't exist
    #[instrument(skip_all)]
    pub async fn update_or_add_connector_customer_payment_processor_tokens(
        state: &SessionState,
        connector_customer_id: &str,
        payment_processor_token_info_map: HashMap<String, PaymentProcessorTokenStatus>,
    ) -> CustomResult<(), errors::StorageError> {
        let redis_conn =
            state
                .store
                .get_redis_conn()
                .change_context(errors::StorageError::RedisError(
                    errors::RedisError::RedisConnectionError.into(),
                ))?;
        let tokens_key = format!("customer:{connector_customer_id}:tokens");

        // allocate capacity up-front to avoid rehashing
        let mut serialized_payment_processor_tokens: HashMap<String, String> =
            HashMap::with_capacity(payment_processor_token_info_map.len());

        // serialize all tokens, preserving explicit error handling and attachable diagnostics
        for (payment_processor_token_id, payment_processor_token_status) in
            payment_processor_token_info_map
        {
            let serialized = serde_json::to_string(&payment_processor_token_status)
                .change_context(errors::StorageError::SerializationFailed)
                .attach_printable("Failed to serialize token status")?;

            serialized_payment_processor_tokens.insert(payment_processor_token_id, serialized);
        }
        let seconds = &state.conf.revenue_recovery.redis_ttl_in_seconds;

        // Update or add tokens
        redis_conn
            .set_hash_fields(
                &tokens_key.into(),
                serialized_payment_processor_tokens,
                Some(*seconds),
            )
            .await
            .change_context(errors::StorageError::RedisError(
                errors::RedisError::SetHashFieldFailed.into(),
            ))?;

        tracing::info!(
            connector_customer_id = %connector_customer_id,
            "Successfully updated or added customer tokens",
        );

        Ok(())
    }

    /// Get current date in `yyyy-mm-dd` format.
    pub fn get_current_date() -> String {
        let today = date_time::now().date();

        let (year, month, day) = (today.year(), today.month(), today.day());

        format!("{year:04}-{month:02}-{day:02}",)
    }

    /// Normalize retry window to exactly `RETRY_WINDOW_DAYS` days (today to `RETRY_WINDOW_DAYS - 1` days ago).
    pub fn normalize_retry_window(
        payment_processor_token: &mut PaymentProcessorTokenStatus,
        today: Date,
    ) {
        let mut normalized_retry_history: HashMap<Date, i32> = HashMap::new();

        for days_ago in 0..RETRY_WINDOW_DAYS {
            let date = today - Duration::days(days_ago.into());

            payment_processor_token
                .daily_retry_history
                .get(&date)
                .map(|&retry_count| {
                    normalized_retry_history.insert(date, retry_count);
                });
        }

        payment_processor_token.daily_retry_history = normalized_retry_history;
    }

    /// Get all payment processor tokens with retry information and wait times.
    pub fn get_tokens_with_retry_metadata(
        state: &SessionState,
        payment_processor_token_info_map: &HashMap<String, PaymentProcessorTokenStatus>,
    ) -> HashMap<String, PaymentProcessorTokenWithRetryInfo> {
        let today = OffsetDateTime::now_utc().date();
        let card_config = &state.conf.revenue_recovery.card_config;

        let mut result: HashMap<String, PaymentProcessorTokenWithRetryInfo> =
            HashMap::with_capacity(payment_processor_token_info_map.len());

        for (payment_processor_token_id, payment_processor_token_status) in
            payment_processor_token_info_map.iter()
        {
            let card_network = payment_processor_token_status
                .payment_processor_token_details
                .card_network
                .clone();

            // Calculate retry information.
            let retry_info = Self::payment_processor_token_retry_info(
                state,
                payment_processor_token_status,
                today,
                card_network.clone(),
            );

            // Determine the wait time (max of monthly and daily wait hours).
            let retry_wait_time_hours = retry_info
                .monthly_wait_hours
                .max(retry_info.daily_wait_hours);

            // Obtain network-specific limits and compute remaining monthly retries.
            let card_network_config = card_config.get_network_config(card_network);

            let monthly_retry_remaining = card_network_config
                .max_retry_count_for_thirty_day
                .saturating_sub(retry_info.total_30_day_retries);

            // Build the per-token result struct.
            let token_with_retry_info = PaymentProcessorTokenWithRetryInfo {
                token_status: payment_processor_token_status.clone(),
                retry_wait_time_hours,
                monthly_retry_remaining,
            };

            result.insert(payment_processor_token_id.clone(), token_with_retry_info);
        }
        tracing::debug!("Fetched payment processor tokens with retry metadata",);

        result
    }

    /// Sum retries over exactly the last 30 days
    fn calculate_total_30_day_retries(token: &PaymentProcessorTokenStatus, today: Date) -> i32 {
        (0..RETRY_WINDOW_DAYS)
            .map(|i| {
                let date = today - Duration::days(i.into());
                token
                    .daily_retry_history
                    .get(&date)
                    .copied()
                    .unwrap_or(INITIAL_RETRY_COUNT)
            })
            .sum()
    }

    /// Calculate wait hours
    fn calculate_wait_hours(target_date: Date, now: OffsetDateTime) -> i64 {
        let expiry_time = target_date.midnight().assume_utc();
        (expiry_time - now).whole_hours().max(0)
    }

    /// Calculate retry counts for exactly the last 30 days
    pub fn payment_processor_token_retry_info(
        state: &SessionState,
        token: &PaymentProcessorTokenStatus,
        today: Date,
        network_type: Option<CardNetwork>,
    ) -> TokenRetryInfo {
        let card_config = &state.conf.revenue_recovery.card_config;
        let card_network_config = card_config.get_network_config(network_type);

        let now = OffsetDateTime::now_utc();

        let total_30_day_retries = Self::calculate_total_30_day_retries(token, today);

        let monthly_wait_hours =
            if total_30_day_retries >= card_network_config.max_retry_count_for_thirty_day {
                (0..RETRY_WINDOW_DAYS)
                    .map(|i| today - Duration::days(i.into()))
                    .find(|date| token.daily_retry_history.get(date).copied().unwrap_or(0) > 0)
                    .map(|date| Self::calculate_wait_hours(date + Duration::days(31), now))
                    .unwrap_or(0)
            } else {
                0
            };

        let today_retries = token
            .daily_retry_history
            .get(&today)
            .copied()
            .unwrap_or(INITIAL_RETRY_COUNT);

        let daily_wait_hours = if today_retries >= card_network_config.max_retries_per_day {
            Self::calculate_wait_hours(today + Duration::days(1), now)
        } else {
            0
        };

        TokenRetryInfo {
            monthly_wait_hours,
            daily_wait_hours,
            total_30_day_retries,
        }
    }

    // Upsert payment processor token
    #[instrument(skip_all)]
    pub async fn upsert_payment_processor_token(
        state: &SessionState,
        connector_customer_id: &str,
        token_data: PaymentProcessorTokenStatus,
    ) -> CustomResult<bool, errors::StorageError> {
        let mut token_map =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?;

        let token_id = token_data
            .payment_processor_token_details
            .payment_processor_token
            .clone();

        let was_existing = token_map.contains_key(&token_id);

        let error_code = token_data.error_code.clone();
        let today = OffsetDateTime::now_utc().date();

        token_map
            .get_mut(&token_id)
            .map(|existing_token| {
                error_code.map(|err| existing_token.error_code = Some(err));

                Self::normalize_retry_window(existing_token, today);

                for (date, &value) in &token_data.daily_retry_history {
                    existing_token
                        .daily_retry_history
                        .entry(*date)
                        .and_modify(|v| *v += value)
                        .or_insert(value);
                }
            })
            .or_else(|| {
                token_map.insert(token_id.clone(), token_data);
                None
            });

        Self::update_or_add_connector_customer_payment_processor_tokens(
            state,
            connector_customer_id,
            token_map,
        )
        .await?;
        tracing::debug!(
            connector_customer_id = connector_customer_id,
            "Upsert payment processor tokens",
        );

        Ok(!was_existing)
    }

    // Update payment processor token error code with billing connector response
    #[instrument(skip_all)]
    pub async fn update_payment_processor_token_error_code_from_process_tracker(
        state: &SessionState,
        connector_customer_id: &str,
        error_code: &Option<String>,
        is_hard_decline: &Option<bool>,
        payment_processor_token_id: Option<&str>,
    ) -> CustomResult<bool, errors::StorageError> {
        let today = OffsetDateTime::now_utc().date();
        let updated_token = match payment_processor_token_id {
            Some(token_id) => {
                Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                    .await?
                    .values()
                    .find(|status| {
                        status
                            .payment_processor_token_details
                            .payment_processor_token
                            == token_id
                    })
                    .map(|status| PaymentProcessorTokenStatus {
                        payment_processor_token_details: status
                            .payment_processor_token_details
                            .clone(),
                        inserted_by_attempt_id: status.inserted_by_attempt_id.clone(),
                        error_code: error_code.clone(),
                        daily_retry_history: status.daily_retry_history.clone(),
                        scheduled_at: None,
                        is_hard_decline: *is_hard_decline,
                    })
            }
            None => None,
        };

        match updated_token {
            Some(mut token) => {
                Self::normalize_retry_window(&mut token, today);

                match token.error_code {
                    None => token.daily_retry_history.clear(),
                    Some(_) => {
                        let current_count = token
                            .daily_retry_history
                            .get(&today)
                            .copied()
                            .unwrap_or(INITIAL_RETRY_COUNT);
                        token.daily_retry_history.insert(today, current_count + 1);
                    }
                }

                let mut tokens_map = HashMap::new();
                tokens_map.insert(
                    token
                        .payment_processor_token_details
                        .payment_processor_token
                        .clone(),
                    token.clone(),
                );

                Self::update_or_add_connector_customer_payment_processor_tokens(
                    state,
                    connector_customer_id,
                    tokens_map,
                )
                .await?;
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "Updated payment processor tokens with error code",
                );
                Ok(true)
            }
            None => {
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "No Token found with token id to update error code",
                );
                Ok(false)
            }
        }
    }

    // Update payment processor token schedule time
    #[instrument(skip_all)]
    pub async fn update_payment_processor_token_schedule_time(
        state: &SessionState,
        connector_customer_id: &str,
        payment_processor_token: &str,
        schedule_time: Option<PrimitiveDateTime>,
    ) -> CustomResult<bool, errors::StorageError> {
        let updated_token =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?
                .values()
                .find(|status| {
                    status
                        .payment_processor_token_details
                        .payment_processor_token
                        == payment_processor_token
                })
                .map(|status| PaymentProcessorTokenStatus {
                    payment_processor_token_details: status.payment_processor_token_details.clone(),
                    inserted_by_attempt_id: status.inserted_by_attempt_id.clone(),
                    error_code: status.error_code.clone(),
                    daily_retry_history: status.daily_retry_history.clone(),
                    scheduled_at: schedule_time,
                    is_hard_decline: status.is_hard_decline,
                });

        match updated_token {
            Some(token) => {
                let mut tokens_map = HashMap::new();
                tokens_map.insert(
                    token
                        .payment_processor_token_details
                        .payment_processor_token
                        .clone(),
                    token.clone(),
                );
                Self::update_or_add_connector_customer_payment_processor_tokens(
                    state,
                    connector_customer_id,
                    tokens_map,
                )
                .await?;
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "Updated payment processor tokens with schedule time",
                );
                Ok(true)
            }
            None => {
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "payment processor tokens with not found",
                );
                Ok(false)
            }
        }
    }

    // Get payment processor token with schedule time
    #[instrument(skip_all)]
    pub async fn get_payment_processor_token_with_schedule_time(
        state: &SessionState,
        connector_customer_id: &str,
    ) -> CustomResult<Option<PaymentProcessorTokenStatus>, errors::StorageError> {
        let tokens =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?;

        let scheduled_token = tokens
            .values()
            .find(|status| status.scheduled_at.is_some())
            .cloned();

        tracing::debug!(
            connector_customer_id = connector_customer_id,
            "Fetched payment processor token with schedule time",
        );

        Ok(scheduled_token)
    }

    // Get payment processor token using token id
    #[instrument(skip_all)]
    pub async fn get_payment_processor_token_using_token_id(
        state: &SessionState,
        connector_customer_id: &str,
        payment_processor_token: &str,
    ) -> CustomResult<Option<PaymentProcessorTokenStatus>, errors::StorageError> {
        // Get all tokens for the customer
        let tokens_map =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?;
        let token_details = tokens_map.get(payment_processor_token).cloned();

        tracing::debug!(
            token_found = token_details.is_some(),
            customer_id = connector_customer_id,
            "Fetched payment processor token & Checked existence ",
        );

        Ok(token_details)
    }

    // Check if all tokens are hard declined or no token found for the customer
    #[instrument(skip_all)]
    pub async fn are_all_tokens_hard_declined(
        state: &SessionState,
        connector_customer_id: &str,
    ) -> CustomResult<bool, errors::StorageError> {
        let tokens_map =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?;
        let all_hard_declined = tokens_map
            .values()
            .all(|token| token.is_hard_decline.unwrap_or(false));

        tracing::debug!(
            connector_customer_id = connector_customer_id,
            all_hard_declined,
            "Checked if all tokens are hard declined or no token found for the customer",
        );

        Ok(all_hard_declined)
    }

    // Get token based on retry type
    pub async fn get_token_based_on_retry_type(
        state: &SessionState,
        connector_customer_id: &str,
        retry_algorithm_type: RevenueRecoveryAlgorithmType,
        last_token_used: Option<&str>,
    ) -> CustomResult<Option<PaymentProcessorTokenStatus>, errors::StorageError> {
        let mut token = None;
        match retry_algorithm_type {
            RevenueRecoveryAlgorithmType::Monitoring => {
                logger::error!("Monitoring type found for Revenue Recovery retry payment");
            }

            RevenueRecoveryAlgorithmType::Cascading => {
                token = match last_token_used {
                    Some(token_id) => {
                        Self::get_payment_processor_token_using_token_id(
                            state,
                            connector_customer_id,
                            token_id,
                        )
                        .await?
                    }
                    None => None,
                };
            }

            RevenueRecoveryAlgorithmType::Smart => {
                token = Self::get_payment_processor_token_with_schedule_time(
                    state,
                    connector_customer_id,
                )
                .await?;
            }
        }

        token = token.and_then(|t| {
            t.is_hard_decline
                .unwrap_or(false)
                .then(|| {
                    logger::error!("Token is hard declined");
                })
                .map_or(Some(t), |_| None)
        });

        Ok(token)
    }
}


pub struct BrowserInformation {
    pub color_depth: Option<u8>,
    pub java_enabled: Option<bool>,
    pub java_script_enabled: Option<bool>,
    pub language: Option<String>,
    pub screen_height: Option<u32>,
    pub screen_width: Option<u32>,
    pub time_zone: Option<i32>,
    pub ip_address: Option<std::net::IpAddr>,
    pub accept_header: Option<String>,
    pub user_agent: Option<String>,
    pub os_type: Option<String>,
    pub os_version: Option<String>,
    pub device_model: Option<String>,
    pub accept_language: Option<String>,
}


pub struct NoonPaymentsActionRequest {
    api_operation: NoonApiOperations,
    order: NoonActionOrder,
    transaction: NoonActionTransaction,
}


File: crates/router/src/core/card_testing_guard.rs

pub mod utils;

use crate::core::errors;


File: crates/euclid/src/dssa/graph.rs
 Public functions: 3
 Public structs: 1

use std::{fmt::Debug, sync::Weak};

use hyperswitch_constraint_graph as cgraph;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
    dssa::types,
    frontend::dir,
    types::{DataType, Metadata},
};

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::*,
        frontend::dir::{enums::*, DirKey, DirKeyKind, DirValue},
        types::*,
    };
}

impl cgraph::KeyNode for dir::DirKey {}

impl cgraph::NodeViz for dir::DirKey {
    fn viz(&self) -> String {
        self.kind.to_string()
    }
}

impl cgraph::ValueNode for dir::DirValue {
    type Key = dir::DirKey;

    fn get_key(&self) -> Self::Key {
        Self::get_key(self)
    }
}

impl cgraph::NodeViz for dir::DirValue {
    fn viz(&self) -> String {
        match self {
            Self::PaymentMethod(pm) => pm.to_string(),
            Self::CardBin(bin) => bin.value.clone(),
            Self::CardType(ct) => ct.to_string(),
            Self::CardNetwork(cn) => cn.to_string(),
            Self::PayLaterType(plt) => plt.to_string(),
            Self::WalletType(wt) => wt.to_string(),
            Self::UpiType(ut) => ut.to_string(),
            Self::BankTransferType(btt) => btt.to_string(),
            Self::BankRedirectType(brt) => brt.to_string(),
            Self::BankDebitType(bdt) => bdt.to_string(),
            Self::CryptoType(ct) => ct.to_string(),
            Self::RewardType(rt) => rt.to_string(),
            Self::PaymentAmount(amt) => amt.number.to_string(),
            Self::PaymentCurrency(curr) => curr.to_string(),
            Self::AuthenticationType(at) => at.to_string(),
            Self::CaptureMethod(cm) => cm.to_string(),
            Self::BusinessCountry(bc) => bc.to_string(),
            Self::BillingCountry(bc) => bc.to_string(),
            Self::Connector(conn) => conn.connector.to_string(),
            Self::MetaData(mv) => format!("[{} = {}]", mv.key, mv.value),
            Self::MandateAcceptanceType(mat) => mat.to_string(),
            Self::MandateType(mt) => mt.to_string(),
            Self::PaymentType(pt) => pt.to_string(),
            Self::VoucherType(vt) => vt.to_string(),
            Self::GiftCardType(gct) => gct.to_string(),
            Self::BusinessLabel(bl) => bl.value.to_string(),
            Self::SetupFutureUsage(sfu) => sfu.to_string(),
            Self::CardRedirectType(crt) => crt.to_string(),
            Self::RealTimePaymentType(rtpt) => rtpt.to_string(),
            Self::OpenBankingType(ob) => ob.to_string(),
            Self::MobilePaymentType(mpt) => mpt.to_string(),
            Self::IssuerName(issuer_name) => issuer_name.value.clone(),
            Self::IssuerCountry(issuer_country) => issuer_country.to_string(),
            Self::CustomerDevicePlatform(customer_device_platform) => {
                customer_device_platform.to_string()
            }
            Self::CustomerDeviceType(customer_device_type) => customer_device_type.to_string(),
            Self::CustomerDeviceDisplaySize(customer_device_display_size) => {
                customer_device_display_size.to_string()
            }
            Self::AcquirerCountry(acquirer_country) => acquirer_country.to_string(),
            Self::AcquirerFraudRate(acquirer_fraud_rate) => acquirer_fraud_rate.number.to_string(),
        }
    }
}

#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "details", rename_all = "snake_case")]
pub enum AnalysisError<V: cgraph::ValueNode> {
    Graph(cgraph::GraphError<V>),
    AssertionTrace {
        trace: Weak<cgraph::AnalysisTrace<V>>,
        metadata: Metadata,
    },
    NegationTrace {
        trace: Weak<cgraph::AnalysisTrace<V>>,
        metadata: Vec<Metadata>,
    },
}

impl<V: cgraph::ValueNode> AnalysisError<V> {
    fn assertion_from_graph_error(metadata: &Metadata, graph_error: cgraph::GraphError<V>) -> Self {
        match graph_error {
            cgraph::GraphError::AnalysisError(trace) => Self::AssertionTrace {
                trace,
                metadata: metadata.clone(),
            },

            other => Self::Graph(other),
        }
    }

    fn negation_from_graph_error(
        metadata: Vec<&Metadata>,
        graph_error: cgraph::GraphError<V>,
    ) -> Self {
        match graph_error {
            cgraph::GraphError::AnalysisError(trace) => Self::NegationTrace {
                trace,
                metadata: metadata.iter().map(|m| (*m).clone()).collect(),
            },

            other => Self::Graph(other),
        }
    }
}

#[derive(Debug)]
pub struct AnalysisContext {
    keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>>,
}

impl AnalysisContext {
    pub fn from_dir_values(vals: impl IntoIterator<Item = dir::DirValue>) -> Self {
        let mut keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>> =
            FxHashMap::default();

        for dir_val in vals {
            let key = dir_val.get_key();
            let set = keywise_values.entry(key).or_default();
            set.insert(dir_val);
        }

        Self { keywise_values }
    }

    pub fn insert(&mut self, value: dir::DirValue) {
        self.keywise_values
            .entry(value.get_key())
            .or_default()
            .insert(value);
    }

    pub fn remove(&mut self, value: dir::DirValue) {
        let set = self.keywise_values.entry(value.get_key()).or_default();

        set.remove(&value);

        if set.is_empty() {
            self.keywise_values.remove(&value.get_key());
        }
    }
}

impl cgraph::CheckingContext for AnalysisContext {
    type Value = dir::DirValue;

    fn from_node_values<L>(vals: impl IntoIterator<Item = L>) -> Self
    where
        L: Into<Self::Value>,
    {
        let mut keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>> =
            FxHashMap::default();

        for dir_val in vals.into_iter().map(L::into) {
            let key = dir_val.get_key();
            let set = keywise_values.entry(key).or_default();
            set.insert(dir_val);
        }

        Self { keywise_values }
    }

    fn check_presence(
        &self,
        value: &cgraph::NodeValue<dir::DirValue>,
        strength: cgraph::Strength,
    ) -> bool {
        match value {
            cgraph::NodeValue::Key(k) => {
                self.keywise_values.contains_key(k) || matches!(strength, cgraph::Strength::Weak)
            }

            cgraph::NodeValue::Value(val) => {
                let key = val.get_key();
                let value_set = if let Some(set) = self.keywise_values.get(&key) {
                    set
                } else {
                    return matches!(strength, cgraph::Strength::Weak);
                };

                match key.kind.get_type() {
                    DataType::EnumVariant | DataType::StrValue | DataType::MetadataValue => {
                        value_set.contains(val)
                    }
                    DataType::Number => val.get_num_value().is_some_and(|num_val| {
                        value_set.iter().any(|ctx_val| {
                            ctx_val
                                .get_num_value()
                                .is_some_and(|ctx_num_val| num_val.fits(&ctx_num_val))
                        })
                    }),
                }
            }
        }
    }

    fn get_values_by_key(
        &self,
        key: &<Self::Value as cgraph::ValueNode>::Key,
    ) -> Option<Vec<Self::Value>> {
        self.keywise_values
            .get(key)
            .map(|set| set.iter().cloned().collect())
    }
}

pub trait CgraphExt {
    fn key_analysis(
        &self,
        key: dir::DirKey,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn check_value_validity(
        &self,
        val: dir::DirValue,
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<bool, cgraph::GraphError<dir::DirValue>>;

    fn key_value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn assertion_analysis(
        &self,
        positive_ctx: &[(&dir::DirValue, &Metadata)],
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;

    fn negation_analysis(
        &self,
        negative_ctx: &[(&[dir::DirValue], &Metadata)],
        analysis_ctx: &mut AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;

    fn perform_context_analysis(
        &self,
        ctx: &types::ConjunctiveContext<'_>,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;
}

impl CgraphExt for cgraph::ConstraintGraph<dir::DirValue> {
    fn key_analysis(
        &self,
        key: dir::DirKey,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.value_map
            .get(&cgraph::NodeValue::Key(key))
            .map_or(Ok(()), |node_id| {
                self.check_node(
                    ctx,
                    *node_id,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                    memo,
                    cycle_map,
                    domains,
                )
            })
    }

    fn value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.value_map
            .get(&cgraph::NodeValue::Value(val))
            .map_or(Ok(()), |node_id| {
                self.check_node(
                    ctx,
                    *node_id,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                    memo,
                    cycle_map,
                    domains,
                )
            })
    }

    fn check_value_validity(
        &self,
        val: dir::DirValue,
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<bool, cgraph::GraphError<dir::DirValue>> {
        let maybe_node_id = self.value_map.get(&cgraph::NodeValue::Value(val));

        let node_id = if let Some(nid) = maybe_node_id {
            nid
        } else {
            return Ok(false);
        };

        let result = self.check_node(
            analysis_ctx,
            *node_id,
            cgraph::Relation::Positive,
            cgraph::Strength::Weak,
            memo,
            cycle_map,
            domains,
        );

        match result {
            Ok(_) => Ok(true),
            Err(e) => {
                e.get_analysis_trace()?;
                Ok(false)
            }
        }
    }

    fn key_value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.key_analysis(val.get_key(), ctx, memo, cycle_map, domains)
            .and_then(|_| self.value_analysis(val, ctx, memo, cycle_map, domains))
    }

    fn assertion_analysis(
        &self,
        positive_ctx: &[(&dir::DirValue, &Metadata)],
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        positive_ctx.iter().try_for_each(|(value, metadata)| {
            self.key_value_analysis((*value).clone(), analysis_ctx, memo, cycle_map, domains)
                .map_err(|e| AnalysisError::assertion_from_graph_error(metadata, e))
        })
    }

    fn negation_analysis(
        &self,
        negative_ctx: &[(&[dir::DirValue], &Metadata)],
        analysis_ctx: &mut AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        let mut keywise_metadata: FxHashMap<dir::DirKey, Vec<&Metadata>> = FxHashMap::default();
        let mut keywise_negation: FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>> =
            FxHashMap::default();

        for (values, metadata) in negative_ctx {
            let mut metadata_added = false;

            for dir_value in *values {
                if !metadata_added {
                    keywise_metadata
                        .entry(dir_value.get_key())
                        .or_default()
                        .push(metadata);

                    metadata_added = true;
                }

                keywise_negation
                    .entry(dir_value.get_key())
                    .or_default()
                    .insert(dir_value);
            }
        }

        for (key, negation_set) in keywise_negation {
            let all_metadata = keywise_metadata.remove(&key).unwrap_or_default();
            let first_metadata = all_metadata.first().copied().cloned().unwrap_or_default();

            self.key_analysis(key.clone(), analysis_ctx, memo, cycle_map, domains)
                .map_err(|e| AnalysisError::assertion_from_graph_error(&first_metadata, e))?;

            let mut value_set = if let Some(set) = key.kind.get_value_set() {
                set
            } else {
                continue;
            };

            value_set.retain(|v| !negation_set.contains(v));

            for value in value_set {
                analysis_ctx.insert(value.clone());
                self.value_analysis(value.clone(), analysis_ctx, memo, cycle_map, domains)
                    .map_err(|e| {
                        AnalysisError::negation_from_graph_error(all_metadata.clone(), e)
                    })?;
                analysis_ctx.remove(value);
            }
        }

        Ok(())
    }

    fn perform_context_analysis(
        &self,
        ctx: &types::ConjunctiveContext<'_>,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        let mut analysis_ctx = AnalysisContext::from_dir_values(
            ctx.iter()
                .filter_map(|ctx_val| ctx_val.value.get_assertion().cloned()),
        );

        let positive_ctx = ctx
            .iter()
            .filter_map(|ctx_val| {
                ctx_val
                    .value
                    .get_assertion()
                    .map(|val| (val, ctx_val.metadata))
            })
            .collect::<Vec<_>>();
        self.assertion_analysis(
            &positive_ctx,
            &analysis_ctx,
            memo,
            &mut cgraph::CycleCheck::new(),
            domains,
        )?;

        let negative_ctx = ctx
            .iter()
            .filter_map(|ctx_val| {
                ctx_val
                    .value
                    .get_negation()
                    .map(|vals| (vals, ctx_val.metadata))
            })
            .collect::<Vec<_>>();
        self.negation_analysis(
            &negative_ctx,
            &mut analysis_ctx,
            memo,
            &mut cgraph::CycleCheck::new(),
            domains,
        )?;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]

    use std::ops::Deref;

    use euclid_macros::knowledge;
    use hyperswitch_constraint_graph::CycleCheck;

    use super::*;
    use crate::{dirval, frontend::dir::enums};

    #[test]
    fn test_strong_positive_relation_success() {
        let graph = knowledge! {
            PaymentMethod(Card) ->> CaptureMethod(Automatic);
            PaymentMethod(not Wallet)
                & PaymentMethod(not PayLater) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_strong_positive_relation_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) ->> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([dirval!(CaptureMethod = Automatic)]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_strong_negative_relation_success() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_strong_negative_relation_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Wallet),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_normal_one_of_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );
        assert!(matches!(
            *Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
                .expect("Expected Arc"),
            cgraph::AnalysisTrace::Value {
                predecessors: Some(cgraph::error::ValueTracePredecessor::OneOf(_)),
                ..
            }
        ));
    }

    #[test]
    fn test_all_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Card),
                dirval!(CaptureMethod = Automatic),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_all_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_all_aggregator_mandatory_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let mut memo = cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            &mut memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(matches!(
            *Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
                .expect("Expected Arc"),
            cgraph::AnalysisTrace::Value {
                predecessors: Some(cgraph::error::ValueTracePredecessor::Mandatory(_)),
                ..
            }
        ));
    }

    #[test]
    fn test_in_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_in_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_not_in_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(not in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
                dirval!(PaymentMethod = BankRedirect),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_not_in_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(not in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
                dirval!(PaymentMethod = BankRedirect),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_in_aggregator_failure_trace() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        if let cgraph::AnalysisTrace::Value {
            predecessors: Some(cgraph::error::ValueTracePredecessor::Mandatory(agg_error)),
            ..
        } = Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
            .expect("Expected arc")
            .deref()
        {
            assert!(matches!(
                *Weak::upgrade(agg_error.deref()).expect("Expected Arc"),
                cgraph::AnalysisTrace::InAggregation {
                    found: Some(dir::DirValue::PaymentMethod(enums::PaymentMethod::PayLater)),
                    ..
                }
            ));
        } else {
            panic!("Failed unwrapping OnlyInAggregation trace from AnalysisTrace");
        }
    }

    #[test]
    fn test_memoization_in_kgraph() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BillingCountry(enums::BillingCountry::India)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BusinessCountry(
                enums::BusinessCountry::UnitedStatesOfAmerica,
            )),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();
        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_2,
                _node_3,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to an edge");
        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(BusinessCountry = UnitedStatesOfAmerica),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Wallet),
                dirval!(BillingCountry = India),
                dirval!(BusinessCountry = UnitedStatesOfAmerica),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );
        let _answer = memo
            .get(&(
                _node_3,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ))
            .expect("Memoization not workng");
        matches!(_answer, Ok(()));
    }

    #[test]
    fn test_cycle_resolution_in_graph() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();
        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_2,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to an edge");
        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(PaymentMethod = Wallet),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = Card),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }

    #[test]
    fn test_cycle_resolution_in_graph1() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
                enums::CaptureMethod::Automatic,
            )),
            None,
            None::<()>,
        );

        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();

        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_1,
                _node_3,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_3 = builder
            .make_edge(
                _node_2,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_4 = builder
            .make_edge(
                _node_3,
                _node_1,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(CaptureMethod = Automatic),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }

    #[test]
    fn test_cycle_resolution_in_graph2() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_0 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BillingCountry(
                enums::BillingCountry::Afghanistan,
            )),
            None,
            None::<()>,
        );

        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
                enums::CaptureMethod::Automatic,
            )),
            None,
            None::<()>,
        );

        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );

        let _node_4 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentCurrency(enums::PaymentCurrency::USD)),
            None,
            None::<()>,
        );

        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();

        let _edge_1 = builder
            .make_edge(
                _node_0,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_3 = builder
            .make_edge(
                _node_1,
                _node_3,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_4 = builder
            .make_edge(
                _node_3,
                _node_4,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_5 = builder
            .make_edge(
                _node_2,
                _node_4,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let _edge_6 = builder
            .make_edge(
                _node_4,
                _node_1,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_7 = builder
            .make_edge(
                _node_4,
                _node_0,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(BillingCountry = Afghanistan),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentCurrency = USD),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(CaptureMethod = Automatic),
                dirval!(BillingCountry = Afghanistan),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }
}


impl super::behaviour::Conversion for MerchantAccount {
    type DstType = diesel_models::merchant_account::MerchantAccount;
    type NewDstType = diesel_models::merchant_account::MerchantAccountNew;
    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let id = self.get_id().to_owned();

        let setter = diesel_models::merchant_account::MerchantAccountSetter {
            id,
            merchant_name: self.merchant_name.map(|name| name.into()),
            merchant_details: self.merchant_details.map(|details| details.into()),
            publishable_key: Some(self.publishable_key),
            storage_scheme: self.storage_scheme,
            metadata: self.metadata,
            created_at: self.created_at,
            modified_at: self.modified_at,
            organization_id: self.organization_id,
            recon_status: self.recon_status,
            version: common_types::consts::API_VERSION,
            is_platform_account: self.is_platform_account,
            product_type: self.product_type,
            merchant_account_type: self.merchant_account_type,
        };

        Ok(diesel_models::MerchantAccount::from(setter))
    }

    async fn convert_back(
        state: &keymanager::KeyManagerState,
        item: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        let id = item.get_id().to_owned();
        let publishable_key =
            item.publishable_key
                .ok_or(ValidationError::MissingRequiredField {
                    field_name: "publishable_key".to_string(),
                })?;

        async {
            Ok::<Self, error_stack::Report<common_utils::errors::CryptoError>>(Self {
                id,
                merchant_name: item
                    .merchant_name
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                merchant_details: item
                    .merchant_details
                    .async_lift(|inner| async {
                        crypto_operation(
                            state,
                            type_name!(Self::DstType),
                            CryptoOperation::DecryptOptional(inner),
                            key_manager_identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_optionaloperation())
                    })
                    .await?,
                publishable_key,
                storage_scheme: item.storage_scheme,
                metadata: item.metadata,
                created_at: item.created_at,
                modified_at: item.modified_at,
                organization_id: item.organization_id,
                recon_status: item.recon_status,
                is_platform_account: item.is_platform_account,
                version: item.version,
                product_type: item.product_type,
                merchant_account_type: item.merchant_account_type.unwrap_or_default(),
            })
        }
        .await
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting merchant data".to_string(),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(diesel_models::merchant_account::MerchantAccountNew {
            id: self.id,
            merchant_name: self.merchant_name.map(Encryption::from),
            merchant_details: self.merchant_details.map(Encryption::from),
            publishable_key: Some(self.publishable_key),
            metadata: self.metadata,
            created_at: now,
            modified_at: now,
            organization_id: self.organization_id,
            recon_status: self.recon_status,
            version: common_types::consts::API_VERSION,
            is_platform_account: self.is_platform_account,
            product_type: self
                .product_type
                .or(Some(common_enums::MerchantProductType::Orchestration)),
            merchant_account_type: self.merchant_account_type,
        })
    }
}

impl super::behaviour::Conversion for for MerchantAccount


pub async fn delete_conditional_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
) -> RouterResponse<()> {
    use common_utils::ext_traits::ValueExt;
    use storage_impl::redis::cache;

    use super::routing::helpers::update_merchant_active_algorithm_ref;

    let db = state.store.as_ref();
    let key = merchant_context
        .get_merchant_account()
        .get_id()
        .get_payment_config_routing_id();
    let mut algo_id: api_models::routing::RoutingAlgorithmRef = merchant_context
        .get_merchant_account()
        .routing_algorithm
        .clone()
        .map(|value| value.parse_value("routing algorithm"))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Could not decode the conditional_config algorithm")?
        .unwrap_or_default();
    algo_id.config_algo_id = None;
    let config_key = cache::CacheKind::DecisionManager(key.clone().into());
    update_merchant_active_algorithm_ref(
        &state,
        merchant_context.get_merchant_key_store(),
        config_key,
        algo_id,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update deleted algorithm ref")?;

    db.delete_config_by_key(&key)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to delete routing config from DB")?;
    Ok(service_api::ApplicationResponse::StatusOk)
}


pub async fn get_metrics(
    pool: &AnalyticsProvider,
    publishable_key: &String,
    req: GetSdkEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<
        SdkEventMetricsBucketIdentifier,
        SdkEventMetricsAccumulator,
    > = HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let req = req.clone();
        let publishable_key_scoped = publishable_key.to_owned();
        let pool = pool.clone();
        set.spawn(async move {
            let data = pool
                .get_sdk_event_metrics(
                    &metric_type,
                    &req.group_by_names.clone(),
                    &publishable_key_scoped,
                    &req.filters,
                    req.time_series.map(|t| t.granularity),
                    &req.time_range,
                )
                .await
                .change_context(AnalyticsError::UnknownError);
            (metric_type, data)
        });
    }

    while let Some((metric, data)) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        logger::info!("Logging Result {:?}", data);
        for (id, value) in data? {
            let metrics_builder = metrics_accumulator.entry(id).or_default();
            match metric {
                SdkEventMetrics::PaymentAttempts => {
                    metrics_builder.payment_attempts.add_metrics_bucket(&value)
                }
                SdkEventMetrics::PaymentMethodsCallCount => metrics_builder
                    .payment_methods_call_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::SdkRenderedCount => metrics_builder
                    .sdk_rendered_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::SdkInitiatedCount => metrics_builder
                    .sdk_initiated_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::PaymentMethodSelectedCount => metrics_builder
                    .payment_method_selected_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::PaymentDataFilledCount => metrics_builder
                    .payment_data_filled_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::AveragePaymentTime => metrics_builder
                    .average_payment_time
                    .add_metrics_bucket(&value),
                SdkEventMetrics::LoadTime => metrics_builder.load_time.add_metrics_bucket(&value),
            }
        }

        logger::debug!(
            "Analytics Accumulated Results: metric: {}, results: {:#?}",
            metric,
            metrics_accumulator
        );
    }

    let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| MetricsBucketResponse {
            values: val.collect(),
            dimensions: id,
        })
        .collect();

    Ok(MetricsResponse {
        query_data,
        meta_data: [AnalyticsMetadata {
            current_time_range: req.time_range,
        }],
    })
}


/// Panics if `json_payload` array does not contain one `GetPaymentIntentMetricRequest` element.
    pub async fn get_merchant_payment_intent_metrics(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<[GetPaymentIntentMetricRequest; 1]>,
    ) -> impl Responder {
        // safety: This shouldn't panic owing to the data type
        #[allow(clippy::expect_used)]
        let payload = json_payload
            .into_inner()
            .to_vec()
            .pop()
            .expect("Couldn't get GetPaymentIntentMetricRequest");
        let flow = AnalyticsFlow::GetPaymentIntentMetrics;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            payload,
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };

                let validator_response = request_validator(
                    AnalyticsRequest {
                        payment_intent: Some(req.clone()),
                        ..Default::default()
                    },
                    &state,
                )
                .await?;
                let ex_rates = validator_response;
                analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }


pub struct IdealInfo {
    pub issuer_id: MultisafepayBankNames,
}


impl<'a> MerchantReferenceIdForCustomer<'a> {
    async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
        &self,
        db: &dyn StorageInterface,
    ) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
        self.merchant_reference_id
            .async_map(|cust| async {
                self.verify_if_merchant_reference_not_present_by_merchant_reference_id(cust, db)
                    .await
            })
            .await
            .transpose()
    }

    async fn verify_if_merchant_reference_not_present_by_merchant_reference_id(
        &self,
        cus: &'a id_type::CustomerId,
        db: &dyn StorageInterface,
    ) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
        match db
            .find_customer_by_customer_id_merchant_id(
                self.key_manager_state,
                cus,
                self.merchant_id,
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
        {
            Err(err) => {
                if !err.current_context().is_db_not_found() {
                    Err(err).switch()
                } else {
                    Ok(())
                }
            }
            Ok(_) => Err(report!(
                errors::CustomersErrorResponse::CustomerAlreadyExists
            )),
        }
    }
}


impl api::PaymentSession for Coingate {}

impl api::PaymentSession for for Coingate


OpenAPI Block Path: components.schemas.SurchargeDetailsResponse
{
  "type": "object",
  "required": [
    "surcharge",
    "display_surcharge_amount",
    "display_tax_on_surcharge_amount",
    "display_total_surcharge_amount"
  ],
  "properties": {
    "surcharge": {
      "$ref": "#/components/schemas/SurchargeResponse"
    },
    "tax_on_surcharge": {
      "allOf": [
        {
          "$ref": "#/components/schemas/SurchargePercentage"
        }
      ],
      "nullable": true
    },
    "display_surcharge_amount": {
      "type": "number",
      "format": "double",
      "description": "surcharge amount for this payment"
    },
    "display_tax_on_surcharge_amount": {
      "type": "number",
      "format": "double",
      "description": "tax on surcharge amount for this payment"
    },
    "display_total_surcharge_amount": {
      "type": "number",
      "format": "double",
      "description": "sum of display_surcharge_amount and display_tax_on_surcharge_amount"
    }
  }
}


pub struct IntegrityCheckError {
    /// Field names for which integrity check failed!
    pub field_names: String,
    /// Connector transaction reference id
    pub connector_transaction_id: Option<String>,
}


pub struct PayoutAttemptResponse {
    /// Unique identifier for the attempt
    pub attempt_id: String,
    /// The status of the attempt
    #[schema(value_type = PayoutStatus, example = "failed")]
    pub status: api_enums::PayoutStatus,
    /// The payout attempt amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
    #[schema(value_type = i64, example = 6583)]
    pub amount: common_utils::types::MinorUnit,
    /// The currency of the amount of the payout attempt
    #[schema(value_type = Option<Currency>, example = "USD")]
    pub currency: Option<api_enums::Currency>,
    /// The connector used for the payout
    pub connector: Option<String>,
    /// Connector's error code in case of failures
    pub error_code: Option<String>,
    /// Connector's error message in case of failures
    pub error_message: Option<String>,
    /// The payout method that was used
    #[schema(value_type = Option<PayoutType>, example = "bank")]
    pub payment_method: Option<api_enums::PayoutType>,
    /// Payment Method Type
    #[schema(value_type = Option<PaymentMethodType>, example = "bacs")]
    pub payout_method_type: Option<api_enums::PaymentMethodType>,
    /// A unique identifier for a payout provided by the connector
    pub connector_transaction_id: Option<String>,
    /// If the payout was cancelled the reason provided here
    pub cancellation_reason: Option<String>,
    /// (This field is not live yet)
    /// Error code unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutAttemptResponse)]
    #[schema(value_type = Option<String>, max_length = 255, example = "UE_000")]
    pub unified_code: Option<UnifiedCode>,
    /// (This field is not live yet)
    /// Error message unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutAttemptResponse)]
    #[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
    pub unified_message: Option<UnifiedMessage>,
}


pub async fn update_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    update_customer: customers::CustomerUpdateRequestInternal,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();
    //Add this in update call if customer can be updated anywhere else

    #[cfg(feature = "v1")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        merchant_reference_id: &update_customer.customer_id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    #[cfg(feature = "v2")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        id: &update_customer.id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    let customer = verify_id_for_update_customer
        .verify_id_and_get_customer_object(db)
        .await?;

    let updated_customer = update_customer
        .request
        .create_domain_model_from_request(
            &None,
            db,
            &merchant_context,
            key_manager_state,
            &state,
            &customer,
        )
        .await?;

    update_customer.request.generate_response(&updated_customer)
}


impl<T> EmailService for T
where
    T: EmailClient,
    <Self as EmailClient>::RichText: Send,
{
    async fn compose_and_send_email(
        &self,
        base_url: &str,
        email_data: Box<dyn EmailData + Send>,
        proxy_url: Option<&String>,
    ) -> EmailResult<()> {
        let email_data = email_data.get_email_data(base_url);
        let email_data = email_data.await?;

        let EmailContents {
            subject,
            body,
            recipient,
        } = email_data;

        let rich_text_string = self.convert_to_rich_text(body)?;

        self.send_email(recipient, subject, rich_text_string, proxy_url)
            .await
    }
}


pub struct ChargebeeCustomer {
    pub payment_method: ChargebeePaymentMethod,
}


pub async fn get_user_theme_using_lineage(
    state: SessionState,
    user_from_token: UserFromToken,
    entity_type: EntityType,
) -> UserResponse<theme_api::GetThemeResponse> {
    let lineage =
        theme_utils::get_theme_lineage_from_user_token(&user_from_token, &state, &entity_type)
            .await?;
    let theme = state
        .store
        .find_theme_by_lineage(lineage)
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: theme.email_config(),
        theme_id: theme.theme_id,
        theme_name: theme.theme_name,
        entity_type: theme.entity_type,
        tenant_id: theme.tenant_id,
        org_id: theme.org_id,
        merchant_id: theme.merchant_id,
        profile_id: theme.profile_id,
        theme_data: parsed_data,
    }))
}


File: crates/router/src/bin/router.rs

use error_stack::ResultExt;
use router::{
    configs::settings::{CmdLineConf, Settings},
    core::errors::{ApplicationError, ApplicationResult},
    logger,
    routes::metrics,
};

#[tokio::main]
async fn main() -> ApplicationResult<()> {
    // get commandline config before initializing config
    let cmd_line = <CmdLineConf as clap::Parser>::parse();

    #[allow(clippy::expect_used)]
    let conf = Settings::with_config_path(cmd_line.config_path)
        .expect("Unable to construct application configuration");
    #[allow(clippy::expect_used)]
    conf.validate()
        .expect("Failed to validate router configuration");

    #[allow(clippy::print_stdout)] // The logger has not yet been initialized
    #[cfg(feature = "vergen")]
    {
        println!("Starting router (Version: {})", router_env::git_tag!());
    }

    let _guard = router_env::setup(
        &conf.log,
        router_env::service_name!(),
        [router_env::service_name!(), "actix_server"],
    )
    .change_context(ApplicationError::ConfigurationError)?;

    logger::info!("Application started [{:?}] [{:?}]", conf.server, conf.log);

    // Spawn a thread for collecting metrics at fixed intervals
    metrics::bg_metrics_collector::spawn_metrics_collector(
        conf.log.telemetry.bg_metrics_collection_interval_in_secs,
    );

    #[allow(clippy::expect_used)]
    let server = Box::pin(router::start_server(conf))
        .await
        .expect("Failed to create the server");
    let _ = server.await;

    Err(error_stack::Report::from(ApplicationError::from(
        std::io::Error::other("Server shut down"),
    )))
}


pub async fn apply_filters_on_payments(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<id_type::ProfileId>>,
    constraints: api::PaymentListFilterConstraints,
) -> RouterResponse<api::PaymentListResponseV2> {
    common_utils::metrics::utils::record_operation_time(
        async {
            let limit = &constraints.limit;
            helpers::validate_payment_list_request_for_joins(*limit)?;
            let db: &dyn StorageInterface = state.store.as_ref();
            let pi_fetch_constraints = (constraints.clone(), profile_id_list.clone()).try_into()?;
            let list: Vec<(storage::PaymentIntent, storage::PaymentAttempt)> = db
                .get_filtered_payment_intents_attempt(
                    &(&state).into(),
                    merchant_context.get_merchant_account().get_id(),
                    &pi_fetch_constraints,
                    merchant_context.get_merchant_key_store(),
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            let data: Vec<api::PaymentsResponse> =
                list.into_iter().map(ForeignFrom::foreign_from).collect();

            let active_attempt_ids = db
                .get_filtered_active_attempt_ids_for_total_count(
                    merchant_context.get_merchant_account().get_id(),
                    &pi_fetch_constraints,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

            let total_count = if constraints.has_no_attempt_filters() {
                i64::try_from(active_attempt_ids.len())
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Error while converting from usize to i64")
            } else {
                db.get_total_count_of_filtered_payment_attempts(
                    merchant_context.get_merchant_account().get_id(),
                    &active_attempt_ids,
                    constraints.connector,
                    constraints.payment_method,
                    constraints.payment_method_type,
                    constraints.authentication_type,
                    constraints.merchant_connector_id,
                    constraints.card_network,
                    constraints.card_discovery,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
            }?;

            Ok(services::ApplicationResponse::Json(
                api::PaymentListResponseV2 {
                    count: data.len(),
                    total_count,
                    data,
                },
            ))
        },
        &metrics::PAYMENT_LIST_LATENCY,
        router_env::metric_attributes!((
            "merchant_id",
            merchant_context.get_merchant_account().get_id().clone()
        )),
    )
    .await
}


pub fn get_tenant_ids(&self) -> Vec<id_type::TenantId> {
        self.tenants
            .0
            .values()
            .map(|tenant| tenant.tenant_id.clone())
            .collect()
    }


pub struct SuccessBasedRoutingPayloadWrapper {
    pub updated_config: SuccessBasedRoutingConfig,
    pub algorithm_id: common_utils::id_type::RoutingId,
    pub profile_id: common_utils::id_type::ProfileId,
}


OpenAPI Block Path: components.schemas.PaymentLinkShowSdkTerms
{
  "type": "string",
  "enum": [
    "always",
    "auto",
    "never"
  ]
}


pub struct RefundResponse {
    id: u64,
    status: u16,
}


impl api::PaymentsCompleteAuthorize for Braintree {}

impl api::PaymentsCompleteAuthorize for for Braintree


pub struct Card {
    pub card_number: CardNumber,
    pub name_on_card: Option<masking::Secret<String>>,
    pub card_exp_month: masking::Secret<String>,
    pub card_exp_year: masking::Secret<String>,
    pub card_brand: Option<String>,
    pub card_isin: Option<String>,
    pub nick_name: Option<String>,
}


pub async fn update_revenue_recovery_algorithm_under_profile(
        self,
        db: &dyn StorageInterface,
        key_manager_state: &KeyManagerState,
        merchant_key_store: &domain::MerchantKeyStore,
        revenue_recovery_retry_algorithm_type: common_enums::RevenueRecoveryAlgorithmType,
    ) -> RouterResult<()> {
        let recovery_algorithm_data =
            diesel_models::business_profile::RevenueRecoveryAlgorithmData {
                monitoring_configured_timestamp: date_time::now(),
            };
        let profile_update = domain::ProfileUpdate::RevenueRecoveryAlgorithmUpdate {
            revenue_recovery_retry_algorithm_type,
            revenue_recovery_retry_algorithm_data: Some(recovery_algorithm_data),
        };

        db.update_profile_by_profile_id(
            key_manager_state,
            merchant_key_store,
            self.profile,
            profile_update,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Failed to update revenue recovery retry algorithm in business profile",
        )?;
        Ok(())
    }


/// set response status code
    pub fn set_routable_connectors(&mut self, connectors: Vec<RoutableConnectorChoice>) {
        let connectors = connectors
            .into_iter()
            .map(|c| {
                format!(
                    "{:?}:{:?}",
                    c.connector,
                    c.merchant_connector_id
                        .map(|id| id.get_string_repr().to_string())
                        .unwrap_or(String::from(""))
                )
            })
            .collect::<Vec<_>>()
            .join(",");
        self.routable_connectors = connectors;
    }


pub struct FileInfo {
    pub file_data: Option<Vec<u8>>,
    pub provider_file_id: Option<String>,
    pub file_type: Option<String>,
}