File: crates/router/src/core/payments/routing.rs
 Public functions: 27
 Public structs: 4

mod transformers;
pub mod utils;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use std::collections::hash_map;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use std::hash::{Hash, Hasher};
use std::{collections::HashMap, str::FromStr, sync::Arc};

#[cfg(feature = "v1")]
use api_models::open_router::{self as or_types, DecidedGateway, OpenRouterDecideGatewayRequest};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use api_models::routing as api_routing;
use api_models::{
    admin as admin_api,
    enums::{self as api_enums, CountryAlpha2},
    routing::ConnectorSelection,
};
use common_types::payments as common_payments_types;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use common_utils::ext_traits::AsyncExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use euclid::{
    backend::{self, inputs as dsl_inputs, EuclidBackend},
    dssa::graph::{self as euclid_graph, CgraphExt},
    enums as euclid_enums,
    frontend::{ast, dir as euclid_dir},
};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use external_services::grpc_client::dynamic_routing::{
    contract_routing_client::ContractBasedDynamicRouting,
    elimination_based_client::EliminationBasedRouting,
    success_rate_client::SuccessBasedDynamicRouting, DynamicRoutingError,
};
use hyperswitch_domain_models::address::Address;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use hyperswitch_interfaces::events::routing_api_logs::{ApiMethod, RoutingEngine};
use kgraph_utils::{
    mca as mca_graph,
    transformers::{IntoContext, IntoDirValue},
    types::CountryCurrencyFilter,
};
use masking::{PeekInterface, Secret};
use rand::distributions::{self, Distribution};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use rand::SeedableRng;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use router_env::{instrument, tracing};
use rustc_hash::FxHashMap;
use storage_impl::redis::cache::{CacheKey, CGRAPH_CACHE, ROUTING_CACHE};

#[cfg(feature = "v2")]
use crate::core::admin;
#[cfg(feature = "payouts")]
use crate::core::payouts;
#[cfg(feature = "v1")]
use crate::core::routing::transformers::OpenRouterDecideGatewayRequestExt;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::routes::app::SessionStateInfo;
use crate::{
    core::{
        errors, errors as oss_errors,
        payments::{
            routing::utils::DecisionEngineApiHandler, OperationSessionGetters,
            OperationSessionSetters,
        },
        routing,
    },
    logger, services,
    types::{
        api::{self, routing as routing_types},
        domain, storage as oss_storage,
        transformers::{ForeignFrom, ForeignInto, ForeignTryFrom},
    },
    utils::{OptionExt, ValueExt},
    SessionState,
};

pub enum CachedAlgorithm {
    Single(Box<routing_types::RoutableConnectorChoice>),
    Priority(Vec<routing_types::RoutableConnectorChoice>),
    VolumeSplit(Vec<routing_types::ConnectorVolumeSplit>),
    Advanced(backend::VirInterpreterBackend<ConnectorSelection>),
}

#[cfg(feature = "v1")]
pub struct SessionFlowRoutingInput<'a> {
    pub state: &'a SessionState,
    pub country: Option<CountryAlpha2>,
    pub key_store: &'a domain::MerchantKeyStore,
    pub merchant_account: &'a domain::MerchantAccount,
    pub payment_attempt: &'a oss_storage::PaymentAttempt,
    pub payment_intent: &'a oss_storage::PaymentIntent,
    pub chosen: api::SessionConnectorDatas,
}

#[cfg(feature = "v2")]
pub struct SessionFlowRoutingInput<'a> {
    pub country: Option<CountryAlpha2>,
    pub payment_intent: &'a oss_storage::PaymentIntent,
    pub chosen: api::SessionConnectorDatas,
}

#[allow(dead_code)]
#[cfg(feature = "v1")]
pub struct SessionRoutingPmTypeInput<'a> {
    state: &'a SessionState,
    key_store: &'a domain::MerchantKeyStore,
    attempt_id: &'a str,
    routing_algorithm: &'a MerchantAccountRoutingAlgorithm,
    backend_input: dsl_inputs::BackendInput,
    allowed_connectors: FxHashMap<String, api::GetToken>,
    profile_id: &'a common_utils::id_type::ProfileId,
}

#[cfg(feature = "v2")]
pub struct SessionRoutingPmTypeInput<'a> {
    routing_algorithm: &'a MerchantAccountRoutingAlgorithm,
    backend_input: dsl_inputs::BackendInput,
    allowed_connectors: FxHashMap<String, api::GetToken>,
    profile_id: &'a common_utils::id_type::ProfileId,
}

type RoutingResult<O> = oss_errors::CustomResult<O, errors::RoutingError>;

#[cfg(feature = "v1")]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum MerchantAccountRoutingAlgorithm {
    V1(routing_types::RoutingAlgorithmRef),
}

#[cfg(feature = "v1")]
impl Default for MerchantAccountRoutingAlgorithm {
    fn default() -> Self {
        Self::V1(routing_types::RoutingAlgorithmRef::default())
    }
}

#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum MerchantAccountRoutingAlgorithm {
    V1(Option<common_utils::id_type::RoutingId>),
}

#[cfg(feature = "payouts")]
pub fn make_dsl_input_for_payouts(
    payout_data: &payouts::PayoutData,
) -> RoutingResult<dsl_inputs::BackendInput> {
    let mandate = dsl_inputs::MandateData {
        mandate_acceptance_type: None,
        mandate_type: None,
        payment_type: None,
    };
    let metadata = payout_data
        .payouts
        .metadata
        .clone()
        .map(|val| val.parse_value("routing_parameters"))
        .transpose()
        .change_context(errors::RoutingError::MetadataParsingError)
        .attach_printable("Unable to parse routing_parameters from metadata of payouts")
        .unwrap_or(None);
    let payment = dsl_inputs::PaymentInput {
        amount: payout_data.payouts.amount,
        card_bin: None,
        currency: payout_data.payouts.destination_currency,
        authentication_type: None,
        capture_method: None,
        business_country: payout_data
            .payout_attempt
            .business_country
            .map(api_enums::Country::from_alpha2),
        billing_country: payout_data
            .billing_address
            .as_ref()
            .and_then(|ba| ba.address.as_ref())
            .and_then(|addr| addr.country)
            .map(api_enums::Country::from_alpha2),
        business_label: payout_data.payout_attempt.business_label.clone(),
        setup_future_usage: None,
    };
    let payment_method = dsl_inputs::PaymentMethodInput {
        payment_method: payout_data
            .payouts
            .payout_type
            .map(api_enums::PaymentMethod::foreign_from),
        payment_method_type: payout_data
            .payout_method_data
            .as_ref()
            .map(api_enums::PaymentMethodType::foreign_from)
            .or_else(|| {
                payout_data.payment_method.as_ref().and_then(|pm| {
                    #[cfg(feature = "v1")]
                    {
                        pm.payment_method_type
                    }
                    #[cfg(feature = "v2")]
                    {
                        pm.payment_method_subtype
                    }
                })
            }),
        card_network: None,
    };
    Ok(dsl_inputs::BackendInput {
        mandate,
        metadata,
        payment,
        payment_method,
        acquirer_data: None,
        customer_device_data: None,
        issuer_data: None,
    })
}

#[cfg(feature = "v2")]
pub fn make_dsl_input(
    payments_dsl_input: &routing::PaymentsDslInput<'_>,
) -> RoutingResult<dsl_inputs::BackendInput> {
    let mandate_data = dsl_inputs::MandateData {
        mandate_acceptance_type: payments_dsl_input.setup_mandate.as_ref().and_then(
            |mandate_data| {
                mandate_data
                    .customer_acceptance
                    .as_ref()
                    .map(|customer_accept| match customer_accept.acceptance_type {
                        common_payments_types::AcceptanceType::Online => {
                            euclid_enums::MandateAcceptanceType::Online
                        }
                        common_payments_types::AcceptanceType::Offline => {
                            euclid_enums::MandateAcceptanceType::Offline
                        }
                    })
            },
        ),
        mandate_type: payments_dsl_input
            .setup_mandate
            .as_ref()
            .and_then(|mandate_data| {
                mandate_data
                    .mandate_type
                    .clone()
                    .map(|mandate_type| match mandate_type {
                        hyperswitch_domain_models::mandates::MandateDataType::SingleUse(_) => {
                            euclid_enums::MandateType::SingleUse
                        }
                        hyperswitch_domain_models::mandates::MandateDataType::MultiUse(_) => {
                            euclid_enums::MandateType::MultiUse
                        }
                    })
            }),
        payment_type: Some(
            if payments_dsl_input
                .recurring_details
                .as_ref()
                .is_some_and(|data| {
                    matches!(
                        data,
                        api_models::mandates::RecurringDetails::ProcessorPaymentToken(_)
                    )
                })
            {
                euclid_enums::PaymentType::PptMandate
            } else {
                payments_dsl_input.setup_mandate.map_or_else(
                    || euclid_enums::PaymentType::NonMandate,
                    |_| euclid_enums::PaymentType::SetupMandate,
                )
            },
        ),
    };
    let payment_method_input = dsl_inputs::PaymentMethodInput {
        payment_method: Some(payments_dsl_input.payment_attempt.payment_method_type),
        payment_method_type: Some(payments_dsl_input.payment_attempt.payment_method_subtype),
        card_network: payments_dsl_input
            .payment_method_data
            .as_ref()
            .and_then(|pm_data| match pm_data {
                domain::PaymentMethodData::Card(card) => card.card_network.clone(),

                _ => None,
            }),
    };

    let payment_input = dsl_inputs::PaymentInput {
        amount: payments_dsl_input
            .payment_attempt
            .amount_details
            .get_net_amount(),
        card_bin: payments_dsl_input.payment_method_data.as_ref().and_then(
            |pm_data| match pm_data {
                domain::PaymentMethodData::Card(card) => Some(card.card_number.get_card_isin()),
                _ => None,
            },
        ),
        currency: payments_dsl_input.currency,
        authentication_type: Some(payments_dsl_input.payment_attempt.authentication_type),
        capture_method: Some(payments_dsl_input.payment_intent.capture_method),
        business_country: None,
        billing_country: payments_dsl_input
            .address
            .get_payment_method_billing()
            .and_then(|billing_address| billing_address.address.as_ref())
            .and_then(|address_details| address_details.country)
            .map(api_enums::Country::from_alpha2),
        business_label: None,
        setup_future_usage: Some(payments_dsl_input.payment_intent.setup_future_usage),
    };

    let metadata = payments_dsl_input
        .payment_intent
        .metadata
        .clone()
        .map(|value| value.parse_value("routing_parameters"))
        .transpose()
        .change_context(errors::RoutingError::MetadataParsingError)
        .attach_printable("Unable to parse routing_parameters from metadata of payment_intent")
        .unwrap_or(None);

    Ok(dsl_inputs::BackendInput {
        metadata,
        payment: payment_input,
        payment_method: payment_method_input,
        mandate: mandate_data,
        acquirer_data: None,
        customer_device_data: None,
        issuer_data: None,
    })
}

#[cfg(feature = "v1")]
pub fn make_dsl_input(
    payments_dsl_input: &routing::PaymentsDslInput<'_>,
) -> RoutingResult<dsl_inputs::BackendInput> {
    let mandate_data = dsl_inputs::MandateData {
        mandate_acceptance_type: payments_dsl_input.setup_mandate.as_ref().and_then(
            |mandate_data| {
                mandate_data
                    .customer_acceptance
                    .as_ref()
                    .map(|cat| match cat.acceptance_type {
                        common_payments_types::AcceptanceType::Online => {
                            euclid_enums::MandateAcceptanceType::Online
                        }
                        common_payments_types::AcceptanceType::Offline => {
                            euclid_enums::MandateAcceptanceType::Offline
                        }
                    })
            },
        ),
        mandate_type: payments_dsl_input
            .setup_mandate
            .as_ref()
            .and_then(|mandate_data| {
                mandate_data.mandate_type.clone().map(|mt| match mt {
                    hyperswitch_domain_models::mandates::MandateDataType::SingleUse(_) => {
                        euclid_enums::MandateType::SingleUse
                    }
                    hyperswitch_domain_models::mandates::MandateDataType::MultiUse(_) => {
                        euclid_enums::MandateType::MultiUse
                    }
                })
            }),
        payment_type: Some(
            if payments_dsl_input
                .recurring_details
                .as_ref()
                .is_some_and(|data| {
                    matches!(
                        data,
                        api_models::mandates::RecurringDetails::ProcessorPaymentToken(_)
                    )
                })
            {
                euclid_enums::PaymentType::PptMandate
            } else {
                payments_dsl_input.setup_mandate.map_or_else(
                    || euclid_enums::PaymentType::NonMandate,
                    |_| euclid_enums::PaymentType::SetupMandate,
                )
            },
        ),
    };
    let payment_method_input = dsl_inputs::PaymentMethodInput {
        payment_method: payments_dsl_input.payment_attempt.payment_method,
        payment_method_type: payments_dsl_input.payment_attempt.payment_method_type,
        card_network: payments_dsl_input
            .payment_method_data
            .as_ref()
            .and_then(|pm_data| match pm_data {
                domain::PaymentMethodData::Card(card) => card.card_network.clone(),

                _ => None,
            }),
    };

    let payment_input = dsl_inputs::PaymentInput {
        amount: payments_dsl_input.payment_attempt.get_total_amount(),
        card_bin: payments_dsl_input.payment_method_data.as_ref().and_then(
            |pm_data| match pm_data {
                domain::PaymentMethodData::Card(card) => {
                    Some(card.card_number.peek().chars().take(6).collect())
                }
                _ => None,
            },
        ),
        currency: payments_dsl_input.currency,
        authentication_type: payments_dsl_input.payment_attempt.authentication_type,
        capture_method: payments_dsl_input
            .payment_attempt
            .capture_method
            .and_then(|cm| cm.foreign_into()),
        business_country: payments_dsl_input
            .payment_intent
            .business_country
            .map(api_enums::Country::from_alpha2),
        billing_country: payments_dsl_input
            .address
            .get_payment_method_billing()
            .and_then(|bic| bic.address.as_ref())
            .and_then(|add| add.country)
            .map(api_enums::Country::from_alpha2),
        business_label: payments_dsl_input.payment_intent.business_label.clone(),
        setup_future_usage: payments_dsl_input.payment_intent.setup_future_usage,
    };

    let metadata = payments_dsl_input
        .payment_intent
        .parse_and_get_metadata("routing_parameters")
        .change_context(errors::RoutingError::MetadataParsingError)
        .attach_printable("Unable to parse routing_parameters from metadata of payment_intent")
        .unwrap_or(None);

    Ok(dsl_inputs::BackendInput {
        metadata,
        payment: payment_input,
        payment_method: payment_method_input,
        mandate: mandate_data,
        acquirer_data: None,
        customer_device_data: None,
        issuer_data: None,
    })
}

pub async fn perform_static_routing_v1(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    algorithm_id: Option<&common_utils::id_type::RoutingId>,
    business_profile: &domain::Profile,
    transaction_data: &routing::TransactionData<'_>,
) -> RoutingResult<(
    Vec<routing_types::RoutableConnectorChoice>,
    Option<common_enums::RoutingApproach>,
)> {
    logger::debug!("euclid_routing: performing routing for connector selection");
    let get_merchant_fallback_config = || async {
        #[cfg(feature = "v1")]
        return routing::helpers::get_merchant_default_config(
            &*state.clone().store,
            business_profile.get_id().get_string_repr(),
            &api_enums::TransactionType::from(transaction_data),
        )
        .await
        .change_context(errors::RoutingError::FallbackConfigFetchFailed);
        #[cfg(feature = "v2")]
        return admin::ProfileWrapper::new(business_profile.clone())
            .get_default_fallback_list_of_connector_under_profile()
            .change_context(errors::RoutingError::FallbackConfigFetchFailed);
    };
    let algorithm_id = if let Some(id) = algorithm_id {
        id
    } else {
        let fallback_config = get_merchant_fallback_config().await?;
        logger::debug!("euclid_routing: active algorithm isn't present, default falling back");
        return Ok((fallback_config, None));
    };
    let cached_algorithm = ensure_algorithm_cached_v1(
        state,
        merchant_id,
        algorithm_id,
        business_profile.get_id(),
        &api_enums::TransactionType::from(transaction_data),
    )
    .await?;

    let backend_input = match transaction_data {
        routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?,
        #[cfg(feature = "payouts")]
        routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?,
    };

    let payment_id = match transaction_data {
        routing::TransactionData::Payment(payment_data) => payment_data
            .payment_attempt
            .payment_id
            .clone()
            .get_string_repr()
            .to_string(),
        #[cfg(feature = "payouts")]
        routing::TransactionData::Payout(payout_data) => payout_data
            .payout_attempt
            .payout_id
            .get_string_repr()
            .to_string(),
    };

    // Decision of de-routing is stored
    let de_evaluated_connector = if !state.conf.open_router.static_routing_enabled {
        logger::debug!("decision_engine_euclid: decision_engine routing not enabled");
        Vec::default()
    } else {
        utils::decision_engine_routing(
            state,
            backend_input.clone(),
            business_profile,
            payment_id,
            get_merchant_fallback_config().await?,
        )
        .await
        .map_err(|e|
            // errors are ignored as this is just for diff checking as of now (optional flow).
            logger::error!(decision_engine_euclid_evaluate_error=?e, "decision_engine_euclid: error in evaluation of rule")
        )
        .unwrap_or_default()
    };

    let (routable_connectors, routing_approach) = match cached_algorithm.as_ref() {
        CachedAlgorithm::Single(conn) => (
            vec![(**conn).clone()],
            Some(common_enums::RoutingApproach::StraightThroughRouting),
        ),
        CachedAlgorithm::Priority(plist) => (plist.clone(), None),
        CachedAlgorithm::VolumeSplit(splits) => (
            perform_volume_split(splits.to_vec())
                .change_context(errors::RoutingError::ConnectorSelectionFailed)?,
            Some(common_enums::RoutingApproach::VolumeBasedRouting),
        ),
        CachedAlgorithm::Advanced(interpreter) => (
            execute_dsl_and_get_connector_v1(backend_input, interpreter)?,
            Some(common_enums::RoutingApproach::RuleBasedRouting),
        ),
    };

    // Results are logged for diff(between legacy and decision_engine's euclid) and have parameters as:
    // is_equal: verifies all output are matching in order,
    // is_equal_length: matches length of both outputs (useful for verifying volume based routing
    // results)
    // de_response: response from the decision_engine's euclid
    // hs_response: response from legacy_euclid
    utils::compare_and_log_result(
        de_evaluated_connector.clone(),
        routable_connectors.clone(),
        "evaluate_routing".to_string(),
    );

    Ok((
        utils::select_routing_result(
            state,
            business_profile,
            routable_connectors,
            de_evaluated_connector,
        )
        .await,
        routing_approach,
    ))
}

async fn ensure_algorithm_cached_v1(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    algorithm_id: &common_utils::id_type::RoutingId,
    profile_id: &common_utils::id_type::ProfileId,
    transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<CachedAlgorithm>> {
    let key = {
        match transaction_type {
            common_enums::TransactionType::Payment => {
                format!(
                    "routing_config_{}_{}",
                    merchant_id.get_string_repr(),
                    profile_id.get_string_repr(),
                )
            }
            #[cfg(feature = "payouts")]
            common_enums::TransactionType::Payout => {
                format!(
                    "routing_config_po_{}_{}",
                    merchant_id.get_string_repr(),
                    profile_id.get_string_repr()
                )
            }
            common_enums::TransactionType::ThreeDsAuthentication => {
                Err(errors::RoutingError::InvalidTransactionType)?
            }
        }
    };

    let cached_algorithm = ROUTING_CACHE
        .get_val::<Arc<CachedAlgorithm>>(CacheKey {
            key: key.clone(),
            prefix: state.tenant.redis_key_prefix.clone(),
        })
        .await;

    let algorithm = if let Some(algo) = cached_algorithm {
        algo
    } else {
        refresh_routing_cache_v1(state, key.clone(), algorithm_id, profile_id).await?
    };

    Ok(algorithm)
}

pub fn perform_straight_through_routing(
    algorithm: &routing_types::StraightThroughAlgorithm,
    creds_identifier: Option<&str>,
) -> RoutingResult<(Vec<routing_types::RoutableConnectorChoice>, bool)> {
    Ok(match algorithm {
        routing_types::StraightThroughAlgorithm::Single(conn) => {
            (vec![(**conn).clone()], creds_identifier.is_none())
        }

        routing_types::StraightThroughAlgorithm::Priority(conns) => (conns.clone(), true),

        routing_types::StraightThroughAlgorithm::VolumeSplit(splits) => (
            perform_volume_split(splits.to_vec())
                .change_context(errors::RoutingError::ConnectorSelectionFailed)
                .attach_printable(
                    "Volume Split connector selection error in straight through routing",
                )?,
            true,
        ),
    })
}

pub fn perform_routing_for_single_straight_through_algorithm(
    algorithm: &routing_types::StraightThroughAlgorithm,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    Ok(match algorithm {
        routing_types::StraightThroughAlgorithm::Single(connector) => vec![(**connector).clone()],

        routing_types::StraightThroughAlgorithm::Priority(_)
        | routing_types::StraightThroughAlgorithm::VolumeSplit(_) => {
            Err(errors::RoutingError::DslIncorrectSelectionAlgorithm)
                .attach_printable("Unsupported algorithm received as a result of static routing")?
        }
    })
}

fn execute_dsl_and_get_connector_v1(
    backend_input: dsl_inputs::BackendInput,
    interpreter: &backend::VirInterpreterBackend<ConnectorSelection>,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    let routing_output: routing_types::StaticRoutingAlgorithm = interpreter
        .execute(backend_input)
        .map(|out| out.connector_selection.foreign_into())
        .change_context(errors::RoutingError::DslExecutionError)?;

    Ok(match routing_output {
        routing_types::StaticRoutingAlgorithm::Priority(plist) => plist,

        routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => perform_volume_split(splits)
            .change_context(errors::RoutingError::DslFinalConnectorSelectionFailed)?,

        _ => Err(errors::RoutingError::DslIncorrectSelectionAlgorithm)
            .attach_printable("Unsupported algorithm received as a result of static routing")?,
    })
}

pub async fn refresh_routing_cache_v1(
    state: &SessionState,
    key: String,
    algorithm_id: &common_utils::id_type::RoutingId,
    profile_id: &common_utils::id_type::ProfileId,
) -> RoutingResult<Arc<CachedAlgorithm>> {
    let algorithm = {
        let algorithm = state
            .store
            .find_routing_algorithm_by_profile_id_algorithm_id(profile_id, algorithm_id)
            .await
            .change_context(errors::RoutingError::DslMissingInDb)?;
        let algorithm: routing_types::StaticRoutingAlgorithm = algorithm
            .algorithm_data
            .parse_value("RoutingAlgorithm")
            .change_context(errors::RoutingError::DslParsingError)?;
        algorithm
    };

    let cached_algorithm = match algorithm {
        routing_types::StaticRoutingAlgorithm::Single(conn) => CachedAlgorithm::Single(conn),
        routing_types::StaticRoutingAlgorithm::Priority(plist) => CachedAlgorithm::Priority(plist),
        routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => {
            CachedAlgorithm::VolumeSplit(splits)
        }
        routing_types::StaticRoutingAlgorithm::Advanced(program) => {
            let interpreter = backend::VirInterpreterBackend::with_program(program)
                .change_context(errors::RoutingError::DslBackendInitError)
                .attach_printable("Error initializing DSL interpreter backend")?;

            CachedAlgorithm::Advanced(interpreter)
        }
        api_models::routing::StaticRoutingAlgorithm::ThreeDsDecisionRule(_program) => {
            Err(errors::RoutingError::InvalidRoutingAlgorithmStructure)
                .attach_printable("Unsupported algorithm received")?
        }
    };

    let arc_cached_algorithm = Arc::new(cached_algorithm);

    ROUTING_CACHE
        .push(
            CacheKey {
                key,
                prefix: state.tenant.redis_key_prefix.clone(),
            },
            arc_cached_algorithm.clone(),
        )
        .await;

    Ok(arc_cached_algorithm)
}

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
pub fn perform_dynamic_routing_volume_split(
    splits: Vec<api_models::routing::RoutingVolumeSplit>,
    rng_seed: Option<&str>,
) -> RoutingResult<api_models::routing::RoutingVolumeSplit> {
    let weights: Vec<u8> = splits.iter().map(|sp| sp.split).collect();
    let weighted_index = distributions::WeightedIndex::new(weights)
        .change_context(errors::RoutingError::VolumeSplitFailed)
        .attach_printable("Error creating weighted distribution for volume split")?;

    let idx = if let Some(seed) = rng_seed {
        let mut hasher = hash_map::DefaultHasher::new();
        seed.hash(&mut hasher);
        let hash = hasher.finish();

        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(hash);
        weighted_index.sample(&mut rng)
    } else {
        let mut rng = rand::thread_rng();
        weighted_index.sample(&mut rng)
    };

    let routing_choice = *splits
        .get(idx)
        .ok_or(errors::RoutingError::VolumeSplitFailed)
        .attach_printable("Volume split index lookup failed")?;

    Ok(routing_choice)
}

pub fn perform_volume_split(
    mut splits: Vec<routing_types::ConnectorVolumeSplit>,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    let weights: Vec<u8> = splits.iter().map(|sp| sp.split).collect();
    let weighted_index = distributions::WeightedIndex::new(weights)
        .change_context(errors::RoutingError::VolumeSplitFailed)
        .attach_printable("Error creating weighted distribution for volume split")?;

    let mut rng = rand::thread_rng();
    let idx = weighted_index.sample(&mut rng);

    splits
        .get(idx)
        .ok_or(errors::RoutingError::VolumeSplitFailed)
        .attach_printable("Volume split index lookup failed")?;

    // Panic Safety: We have performed a `get(idx)` operation just above which will
    // ensure that the index is always present, else throw an error.
    let removed = splits.remove(idx);
    splits.insert(0, removed);

    Ok(splits.into_iter().map(|sp| sp.connector).collect())
}

// #[cfg(feature = "v1")]
pub async fn get_merchant_cgraph(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    profile_id: &common_utils::id_type::ProfileId,
    transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<euclid_dir::DirValue>>> {
    let merchant_id = &key_store.merchant_id;

    let key = {
        match transaction_type {
            api_enums::TransactionType::Payment => {
                format!(
                    "cgraph_{}_{}",
                    merchant_id.get_string_repr(),
                    profile_id.get_string_repr()
                )
            }
            #[cfg(feature = "payouts")]
            api_enums::TransactionType::Payout => {
                format!(
                    "cgraph_po_{}_{}",
                    merchant_id.get_string_repr(),
                    profile_id.get_string_repr()
                )
            }
            api_enums::TransactionType::ThreeDsAuthentication => {
                Err(errors::RoutingError::InvalidTransactionType)?
            }
        }
    };

    let cached_cgraph = CGRAPH_CACHE
        .get_val::<Arc<hyperswitch_constraint_graph::ConstraintGraph<euclid_dir::DirValue>>>(
            CacheKey {
                key: key.clone(),
                prefix: state.tenant.redis_key_prefix.clone(),
            },
        )
        .await;

    let cgraph = if let Some(graph) = cached_cgraph {
        graph
    } else {
        refresh_cgraph_cache(state, key_store, key.clone(), profile_id, transaction_type).await?
    };

    Ok(cgraph)
}

// #[cfg(feature = "v1")]
pub async fn refresh_cgraph_cache(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    key: String,
    profile_id: &common_utils::id_type::ProfileId,
    transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<euclid_dir::DirValue>>> {
    let mut merchant_connector_accounts = state
        .store
        .find_merchant_connector_account_by_merchant_id_and_disabled_list(
            &state.into(),
            &key_store.merchant_id,
            false,
            key_store,
        )
        .await
        .change_context(errors::RoutingError::KgraphCacheRefreshFailed)?;

    match transaction_type {
        api_enums::TransactionType::Payment => {
            merchant_connector_accounts.retain(|mca| {
                mca.connector_type != storage_enums::ConnectorType::PaymentVas
                    && mca.connector_type != storage_enums::ConnectorType::PaymentMethodAuth
                    && mca.connector_type != storage_enums::ConnectorType::PayoutProcessor
                    && mca.connector_type != storage_enums::ConnectorType::AuthenticationProcessor
            });
        }
        #[cfg(feature = "payouts")]
        api_enums::TransactionType::Payout => {
            merchant_connector_accounts
                .retain(|mca| mca.connector_type == storage_enums::ConnectorType::PayoutProcessor);
        }
        api_enums::TransactionType::ThreeDsAuthentication => {
            Err(errors::RoutingError::InvalidTransactionType)?
        }
    };

    let connector_type = match transaction_type {
        api_enums::TransactionType::Payment => common_enums::ConnectorType::PaymentProcessor,
        #[cfg(feature = "payouts")]
        api_enums::TransactionType::Payout => common_enums::ConnectorType::PayoutProcessor,
        api_enums::TransactionType::ThreeDsAuthentication => {
            Err(errors::RoutingError::InvalidTransactionType)?
        }
    };

    let merchant_connector_accounts = merchant_connector_accounts
        .filter_based_on_profile_and_connector_type(profile_id, connector_type);

    let api_mcas = merchant_connector_accounts
        .into_iter()
        .map(admin_api::MerchantConnectorResponse::foreign_try_from)
        .collect::<Result<Vec<_>, _>>()
        .change_context(errors::RoutingError::KgraphCacheRefreshFailed)?;
    let connector_configs = state
        .conf
        .pm_filters
        .0
        .clone()
        .into_iter()
        .filter(|(key, _)| key != "default")
        .map(|(key, value)| {
            let key = api_enums::RoutableConnectors::from_str(&key)
                .map_err(|_| errors::RoutingError::InvalidConnectorName(key))?;

            Ok((key, value.foreign_into()))
        })
        .collect::<Result<HashMap<_, _>, errors::RoutingError>>()?;
    let default_configs = state
        .conf
        .pm_filters
        .0
        .get("default")
        .cloned()
        .map(ForeignFrom::foreign_from);
    let config_pm_filters = CountryCurrencyFilter {
        connector_configs,
        default_configs,
    };
    let cgraph = Arc::new(
        mca_graph::make_mca_graph(api_mcas, &config_pm_filters)
            .change_context(errors::RoutingError::KgraphCacheRefreshFailed)
            .attach_printable("when construction cgraph")?,
    );

    CGRAPH_CACHE
        .push(
            CacheKey {
                key,
                prefix: state.tenant.redis_key_prefix.clone(),
            },
            Arc::clone(&cgraph),
        )
        .await;

    Ok(cgraph)
}

#[allow(clippy::too_many_arguments)]
pub async fn perform_cgraph_filtering(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    chosen: Vec<routing_types::RoutableConnectorChoice>,
    backend_input: dsl_inputs::BackendInput,
    eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
    profile_id: &common_utils::id_type::ProfileId,
    transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    let context = euclid_graph::AnalysisContext::from_dir_values(
        backend_input
            .into_context()
            .change_context(errors::RoutingError::KgraphAnalysisError)?,
    );
    let cached_cgraph = get_merchant_cgraph(state, key_store, profile_id, transaction_type).await?;

    let mut final_selection = Vec::<routing_types::RoutableConnectorChoice>::new();
    for choice in chosen {
        let routable_connector = choice.connector;
        let euclid_choice: ast::ConnectorChoice = choice.clone().foreign_into();
        let dir_val = euclid_choice
            .into_dir_value()
            .change_context(errors::RoutingError::KgraphAnalysisError)?;
        let cgraph_eligible = cached_cgraph
            .check_value_validity(
                dir_val,
                &context,
                &mut hyperswitch_constraint_graph::Memoization::new(),
                &mut hyperswitch_constraint_graph::CycleCheck::new(),
                None,
            )
            .change_context(errors::RoutingError::KgraphAnalysisError)?;

        let filter_eligible =
            eligible_connectors.map_or(true, |list| list.contains(&routable_connector));

        if cgraph_eligible && filter_eligible {
            final_selection.push(choice);
        }
    }

    Ok(final_selection)
}

pub async fn perform_eligibility_analysis(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    chosen: Vec<routing_types::RoutableConnectorChoice>,
    transaction_data: &routing::TransactionData<'_>,
    eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
    profile_id: &common_utils::id_type::ProfileId,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    let backend_input = match transaction_data {
        routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?,
        #[cfg(feature = "payouts")]
        routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?,
    };

    perform_cgraph_filtering(
        state,
        key_store,
        chosen,
        backend_input,
        eligible_connectors,
        profile_id,
        &api_enums::TransactionType::from(transaction_data),
    )
    .await
}

pub async fn perform_fallback_routing(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    transaction_data: &routing::TransactionData<'_>,
    eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
    business_profile: &domain::Profile,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
    #[cfg(feature = "v1")]
    let fallback_config = routing::helpers::get_merchant_default_config(
        &*state.store,
        match transaction_data {
            routing::TransactionData::Payment(payment_data) => payment_data


impl webhooks::IncomingWebhook for Payload {
    async fn verify_webhook_source(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
        _connector_account_details: common_utils::crypto::Encryptable<Secret<serde_json::Value>>,
        _connector_label: &str,
    ) -> CustomResult<bool, errors::ConnectorError> {
        // Payload does not support source verification
        // It does, but the client id and client secret generation is not possible at present
        // It requires OAuth connect which falls under Access Token flow and it also requires multiple calls to be made
        // We return false just so that a PSync call is triggered internally
        Ok(false)
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let webhook_body: responses::PayloadWebhookEvent = request
            .body
            .parse_struct("PayloadWebhookEvent")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        let reference_id = match webhook_body.trigger {
            responses::PayloadWebhooksTrigger::Payment
            | responses::PayloadWebhooksTrigger::Processed
            | responses::PayloadWebhooksTrigger::Authorized
            | responses::PayloadWebhooksTrigger::Credit
            | responses::PayloadWebhooksTrigger::Reversal
            | responses::PayloadWebhooksTrigger::Void
            | responses::PayloadWebhooksTrigger::AutomaticPayment
            | responses::PayloadWebhooksTrigger::Decline
            | responses::PayloadWebhooksTrigger::Deposit
            | responses::PayloadWebhooksTrigger::Reject
            | responses::PayloadWebhooksTrigger::PaymentActivationStatus
            | responses::PayloadWebhooksTrigger::PaymentLinkStatus
            | responses::PayloadWebhooksTrigger::ProcessingStatus
            | responses::PayloadWebhooksTrigger::BankAccountReject
            | responses::PayloadWebhooksTrigger::Chargeback
            | responses::PayloadWebhooksTrigger::ChargebackReversal
            | responses::PayloadWebhooksTrigger::TransactionOperation
            | responses::PayloadWebhooksTrigger::TransactionOperationClear => {
                api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::ConnectorTransactionId(
                        webhook_body
                            .triggered_on
                            .transaction_id
                            .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
                    ),
                )
            }

            responses::PayloadWebhooksTrigger::Refund => {
                api_models::webhooks::ObjectReferenceId::RefundId(
                    api_models::webhooks::RefundIdType::ConnectorRefundId(
                        webhook_body
                            .triggered_on
                            .transaction_id
                            .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
                    ),
                )
            }
        };

        Ok(reference_id)
    }

    fn get_webhook_event_type(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        let webhook_body: responses::PayloadWebhookEvent =
            request.body.parse_struct("PayloadWebhookEvent").switch()?;

        Ok(api_models::webhooks::IncomingWebhookEvent::from(
            webhook_body.trigger,
        ))
    }

    fn get_webhook_resource_object(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let webhook_body: responses::PayloadWebhookEvent = request
            .body
            .parse_struct("PayloadWebhookEvent")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        Ok(Box::new(webhook_body))
    }
}

impl webhooks::IncomingWebhook for for Payload


pub fn check_ppcp_custom_status(&self) -> Option<api::PayPalOnboardingStatus> {
        match self.get_ppcp_custom_status() {
            Some(VettingStatus::Denied) => Some(api::PayPalOnboardingStatus::PpcpCustomDenied),
            Some(VettingStatus::Subscribed) => None,
            _ => Some(api::PayPalOnboardingStatus::MorePermissionsNeeded),
        }
    }


File: crates/api_models/src/enums.rs
 Public functions: 8
 Public structs: 1

use std::str::FromStr;

pub use common_enums::*;
use utoipa::ToSchema;

pub use super::connector_enums::Connector;

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
)]

/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum RoutingAlgorithm {
    RoundRobin,
    MaxConversion,
    MinCost,
    Custom,
}

#[cfg(feature = "payouts")]
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    Hash,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PayoutConnectors {
    Adyen,
    Adyenplatform,
    Cybersource,
    Ebanx,
    Nomupay,
    Payone,
    Paypal,
    Stripe,
    Wise,
}

#[cfg(feature = "v2")]
/// Whether active attempt is to be set/unset
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub enum UpdateActiveAttempt {
    /// Request to set the active attempt id
    #[schema(value_type = Option<String>)]
    Set(common_utils::id_type::GlobalAttemptId),
    /// To unset the active attempt id
    Unset,
}

#[cfg(feature = "payouts")]
impl From<PayoutConnectors> for RoutableConnectors {
    fn from(value: PayoutConnectors) -> Self {
        match value {
            PayoutConnectors::Adyen => Self::Adyen,
            PayoutConnectors::Adyenplatform => Self::Adyenplatform,
            PayoutConnectors::Cybersource => Self::Cybersource,
            PayoutConnectors::Ebanx => Self::Ebanx,
            PayoutConnectors::Nomupay => Self::Nomupay,
            PayoutConnectors::Payone => Self::Payone,
            PayoutConnectors::Paypal => Self::Paypal,
            PayoutConnectors::Stripe => Self::Stripe,
            PayoutConnectors::Wise => Self::Wise,
        }
    }
}

#[cfg(feature = "payouts")]
impl From<PayoutConnectors> for Connector {
    fn from(value: PayoutConnectors) -> Self {
        match value {
            PayoutConnectors::Adyen => Self::Adyen,
            PayoutConnectors::Adyenplatform => Self::Adyenplatform,
            PayoutConnectors::Cybersource => Self::Cybersource,
            PayoutConnectors::Ebanx => Self::Ebanx,
            PayoutConnectors::Nomupay => Self::Nomupay,
            PayoutConnectors::Payone => Self::Payone,
            PayoutConnectors::Paypal => Self::Paypal,
            PayoutConnectors::Stripe => Self::Stripe,
            PayoutConnectors::Wise => Self::Wise,
        }
    }
}

#[cfg(feature = "payouts")]
impl TryFrom<Connector> for PayoutConnectors {
    type Error = String;
    fn try_from(value: Connector) -> Result<Self, Self::Error> {
        match value {
            Connector::Adyen => Ok(Self::Adyen),
            Connector::Adyenplatform => Ok(Self::Adyenplatform),
            Connector::Cybersource => Ok(Self::Cybersource),
            Connector::Ebanx => Ok(Self::Ebanx),
            Connector::Nomupay => Ok(Self::Nomupay),
            Connector::Payone => Ok(Self::Payone),
            Connector::Paypal => Ok(Self::Paypal),
            Connector::Stripe => Ok(Self::Stripe),
            Connector::Wise => Ok(Self::Wise),
            _ => Err(format!("Invalid payout connector {value}")),
        }
    }
}

#[cfg(feature = "frm")]
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    Hash,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum FrmConnectors {
    /// Signifyd Risk Manager. Official docs: https://docs.signifyd.com/
    Signifyd,
    Riskified,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    Hash,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum TaxConnectors {
    Taxjar,
}

#[derive(Clone, Debug, serde::Serialize, strum::EnumString, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum BillingConnectors {
    Chargebee,
    Recurly,
    Stripebilling,
    Custombilling,
    #[cfg(feature = "dummy_connector")]
    DummyBillingConnector,
}

#[derive(Clone, Copy, Debug, serde::Serialize, strum::EnumString, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum VaultConnectors {
    Vgs,
    HyperswitchVault,
}

impl From<VaultConnectors> for Connector {
    fn from(value: VaultConnectors) -> Self {
        match value {
            VaultConnectors::Vgs => Self::Vgs,
            VaultConnectors::HyperswitchVault => Self::HyperswitchVault,
        }
    }
}

#[derive(
    Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum FrmAction {
    CancelTxn,
    AutoRefund,
    ManualReview,
}

#[derive(
    Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum FrmPreferredFlowTypes {
    Pre,
    Post,
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct UnresolvedResponseReason {
    pub code: String,
    /// A message to merchant to give hint on next action he/she should do to resolve
    pub message: String,
}

/// Possible field type of required fields in payment_method_data
#[derive(
    Clone,
    Debug,
    Eq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum FieldType {
    UserCardNumber,
    UserCardExpiryMonth,
    UserCardExpiryYear,
    UserCardCvc,
    UserCardNetwork,
    UserFullName,
    UserEmailAddress,
    UserPhoneNumber,
    UserPhoneNumberCountryCode,           //phone number's country code
    UserCountry { options: Vec<String> }, //for country inside payment method data ex- bank redirect
    UserCurrency { options: Vec<String> },
    UserCryptoCurrencyNetwork, //for crypto network associated with the cryptopcurrency
    UserBillingName,
    UserAddressLine1,
    UserAddressLine2,
    UserAddressCity,
    UserAddressPincode,
    UserAddressState,
    UserAddressCountry { options: Vec<String> },
    UserShippingName,
    UserShippingAddressLine1,
    UserShippingAddressLine2,
    UserShippingAddressCity,
    UserShippingAddressPincode,
    UserShippingAddressState,
    UserShippingAddressCountry { options: Vec<String> },
    UserSocialSecurityNumber,
    UserBlikCode,
    UserBank,
    UserBankOptions { options: Vec<String> },
    UserBankAccountNumber,
    UserSourceBankAccountId,
    UserDestinationBankAccountId,
    Text,
    DropDown { options: Vec<String> },
    UserDateOfBirth,
    UserVpaId,
    LanguagePreference { options: Vec<String> },
    UserPixKey,
    UserCpf,
    UserCnpj,
    UserIban,
    UserBsbNumber,
    UserBankSortCode,
    UserBankRoutingNumber,
    UserBankType { options: Vec<String> },
    UserBankAccountHolderName,
    UserMsisdn,
    UserClientIdentifier,
    OrderDetailsProductName,
}

impl FieldType {
    pub fn get_billing_variants() -> Vec<Self> {
        vec![
            Self::UserBillingName,
            Self::UserAddressLine1,
            Self::UserAddressLine2,
            Self::UserAddressCity,
            Self::UserAddressPincode,
            Self::UserAddressState,
            Self::UserAddressCountry { options: vec![] },
        ]
    }

    pub fn get_shipping_variants() -> Vec<Self> {
        vec![
            Self::UserShippingName,
            Self::UserShippingAddressLine1,
            Self::UserShippingAddressLine2,
            Self::UserShippingAddressCity,
            Self::UserShippingAddressPincode,
            Self::UserShippingAddressState,
            Self::UserShippingAddressCountry { options: vec![] },
        ]
    }
}

/// This implementatiobn is to ignore the inner value of UserAddressCountry enum while comparing
impl PartialEq for FieldType {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::UserCardNumber, Self::UserCardNumber) => true,
            (Self::UserCardExpiryMonth, Self::UserCardExpiryMonth) => true,
            (Self::UserCardExpiryYear, Self::UserCardExpiryYear) => true,
            (Self::UserCardCvc, Self::UserCardCvc) => true,
            (Self::UserFullName, Self::UserFullName) => true,
            (Self::UserEmailAddress, Self::UserEmailAddress) => true,
            (Self::UserPhoneNumber, Self::UserPhoneNumber) => true,
            (Self::UserPhoneNumberCountryCode, Self::UserPhoneNumberCountryCode) => true,
            (
                Self::UserCountry {
                    options: options_self,
                },
                Self::UserCountry {
                    options: options_other,
                },
            ) => options_self.eq(options_other),
            (
                Self::UserCurrency {
                    options: options_self,
                },
                Self::UserCurrency {
                    options: options_other,
                },
            ) => options_self.eq(options_other),
            (Self::UserCryptoCurrencyNetwork, Self::UserCryptoCurrencyNetwork) => true,
            (Self::UserBillingName, Self::UserBillingName) => true,
            (Self::UserAddressLine1, Self::UserAddressLine1) => true,
            (Self::UserAddressLine2, Self::UserAddressLine2) => true,
            (Self::UserAddressCity, Self::UserAddressCity) => true,
            (Self::UserAddressPincode, Self::UserAddressPincode) => true,
            (Self::UserAddressState, Self::UserAddressState) => true,
            (Self::UserAddressCountry { .. }, Self::UserAddressCountry { .. }) => true,
            (Self::UserShippingName, Self::UserShippingName) => true,
            (Self::UserShippingAddressLine1, Self::UserShippingAddressLine1) => true,
            (Self::UserShippingAddressLine2, Self::UserShippingAddressLine2) => true,
            (Self::UserShippingAddressCity, Self::UserShippingAddressCity) => true,
            (Self::UserShippingAddressPincode, Self::UserShippingAddressPincode) => true,
            (Self::UserShippingAddressState, Self::UserShippingAddressState) => true,
            (Self::UserShippingAddressCountry { .. }, Self::UserShippingAddressCountry { .. }) => {
                true
            }
            (Self::UserBlikCode, Self::UserBlikCode) => true,
            (Self::UserBank, Self::UserBank) => true,
            (Self::Text, Self::Text) => true,
            (
                Self::DropDown {
                    options: options_self,
                },
                Self::DropDown {
                    options: options_other,
                },
            ) => options_self.eq(options_other),
            (Self::UserDateOfBirth, Self::UserDateOfBirth) => true,
            (Self::UserVpaId, Self::UserVpaId) => true,
            (Self::UserPixKey, Self::UserPixKey) => true,
            (Self::UserCpf, Self::UserCpf) => true,
            (Self::UserCnpj, Self::UserCnpj) => true,
            (Self::LanguagePreference { .. }, Self::LanguagePreference { .. }) => true,
            (Self::UserMsisdn, Self::UserMsisdn) => true,
            (Self::UserClientIdentifier, Self::UserClientIdentifier) => true,
            (Self::OrderDetailsProductName, Self::OrderDetailsProductName) => true,
            _unused => false,
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_partialeq_for_field_type() {
        let user_address_country_is_us = FieldType::UserAddressCountry {
            options: vec!["US".to_string()],
        };

        let user_address_country_is_all = FieldType::UserAddressCountry {
            options: vec!["ALL".to_string()],
        };

        assert!(user_address_country_is_us.eq(&user_address_country_is_all))
    }
}

/// Denotes the retry action
#[derive(
    Debug,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
    Clone,
    PartialEq,
    Eq,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum RetryAction {
    /// Payment can be retried from the client side until the payment is successful or payment expires or the attempts(configured by the merchant) for payment are exhausted
    ManualRetry,
    /// Denotes that the payment is requeued
    Requeue,
}

#[derive(Clone, Copy)]
pub enum LockerChoice {
    HyperswitchCardVault,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
    ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PmAuthConnectors {
    Plaid,
}

pub fn convert_pm_auth_connector(connector_name: &str) -> Option<PmAuthConnectors> {
    PmAuthConnectors::from_str(connector_name).ok()
}

pub fn convert_authentication_connector(connector_name: &str) -> Option<AuthenticationConnectors> {
    AuthenticationConnectors::from_str(connector_name).ok()
}

pub fn convert_tax_connector(connector_name: &str) -> Option<TaxConnectors> {
    TaxConnectors::from_str(connector_name).ok()
}

pub fn convert_billing_connector(connector_name: &str) -> Option<BillingConnectors> {
    BillingConnectors::from_str(connector_name).ok()
}
#[cfg(feature = "frm")]
pub fn convert_frm_connector(connector_name: &str) -> Option<FrmConnectors> {
    FrmConnectors::from_str(connector_name).ok()
}

pub fn convert_vault_connector(connector_name: &str) -> Option<VaultConnectors> {
    VaultConnectors::from_str(connector_name).ok()
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)]
pub enum ReconPermissionScope {
    #[serde(rename = "R")]
    Read = 0,
    #[serde(rename = "RW")]
    Write = 1,
}

impl From<PermissionScope> for ReconPermissionScope {
    fn from(scope: PermissionScope) -> Self {
        match scope {
            PermissionScope::Read => Self::Read,
            PermissionScope::Write => Self::Write,
        }
    }
}


impl Responder {
    let flow = Flow::PaymentsCancel;
    let mut payload = json_payload.into_inner();
    let payment_id = path.into_inner();

    tracing::Span::current().record("payment_id", payment_id.get_string_repr());

    payload.payment_id = payment_id;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, req_state| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payments::payments_core::<
                api_types::Void,
                payment_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Void>,
            >(
                state,
                req_state,
                merchant_context,
                auth.profile_id,
                payments::PaymentCancel,
                req,
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                HeaderPayload::default(),
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: true,
        }),
        locking_action,
    ))
    .await
}


pub struct CashappQr {}


pub async fn profile_list() {}


impl api::PaymentCapture for Thunes {}


OpenAPI Block Path: components.schemas.PaymentMethodSpecificFeatures
{
  "oneOf": [
    {
      "$ref": "#/components/schemas/CardSpecificFeatures"
    }
  ]
}


File: crates/hyperswitch_connectors/src/connectors/zen/transformers.rs
 Public structs: 24

use cards::CardNumber;
use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::{OptionExt, ValueExt},
    pii::{self},
    request::Method,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        BankDebitData, BankRedirectData, BankTransferData, Card, CardRedirectData, GiftCardData,
        PayLaterData, PaymentMethodData, VoucherData, WalletData,
    },
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{BrowserInformation, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::{
    api,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use ring::digest;
use serde::{Deserialize, Serialize};
use strum::Display;

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, BrowserInformationData, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData,
        RouterData as _,
    },
};

#[derive(Debug, Serialize)]
pub struct ZenRouterData<T> {
    pub amount: String,
    pub router_data: T,
}

impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for ZenRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T),
    ) -> Result<Self, Self::Error> {
        let amount = utils::get_amount_as_string(currency_unit, amount, currency)?;
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

// Auth Struct
pub struct ZenAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for ZenAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::HeaderKey { api_key } = auth_type {
            Ok(Self {
                api_key: api_key.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType.into())
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiRequest {
    merchant_transaction_id: String,
    payment_channel: ZenPaymentChannels,
    amount: String,
    currency: enums::Currency,
    payment_specific_data: ZenPaymentSpecificData,
    customer: ZenCustomerDetails,
    custom_ipn_url: String,
    items: Vec<ZenItemObject>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentsRequest {
    ApiRequest(Box<ApiRequest>),
    CheckoutRequest(Box<CheckoutRequest>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckoutRequest {
    amount: String,
    currency: enums::Currency,
    custom_ipn_url: String,
    items: Vec<ZenItemObject>,
    merchant_transaction_id: String,
    signature: Option<Secret<String>>,
    specified_payment_channel: ZenPaymentChannels,
    terminal_uuid: Secret<String>,
    url_redirect: String,
}

#[derive(Clone, Debug, Display, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(clippy::enum_variant_names)]
pub enum ZenPaymentChannels {
    PclCard,
    PclGooglepay,
    PclApplepay,
    PclBoacompraBoleto,
    PclBoacompraEfecty,
    PclBoacompraMultibanco,
    PclBoacompraPagoefectivo,
    PclBoacompraPix,
    PclBoacompraPse,
    PclBoacompraRedcompra,
    PclBoacompraRedpagos,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenCustomerDetails {
    email: pii::Email,
    ip: Secret<String, pii::IpAddress>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentSpecificData {
    ZenOnetimePayment(Box<ZenPaymentData>),
    ZenGeneralPayment(ZenGeneralPaymentData),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenPaymentData {
    browser_details: ZenBrowserDetails,
    #[serde(rename = "type")]
    payment_type: ZenPaymentTypes,
    #[serde(skip_serializing_if = "Option::is_none")]
    token: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    card: Option<ZenCardDetails>,
    descriptor: String,
    return_verify_url: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenGeneralPaymentData {
    #[serde(rename = "type")]
    payment_type: ZenPaymentTypes,
    return_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenBrowserDetails {
    color_depth: String,
    java_enabled: bool,
    lang: String,
    screen_height: String,
    screen_width: String,
    timezone: String,
    accept_header: String,
    window_size: String,
    user_agent: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ZenPaymentTypes {
    Onetime,
    General,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenCardDetails {
    number: CardNumber,
    expiry_date: Secret<String>,
    cvv: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenItemObject {
    name: String,
    price: String,
    quantity: u16,
    line_amount_total: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SessionObject {
    pub apple_pay: Option<WalletSessionData>,
    pub google_pay: Option<WalletSessionData>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WalletSessionData {
    pub terminal_uuid: Option<Secret<String>>,
    pub pay_wall_secret: Option<Secret<String>>,
}

impl TryFrom<(&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card)> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card),
    ) -> Result<Self, Self::Error> {
        let (item, ccard) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let browser_details = get_browser_details(&browser_info)?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenOnetimePayment(Box::new(ZenPaymentData {
                browser_details,
                //Connector Specific for cards
                payment_type: ZenPaymentTypes::Onetime,
                token: None,
                card: Some(ZenCardDetails {
                    number: ccard.card_number.clone(),
                    expiry_date: ccard
                        .get_card_expiry_month_year_2_digit_with_delimiter("".to_owned())?,
                    cvv: ccard.card_cvc.clone(),
                }),
                descriptor: item
                    .router_data
                    .get_description()?
                    .chars()
                    .take(24)
                    .collect(),
                return_verify_url: item.router_data.request.router_return_url.clone(),
            }));
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel: ZenPaymentChannels::PclCard,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &VoucherData,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &VoucherData,
        ),
    ) -> Result<Self, Self::Error> {
        let (item, voucher_data) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenGeneralPayment(ZenGeneralPaymentData {
                //Connector Specific for Latam Methods
                payment_type: ZenPaymentTypes::General,
                return_url: item.router_data.request.get_router_return_url()?,
            });
        let payment_channel = match voucher_data {
            VoucherData::Boleto { .. } => ZenPaymentChannels::PclBoacompraBoleto,
            VoucherData::Efecty => ZenPaymentChannels::PclBoacompraEfecty,
            VoucherData::PagoEfectivo => ZenPaymentChannels::PclBoacompraPagoefectivo,
            VoucherData::RedCompra => ZenPaymentChannels::PclBoacompraRedcompra,
            VoucherData::RedPagos => ZenPaymentChannels::PclBoacompraRedpagos,
            VoucherData::Oxxo
            | VoucherData::Alfamart { .. }
            | VoucherData::Indomaret { .. }
            | VoucherData::SevenEleven { .. }
            | VoucherData::Lawson { .. }
            | VoucherData::MiniStop { .. }
            | VoucherData::FamilyMart { .. }
            | VoucherData::Seicomart { .. }
            | VoucherData::PayEasy { .. } => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            ))?,
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &Box<BankTransferData>,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &Box<BankTransferData>,
        ),
    ) -> Result<Self, Self::Error> {
        let (item, bank_transfer_data) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenGeneralPayment(ZenGeneralPaymentData {
                //Connector Specific for Latam Methods
                payment_type: ZenPaymentTypes::General,
                return_url: item.router_data.request.get_router_return_url()?,
            });
        let payment_channel = match **bank_transfer_data {
            BankTransferData::MultibancoBankTransfer { .. } => {
                ZenPaymentChannels::PclBoacompraMultibanco
            }
            BankTransferData::Pix { .. } => ZenPaymentChannels::PclBoacompraPix,
            BankTransferData::Pse { .. } => ZenPaymentChannels::PclBoacompraPse,
            BankTransferData::SepaBankTransfer { .. }
            | BankTransferData::AchBankTransfer { .. }
            | BankTransferData::BacsBankTransfer { .. }
            | BankTransferData::PermataBankTransfer { .. }
            | BankTransferData::BcaBankTransfer { .. }
            | BankTransferData::BniVaBankTransfer { .. }
            | BankTransferData::BriVaBankTransfer { .. }
            | BankTransferData::CimbVaBankTransfer { .. }
            | BankTransferData::DanamonVaBankTransfer { .. }
            | BankTransferData::LocalBankTransfer { .. }
            | BankTransferData::InstantBankTransfer {}
            | BankTransferData::InstantBankTransferFinland { .. }
            | BankTransferData::InstantBankTransferPoland { .. }
            | BankTransferData::IndonesianBankTransfer { .. }
            | BankTransferData::MandiriVaBankTransfer { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                ))?
            }
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

/*
impl TryFrom<(&types::PaymentsAuthorizeRouterData, &GooglePayWalletData)> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, gpay_pay_redirect_data): (&types::PaymentsAuthorizeRouterData, &GooglePayWalletData),
    ) -> Result<Self, Self::Error> {
        let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
        let browser_info = item.request.get_browser_info()?;
        let browser_details = get_browser_details(&browser_info)?;
        let ip = browser_info.get_ip_address()?;
        let payment_specific_data = ZenPaymentData {
            browser_details,
            //Connector Specific for wallet
            payment_type: ZenPaymentTypes::ExternalPaymentToken,
            token: Some(Secret::new(
                gpay_pay_redirect_data.tokenization_data.token.clone(),
            )),
            card: None,
            descriptor: item.get_description()?.chars().take(24).collect(),
            return_verify_url: item.request.router_return_url.clone(),
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.attempt_id.clone(),
            payment_channel: ZenPaymentChannels::PclGooglepay,
            currency: item.request.currency,
            payment_specific_data,
            customer: get_customer(item, ip)?,
            custom_ipn_url: item.request.get_webhook_url()?,
            items: get_item_object(item, amount.clone())?,
            amount,
        })))
    }
}
*/
/*
impl
    TryFrom<(
        &types::PaymentsAuthorizeRouterData,
        &Box<ApplePayRedirectData>,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, _apple_pay_redirect_data): (
            &types::PaymentsAuthorizeRouterData,
            &Box<ApplePayRedirectData>,
        ),
    ) -> Result<Self, Self::Error> {
        let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
        let connector_meta = item.get_connector_meta()?;
        let session: SessionObject = connector_meta
            .parse_value("SessionObject")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let applepay_session_data = session
            .apple_pay
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        let terminal_uuid = applepay_session_data
            .terminal_uuid
            .clone()
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        let mut checkout_request = CheckoutRequest {
            merchant_transaction_id: item.attempt_id.clone(),
            specified_payment_channel: ZenPaymentChannels::PclApplepay,
            currency: item.request.currency,
            custom_ipn_url: item.request.get_webhook_url()?,
            items: get_item_object(item, amount.clone())?,
            amount,
            terminal_uuid: Secret::new(terminal_uuid),
            signature: None,
            url_redirect: item.request.get_return_url()?,
        };
        checkout_request.signature = Some(get_checkout_signature(
            &checkout_request,
            &applepay_session_data,
        )?);
        Ok(Self::CheckoutRequest(Box::new(checkout_request)))
    }
}
*/

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &WalletData,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, wallet_data): (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &WalletData,
        ),
    ) -> Result<Self, Self::Error> {
        let amount = item.amount.to_owned();
        let connector_meta = item.router_data.get_connector_meta()?;
        let session: SessionObject = connector_meta
            .parse_value("SessionObject")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let (specified_payment_channel, session_data) = match wallet_data {
            WalletData::ApplePayRedirect(_) => (
                ZenPaymentChannels::PclApplepay,
                session
                    .apple_pay
                    .ok_or(errors::ConnectorError::InvalidWalletToken {
                        wallet_name: "Apple Pay".to_string(),
                    })?,
            ),
            WalletData::GooglePayRedirect(_) => (
                ZenPaymentChannels::PclGooglepay,
                session
                    .google_pay
                    .ok_or(errors::ConnectorError::InvalidWalletToken {
                        wallet_name: "Google Pay".to_string(),
                    })?,
            ),
            WalletData::WeChatPayRedirect(_)
            | WalletData::PaypalRedirect(_)
            | WalletData::AmazonPay(_)
            | WalletData::ApplePay(_)
            | WalletData::GooglePay(_)
            | WalletData::BluecodeRedirect {}
            | WalletData::AliPayQr(_)
            | WalletData::AliPayRedirect(_)
            | WalletData::AliPayHkRedirect(_)
            | WalletData::AmazonPayRedirect(_)
            | WalletData::Paysera(_)
            | WalletData::Skrill(_)
            | WalletData::MomoRedirect(_)
            | WalletData::KakaoPayRedirect(_)
            | WalletData::GoPayRedirect(_)
            | WalletData::GcashRedirect(_)
            | WalletData::ApplePayThirdPartySdk(_)
            | WalletData::DanaRedirect {}
            | WalletData::GooglePayThirdPartySdk(_)
            | WalletData::MbWayRedirect(_)
            | WalletData::MobilePayRedirect(_)
            | WalletData::PaypalSdk(_)
            | WalletData::Paze(_)
            | WalletData::SamsungPay(_)
            | WalletData::TwintRedirect {}
            | WalletData::VippsRedirect {}
            | WalletData::TouchNGoRedirect(_)
            | WalletData::CashappQr(_)
            | WalletData::SwishQr(_)
            | WalletData::WeChatPayQr(_)
            | WalletData::Mifinity(_)
            | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            ))?,
        };
        let terminal_uuid = session_data
            .terminal_uuid
            .clone()
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?
            .expose();
        let mut checkout_request = CheckoutRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            specified_payment_channel,
            currency: item.router_data.request.currency,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
            terminal_uuid: Secret::new(terminal_uuid),
            signature: None,
            url_redirect: item.router_data.request.get_router_return_url()?,
        };
        checkout_request.signature =
            Some(get_checkout_signature(&checkout_request, &session_data)?);
        Ok(Self::CheckoutRequest(Box::new(checkout_request)))
    }
}

fn get_checkout_signature(
    checkout_request: &CheckoutRequest,
    session: &WalletSessionData,
) -> Result<Secret<String>, error_stack::Report<errors::ConnectorError>> {
    let pay_wall_secret = session
        .pay_wall_secret
        .clone()
        .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
    let mut signature_data = get_signature_data(checkout_request)?;
    signature_data.push_str(&pay_wall_secret.expose());
    let payload_digest = digest::digest(&digest::SHA256, signature_data.as_bytes());
    let mut signature = hex::encode(payload_digest);
    signature.push_str(";sha256");
    Ok(Secret::new(signature))
}

/// Fields should be in alphabetical order
fn get_signature_data(
    checkout_request: &CheckoutRequest,
) -> Result<String, errors::ConnectorError> {
    let specified_payment_channel = match checkout_request.specified_payment_channel {
        ZenPaymentChannels::PclCard => "pcl_card",
        ZenPaymentChannels::PclGooglepay => "pcl_googlepay",
        ZenPaymentChannels::PclApplepay => "pcl_applepay",
        ZenPaymentChannels::PclBoacompraBoleto => "pcl_boacompra_boleto",
        ZenPaymentChannels::PclBoacompraEfecty => "pcl_boacompra_efecty",
        ZenPaymentChannels::PclBoacompraMultibanco => "pcl_boacompra_multibanco",
        ZenPaymentChannels::PclBoacompraPagoefectivo => "pcl_boacompra_pagoefectivo",
        ZenPaymentChannels::PclBoacompraPix => "pcl_boacompra_pix",
        ZenPaymentChannels::PclBoacompraPse => "pcl_boacompra_pse",
        ZenPaymentChannels::PclBoacompraRedcompra => "pcl_boacompra_redcompra",
        ZenPaymentChannels::PclBoacompraRedpagos => "pcl_boacompra_redpagos",
    };
    let mut signature_data = vec![
        format!("amount={}", checkout_request.amount),
        format!("currency={}", checkout_request.currency),
        format!("customipnurl={}", checkout_request.custom_ipn_url),
    ];
    for index in 0..checkout_request.items.len() {
        let prefix = format!("items[{index}].");
        let checkout_request_items = checkout_request
            .items
            .get(index)
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        signature_data.push(format!(
            "{prefix}lineamounttotal={}",
            checkout_request_items.line_amount_total
        ));
        signature_data.push(format!("{prefix}name={}", checkout_request_items.name));
        signature_data.push(format!("{prefix}price={}", checkout_request_items.price));
        signature_data.push(format!(
            "{prefix}quantity={}",
            checkout_request_items.quantity
        ));
    }
    signature_data.push(format!(
        "merchanttransactionid={}",
        checkout_request.merchant_transaction_id
    ));
    signature_data.push(format!(
        "specifiedpaymentchannel={specified_payment_channel}"
    ));
    signature_data.push(format!(
        "terminaluuid={}",
        checkout_request.terminal_uuid.peek()
    ));
    signature_data.push(format!("urlredirect={}", checkout_request.url_redirect));
    let signature = signature_data.join("&");
    Ok(signature.to_lowercase())
}

fn get_customer(
    item: &types::PaymentsAuthorizeRouterData,
    ip: Secret<String, pii::IpAddress>,
) -> Result<ZenCustomerDetails, error_stack::Report<errors::ConnectorError>> {
    Ok(ZenCustomerDetails {
        email: item.request.get_email()?,
        ip,
    })
}

fn get_item_object(
    item: &types::PaymentsAuthorizeRouterData,
) -> Result<Vec<ZenItemObject>, error_stack::Report<errors::ConnectorError>> {
    let order_details = item.request.get_order_details()?;

    order_details
        .iter()
        .map(|data| {
            Ok(ZenItemObject {
                name: data.product_name.clone(),
                quantity: data.quantity,
                price: utils::to_currency_base_unit_with_zero_decimal_check(
                    data.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future.
                    item.request.currency,
                )?,
                line_amount_total: (f64::from(data.quantity)
                    * utils::to_currency_base_unit_asf64(
                        data.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future.
                        item.request.currency,
                    )?)
                .to_string(),
            })
        })
        .collect::<Result<_, _>>()
}

fn get_browser_details(
    browser_info: &BrowserInformation,
) -> CustomResult<ZenBrowserDetails, errors::ConnectorError> {
    let screen_height = browser_info
        .screen_height
        .get_required_value("screen_height")
        .change_context(errors::ConnectorError::MissingRequiredField {
            field_name: "screen_height",
        })?;

    let screen_width = browser_info
        .screen_width
        .get_required_value("screen_width")
        .change_context(errors::ConnectorError::MissingRequiredField {
            field_name: "screen_width",
        })?;

    let window_size = match (screen_height, screen_width) {
        (250, 400) => "01",
        (390, 400) => "02",
        (500, 600) => "03",
        (600, 400) => "04",
        _ => "05",
    }
    .to_string();

    Ok(ZenBrowserDetails {
        color_depth: browser_info.get_color_depth()?.to_string(),
        java_enabled: browser_info.get_java_enabled()?,
        lang: browser_info.get_language()?,
        screen_height: screen_height.to_string(),
        screen_width: screen_width.to_string(),
        timezone: browser_info.get_time_zone()?.to_string(),
        accept_header: browser_info.get_accept_header()?,
        user_agent: browser_info.get_user_agent()?,
        window_size,
    })
}

impl TryFrom<&ZenRouterData<&types::PaymentsAuthorizeRouterData>> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match &item.router_data.request.payment_method_data {
            PaymentMethodData::Card(card) => Self::try_from((item, card)),
            PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, wallet_data)),
            PaymentMethodData::Voucher(voucher_data) => Self::try_from((item, voucher_data)),
            PaymentMethodData::BankTransfer(bank_transfer_data) => {
                Self::try_from((item, bank_transfer_data))
            }
            PaymentMethodData::BankRedirect(bank_redirect_data) => {
                Self::try_from(bank_redirect_data)
            }
            PaymentMethodData::PayLater(paylater_data) => Self::try_from(paylater_data),
            PaymentMethodData::BankDebit(bank_debit_data) => Self::try_from(bank_debit_data),
            PaymentMethodData::CardRedirect(car_redirect_data) => Self::try_from(car_redirect_data),
            PaymentMethodData::GiftCard(gift_card_data) => Self::try_from(gift_card_data.as_ref()),
            PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                ))?
            }
        }
    }
}

impl TryFrom<&BankRedirectData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &BankRedirectData) -> Result<Self, Self::Error> {
        match value {
            BankRedirectData::Ideal { .. }
            | BankRedirectData::Sofort { .. }
            | BankRedirectData::BancontactCard { .. }
            | BankRedirectData::Blik { .. }
            | BankRedirectData::Trustly { .. }
            | BankRedirectData::Eft { .. }
            | BankRedirectData::Eps { .. }
            | BankRedirectData::Giropay { .. }
            | BankRedirectData::Przelewy24 { .. }
            | BankRedirectData::Bizum {}
            | BankRedirectData::Interac { .. }
            | BankRedirectData::OnlineBankingCzechRepublic { .. }
            | BankRedirectData::OnlineBankingFinland { .. }
            | BankRedirectData::OnlineBankingPoland { .. }
            | BankRedirectData::OnlineBankingSlovakia { .. }
            | BankRedirectData::OpenBankingUk { .. }
            | BankRedirectData::OnlineBankingFpx { .. }
            | BankRedirectData::OnlineBankingThailand { .. }
            | BankRedirectData::LocalBankRedirect {} => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                )
                .into())
            }
        }
    }
}

impl TryFrom<&PayLaterData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &PayLaterData) -> Result<Self, Self::Error> {
        match value {
            PayLaterData::KlarnaRedirect { .. }
            | PayLaterData::KlarnaSdk { .. }
            | PayLaterData::AffirmRedirect {}
            | PayLaterData::AfterpayClearpayRedirect { .. }
            | PayLaterData::PayBrightRedirect {}
            | PayLaterData::WalleyRedirect {}
            | PayLaterData::AlmaRedirect {}
            | PayLaterData::FlexitiRedirect {}
            | PayLaterData::AtomeRedirect {}
            | PayLaterData::BreadpayRedirect {} => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

impl TryFrom<&BankDebitData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &BankDebitData) -> Result<Self, Self::Error> {
        match value {
            BankDebitData::AchBankDebit { .. }
            | BankDebitData::SepaBankDebit { .. }
            | BankDebitData::BecsBankDebit { .. }
            | BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

impl TryFrom<&CardRedirectData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &CardRedirectData) -> Result<Self, Self::Error> {
        match value {
            CardRedirectData::Knet {}
            | CardRedirectData::Benefit {}
            | CardRedirectData::MomoAtm {}
            | CardRedirectData::CardRedirect {} => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

impl TryFrom<&GiftCardData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &GiftCardData) -> Result<Self, Self::Error> {
        match value {
            GiftCardData::PaySafeCard {}
            | GiftCardData::Givex(_)
            | GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

// PaymentsResponse
#[derive(Debug, Default, Deserialize, Clone, strum::Display, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ZenPaymentStatus {
    Authorized,
    Accepted,
    #[default]
    Pending,
    Rejected,
    Canceled,
}

impl ForeignTryFrom<(ZenPaymentStatus, Option<ZenActions>)> for enums::AttemptStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(item: (ZenPaymentStatus, Option<ZenActions>)) -> Result<Self, Self::Error> {
        let (item_txn_status, item_action_status) = item;
        Ok(match item_txn_status {
            // Payment has been authorized at connector end, They will send webhook when it gets accepted
            ZenPaymentStatus::Authorized => Self::Pending,
            ZenPaymentStatus::Accepted => Self::Charged,
            ZenPaymentStatus::Pending => {
                item_action_status.map_or(Self::Pending, |action| match action {
                    ZenActions::Redirect => Self::AuthenticationPending,
                })
            }
            ZenPaymentStatus::Rejected => Self::Failure,
            ZenPaymentStatus::Canceled => Self::Voided,
        })
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiResponse {
    status: ZenPaymentStatus,
    id: String,
    // merchant_transaction_id: Option<String>,
    merchant_action: Option<ZenMerchantAction>,
    reject_code: Option<String>,
    reject_reason: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentsResponse {
    ApiResponse(ApiResponse),
    CheckoutResponse(CheckoutResponse),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckoutResponse {
    redirect_url: url::Url,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenMerchantAction {
    action: ZenActions,
    data: ZenMerchantActionData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ZenActions {
    Redirect,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenMerchantActionData {
    redirect_url: url::Url,
}

impl<F, T> TryFrom<ResponseRouterData<F, ZenPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, ZenPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            ZenPaymentsResponse::ApiResponse(response) => Self::try_from(ResponseRouterData {
                response,
                data: item.data,
                http_code: item.http_code,
            }),
            ZenPaymentsResponse::CheckoutResponse(response) => Self::try_from(ResponseRouterData {
                response,
                data: item.data,
                http_code: item.http_code,
            }),
        }
    }
}

fn get_zen_response(
    response: ApiResponse,
    status_code: u16,
) -> CustomResult<
    (
        enums::AttemptStatus,
        Option<ErrorResponse>,
        PaymentsResponseData,
    ),
    errors::ConnectorError,
> {
    let redirection_data_action = response.merchant_action.map(|merchant_action| {
        (
            RedirectForm::from((merchant_action.data.redirect_url, Method::Get)),
            merchant_action.action,
        )
    });
    let (redirection_data, action) = match redirection_data_action {
        Some((redirect_form, action)) => (Some(redirect_form), Some(action)),
        None => (None, None),
    };
    let status = enums::AttemptStatus::foreign_try_from((response.status, action))?;
    let error = if utils::is_payment_failure(status) {
        Some(ErrorResponse {
            code: response
                .reject_code
                .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
            message: response
                .reject_reason
                .clone()
                .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
            reason: response.reject_reason,
            status_code,
            attempt_status: Some(status),
            connector_transaction_id: Some(response.id.clone()),
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    } else {
        None
    };
    let payment_response_data = PaymentsResponseData::TransactionResponse {
        resource_id: ResponseId::ConnectorTransactionId(response.id.clone()),
        redirection_data: Box::new(redirection_data),
        mandate_reference: Box::new(None),
        connector_metadata: None,
        network_txn_id: None,
        connector_response_reference_id: None,
        incremental_authorization_allowed: None,
        charges: None,
    };
    Ok((status, error, payment_response_data))
}

impl<F, T> TryFrom<ResponseRouterData<F, ApiResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: ResponseRouterData<F, ApiResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {


impl api::RefundExecute for Aci {}


File: crates/router/src/core/fraud_check/flows.rs

pub mod checkout_flow;
pub mod fulfillment_flow;
pub mod record_return;
pub mod sale_flow;
pub mod transaction_flow;

use async_trait::async_trait;

use crate::{
    core::{
        errors::RouterResult,
        payments::{self, flows::ConstructFlowSpecificData},
    },
    routes::SessionState,
    services,
    types::{
        api::{Connector, FraudCheckConnectorData},
        domain,
        fraud_check::FraudCheckResponseData,
    },
};

#[async_trait]
pub trait FeatureFrm<F, T> {
    async fn decide_frm_flows<'a>(
        self,
        state: &SessionState,
        connector: &FraudCheckConnectorData,
        call_connector_action: payments::CallConnectorAction,
        merchant_context: &domain::MerchantContext,
    ) -> RouterResult<Self>
    where
        Self: Sized,
        F: Clone,
        dyn Connector: services::ConnectorIntegration<F, T, FraudCheckResponseData>;
}


pub struct DisputeResponseData {
    pub id: String,
    pub amount: MinorUnit,
    pub currency: api_models::enums::Currency,
    pub token: String,
    pub dispute_reason_description: String,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub due_date: Option<PrimitiveDateTime>,
    pub status: RapydWebhookDisputeStatus,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub created_at: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub updated_at: Option<PrimitiveDateTime>,
    pub original_transaction_id: String,
}