File: crates/analytics/src/search.rs
 Public functions: 3

use api_models::analytics::search::{
    GetGlobalSearchRequest, GetSearchRequestWithIndex, GetSearchResponse, OpenMsearchOutput,
    OpensearchOutput, SearchIndex, SearchStatus,
};
use common_utils::errors::{CustomResult, ReportSwitchExt};
use error_stack::ResultExt;
use router_env::tracing;
use serde_json::Value;

use crate::{
    enums::AuthInfo,
    opensearch::{OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder},
};

pub fn convert_to_value<T: Into<Value>>(items: Vec<T>) -> Vec<Value> {
    items.into_iter().map(|item| item.into()).collect()
}

pub async fn msearch_results(
    client: &OpenSearchClient,
    req: GetGlobalSearchRequest,
    search_params: Vec<AuthInfo>,
    indexes: Vec<SearchIndex>,
) -> CustomResult<Vec<GetSearchResponse>, OpenSearchError> {
    if req.query.trim().is_empty()
        && req
            .filters
            .as_ref()
            .map_or(true, |filters| filters.is_all_none())
    {
        return Err(OpenSearchError::BadRequestError(
            "Both query and filters are empty".to_string(),
        )
        .into());
    }
    let mut query_builder = OpenSearchQueryBuilder::new(
        OpenSearchQuery::Msearch(indexes.clone()),
        req.query,
        search_params,
    );

    if let Some(filters) = req.filters {
        if let Some(currency) = filters.currency {
            if !currency.is_empty() {
                query_builder
                    .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency))
                    .switch()?;
            }
        };
        if let Some(status) = filters.status {
            if !status.is_empty() {
                query_builder
                    .add_filter_clause("status.keyword".to_string(), convert_to_value(status))
                    .switch()?;
            }
        };
        if let Some(payment_method) = filters.payment_method {
            if !payment_method.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_method.keyword".to_string(),
                        convert_to_value(payment_method),
                    )
                    .switch()?;
            }
        };
        if let Some(customer_email) = filters.customer_email {
            if !customer_email.is_empty() {
                query_builder
                    .add_filter_clause(
                        "customer_email.keyword".to_string(),
                        convert_to_value(
                            customer_email
                                .iter()
                                .filter_map(|email| {
                                    // TODO: Add trait based inputs instead of converting this to strings
                                    serde_json::to_value(email)
                                        .ok()
                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
                                })
                                .collect(),
                        ),
                    )
                    .switch()?;
            }
        };
        if let Some(search_tags) = filters.search_tags {
            if !search_tags.is_empty() {
                query_builder
                    .add_filter_clause(
                        "feature_metadata.search_tags.keyword".to_string(),
                        convert_to_value(
                            search_tags
                                .iter()
                                .filter_map(|search_tag| {
                                    // TODO: Add trait based inputs instead of converting this to strings
                                    serde_json::to_value(search_tag)
                                        .ok()
                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
                                })
                                .collect(),
                        ),
                    )
                    .switch()?;
            }
        };
        if let Some(connector) = filters.connector {
            if !connector.is_empty() {
                query_builder
                    .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector))
                    .switch()?;
            }
        };
        if let Some(payment_method_type) = filters.payment_method_type {
            if !payment_method_type.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_method_type.keyword".to_string(),
                        convert_to_value(payment_method_type),
                    )
                    .switch()?;
            }
        };
        if let Some(card_network) = filters.card_network {
            if !card_network.is_empty() {
                query_builder
                    .add_filter_clause(
                        "card_network.keyword".to_string(),
                        convert_to_value(card_network),
                    )
                    .switch()?;
            }
        };
        if let Some(card_last_4) = filters.card_last_4 {
            if !card_last_4.is_empty() {
                query_builder
                    .add_filter_clause(
                        "card_last_4.keyword".to_string(),
                        convert_to_value(card_last_4),
                    )
                    .switch()?;
            }
        };
        if let Some(payment_id) = filters.payment_id {
            if !payment_id.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_id.keyword".to_string(),
                        convert_to_value(payment_id),
                    )
                    .switch()?;
            }
        };
        if let Some(amount) = filters.amount {
            if !amount.is_empty() {
                query_builder
                    .add_filter_clause("amount".to_string(), convert_to_value(amount))
                    .switch()?;
            }
        };
        if let Some(customer_id) = filters.customer_id {
            if !customer_id.is_empty() {
                query_builder
                    .add_filter_clause(
                        "customer_id.keyword".to_string(),
                        convert_to_value(customer_id),
                    )
                    .switch()?;
            }
        };
    };

    if let Some(time_range) = req.time_range {
        query_builder.set_time_range(time_range.into()).switch()?;
    };

    let response_text: OpenMsearchOutput = client
        .execute(query_builder)
        .await
        .change_context(OpenSearchError::ConnectionError)?
        .text()
        .await
        .change_context(OpenSearchError::ResponseError)
        .and_then(|body: String| {
            serde_json::from_str::<OpenMsearchOutput>(&body)
                .change_context(OpenSearchError::DeserialisationError)
                .attach_printable(body.clone())
        })?;

    let response_body: OpenMsearchOutput = response_text;

    Ok(response_body
        .responses
        .into_iter()
        .zip(indexes)
        .map(|(index_hit, index)| match index_hit {
            OpensearchOutput::Success(success) => GetSearchResponse {
                count: success.hits.total.value,
                index,
                hits: success
                    .hits
                    .hits
                    .into_iter()
                    .map(|hit| hit.source)
                    .collect(),
                status: SearchStatus::Success,
            },
            OpensearchOutput::Error(error) => {
                tracing::error!(
                    index = ?index,
                    error_response = ?error,
                    "Search error"
                );
                GetSearchResponse {
                    count: 0,
                    index,
                    hits: Vec::new(),
                    status: SearchStatus::Failure,
                }
            }
        })
        .collect())
}

pub async fn search_results(
    client: &OpenSearchClient,
    req: GetSearchRequestWithIndex,
    search_params: Vec<AuthInfo>,
) -> CustomResult<GetSearchResponse, OpenSearchError> {
    let search_req = req.search_req;
    if search_req.query.trim().is_empty()
        && search_req
            .filters
            .as_ref()
            .map_or(true, |filters| filters.is_all_none())
    {
        return Err(OpenSearchError::BadRequestError(
            "Both query and filters are empty".to_string(),
        )
        .into());
    }
    let mut query_builder = OpenSearchQueryBuilder::new(
        OpenSearchQuery::Search(req.index),
        search_req.query,
        search_params,
    );

    if let Some(filters) = search_req.filters {
        if let Some(currency) = filters.currency {
            if !currency.is_empty() {
                query_builder
                    .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency))
                    .switch()?;
            }
        };
        if let Some(status) = filters.status {
            if !status.is_empty() {
                query_builder
                    .add_filter_clause("status.keyword".to_string(), convert_to_value(status))
                    .switch()?;
            }
        };
        if let Some(payment_method) = filters.payment_method {
            if !payment_method.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_method.keyword".to_string(),
                        convert_to_value(payment_method),
                    )
                    .switch()?;
            }
        };
        if let Some(customer_email) = filters.customer_email {
            if !customer_email.is_empty() {
                query_builder
                    .add_filter_clause(
                        "customer_email.keyword".to_string(),
                        convert_to_value(
                            customer_email
                                .iter()
                                .filter_map(|email| {
                                    // TODO: Add trait based inputs instead of converting this to strings
                                    serde_json::to_value(email)
                                        .ok()
                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
                                })
                                .collect(),
                        ),
                    )
                    .switch()?;
            }
        };
        if let Some(search_tags) = filters.search_tags {
            if !search_tags.is_empty() {
                query_builder
                    .add_filter_clause(
                        "feature_metadata.search_tags.keyword".to_string(),
                        convert_to_value(
                            search_tags
                                .iter()
                                .filter_map(|search_tag| {
                                    // TODO: Add trait based inputs instead of converting this to strings
                                    serde_json::to_value(search_tag)
                                        .ok()
                                        .and_then(|a| a.as_str().map(|a| a.to_string()))
                                })
                                .collect(),
                        ),
                    )
                    .switch()?;
            }
        };
        if let Some(connector) = filters.connector {
            if !connector.is_empty() {
                query_builder
                    .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector))
                    .switch()?;
            }
        };
        if let Some(payment_method_type) = filters.payment_method_type {
            if !payment_method_type.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_method_type.keyword".to_string(),
                        convert_to_value(payment_method_type),
                    )
                    .switch()?;
            }
        };
        if let Some(card_network) = filters.card_network {
            if !card_network.is_empty() {
                query_builder
                    .add_filter_clause(
                        "card_network.keyword".to_string(),
                        convert_to_value(card_network),
                    )
                    .switch()?;
            }
        };
        if let Some(card_last_4) = filters.card_last_4 {
            if !card_last_4.is_empty() {
                query_builder
                    .add_filter_clause(
                        "card_last_4.keyword".to_string(),
                        convert_to_value(card_last_4),
                    )
                    .switch()?;
            }
        };
        if let Some(payment_id) = filters.payment_id {
            if !payment_id.is_empty() {
                query_builder
                    .add_filter_clause(
                        "payment_id.keyword".to_string(),
                        convert_to_value(payment_id),
                    )
                    .switch()?;
            }
        };
        if let Some(amount) = filters.amount {
            if !amount.is_empty() {
                query_builder
                    .add_filter_clause("amount".to_string(), convert_to_value(amount))
                    .switch()?;
            }
        };
        if let Some(customer_id) = filters.customer_id {
            if !customer_id.is_empty() {
                query_builder
                    .add_filter_clause(
                        "customer_id.keyword".to_string(),
                        convert_to_value(customer_id),
                    )
                    .switch()?;
            }
        };
    };

    if let Some(time_range) = search_req.time_range {
        query_builder.set_time_range(time_range.into()).switch()?;
    };

    query_builder
        .set_offset_n_count(search_req.offset, search_req.count)
        .switch()?;

    let response_text: OpensearchOutput = client
        .execute(query_builder)
        .await
        .change_context(OpenSearchError::ConnectionError)?
        .text()
        .await
        .change_context(OpenSearchError::ResponseError)
        .and_then(|body: String| {
            serde_json::from_str::<OpensearchOutput>(&body)
                .change_context(OpenSearchError::DeserialisationError)
                .attach_printable(body.clone())
        })?;

    let response_body: OpensearchOutput = response_text;

    match response_body {
        OpensearchOutput::Success(success) => Ok(GetSearchResponse {
            count: success.hits.total.value,
            index: req.index,
            hits: success
                .hits
                .hits
                .into_iter()
                .map(|hit| hit.source)
                .collect(),
            status: SearchStatus::Success,
        }),
        OpensearchOutput::Error(error) => {
            tracing::error!(
                index = ?req.index,
                error_response = ?error,
                "Search error"
            );
            Ok(GetSearchResponse {
                count: 0,
                index: req.index,
                hits: Vec::new(),
                status: SearchStatus::Failure,
            })
        }
    }
}


File: crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::RefundMetricRow;
use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(crate) struct RefundSuccessCount {}

#[async_trait::async_trait]
impl<T> super::RefundMetric<T> for RefundSuccessCount
where
    T: AnalyticsDataSource + super::RefundMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[RefundDimensions],
        auth: &AuthInfo,
        filters: &RefundFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(RefundMetricsBucketIdentifier, RefundMetricRow)>>
    where
        T: AnalyticsDataSource + super::RefundMetricAnalytics,
    {
        let mut query_builder = QueryBuilder::new(AnalyticsCollection::RefundSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        time_range.set_filter_clause(&mut query_builder).switch()?;

        for dim in dimensions.iter() {
            query_builder.add_group_by_clause(dim).switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                RefundDimensions::RefundStatus,
                storage_enums::RefundStatus::Success,
            )
            .switch()?;
        query_builder
            .execute_query::<RefundMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    RefundMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.refund_type.as_ref().map(|i| i.0.to_string()),
                        i.profile_id.clone(),
                        i.refund_reason.clone(),
                        i.refund_error_message.clone(),
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(RefundMetricsBucketIdentifier, RefundMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


pub struct PaypalAuthUpdateResponse {
    pub access_token: Secret<String>,
    pub token_type: String,
    pub expires_in: i64,
}


pub struct AirwallexPaymentsNextAction {
    url: Url,
    method: Method,
    data: AirwallexRedirectFormData,
    stage: AirwallexNextActionStage,
}


/// Performs Payment Setup Mandate
    pub async fn payment_setup_mandate(
        &self,
        payment_register_request: payments_grpc::PaymentServiceRegisterRequest,
        connector_auth_metadata: ConnectorAuthMetadata,
        grpc_headers: GrpcHeaders,
    ) -> UnifiedConnectorServiceResult<tonic::Response<payments_grpc::PaymentServiceRegisterResponse>>
    {
        let mut request = tonic::Request::new(payment_register_request);

        let connector_name = connector_auth_metadata.connector_name.clone();
        let metadata = build_unified_connector_service_grpc_headers(
            connector_auth_metadata,
            None,
            grpc_headers,
        )?;
        *request.metadata_mut() = metadata;

        self.client
            .clone()
            .register(request)
            .await
            .change_context(UnifiedConnectorServiceError::PaymentRegisterFailure)
            .inspect_err(|error| {
                logger::error!(
                    grpc_error=?error,
                    method="payment_setup_mandate",
                    connector_name=?connector_name,
                    "UCS payment setup mandate gRPC call failed"
                )
            })
    }


pub struct ProcessingInformationResponse {
    payment_solution: Option<String>,
    commerce_indicator: Option<String>,
    commerce_indicator_label: Option<String>,
    ecommerce_indicator: Option<String>,
}


pub fn construct_uas_router_data<F: Clone, Req, Res>(
    state: &SessionState,
    authentication_connector_name: String,
    payment_method: PaymentMethod,
    merchant_id: common_utils::id_type::MerchantId,
    address: Option<PaymentAddress>,
    request_data: Req,
    merchant_connector_account: &payments::helpers::MerchantConnectorAccountType,
    authentication_id: Option<common_utils::id_type::AuthenticationId>,
    payment_id: Option<common_utils::id_type::PaymentId>,
) -> RouterResult<RouterData<F, Req, Res>> {
    let auth_type: ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing ConnectorAuthType")?;
    Ok(RouterData {
        flow: PhantomData,
        merchant_id,
        customer_id: None,
        connector_customer: None,
        connector: authentication_connector_name,
        payment_id: payment_id
            .map(|id| id.get_string_repr().to_owned())
            .unwrap_or_default(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        status: common_enums::AttemptStatus::default(),
        payment_method,
        connector_auth_type: auth_type,
        description: None,
        address: address.unwrap_or_default(),
        auth_type: common_enums::AuthenticationType::default(),
        connector_meta_data: merchant_connector_account.get_metadata().clone(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_api_version: None,
        request: request_data,
        response: Err(ErrorResponse::default()),
        connector_request_reference_id:
            IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        dispute_id: None,
        refund_id: None,
        payment_method_status: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload: None,
        connector_mandate_request_reference_id: None,
        authentication_id,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
    })
}


pub async fn find_process_by_id(conn: &PgPooledConn, id: &str) -> StorageResult<Option<Self>> {
        generics::generic_find_by_id_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            id.to_owned(),
        )
        .await
    }


pub struct ExtractedPayload {
    pub payload_type: PayloadType,
    pub merchant_id: Option<MerchantId>,
    pub key_id: Option<ApiKeyId>,
}


impl ConnectorSpecifications for Phonepe {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&*PHONEPE_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*PHONEPE_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&*PHONEPE_SUPPORTED_WEBHOOK_FLOWS)
    }
}

impl ConnectorSpecifications for for Phonepe


File: crates/router/src/core/routing/helpers.rs
 Public functions: 32
 Public structs: 5

//! Analysis for usage of all helper functions for use case of routing
//!
//! Functions that are used to perform the retrieval of merchant's
//! routing dict, configs, defaults
use std::fmt::Debug;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use std::str::FromStr;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use std::sync::Arc;

#[cfg(feature = "v1")]
use api_models::open_router;
use api_models::routing as routing_types;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use common_utils::ext_traits::ValueExt;
use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState};
use diesel_models::configs;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use diesel_models::dynamic_routing_stats::{DynamicRoutingStatsNew, DynamicRoutingStatsUpdate};
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use diesel_models::routing_algorithm;
use error_stack::ResultExt;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use external_services::grpc_client::dynamic_routing::{
    contract_routing_client::ContractBasedDynamicRouting,
    elimination_based_client::EliminationBasedRouting,
    success_rate_client::SuccessBasedDynamicRouting,
};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use hyperswitch_domain_models::api::ApplicationResponse;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use hyperswitch_interfaces::events::routing_api_logs as routing_events;
#[cfg(feature = "v1")]
use router_env::logger;
#[cfg(feature = "v1")]
use router_env::{instrument, tracing};
use rustc_hash::FxHashSet;
use storage_impl::redis::cache;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use storage_impl::redis::cache::Cacheable;

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::db::errors::StorageErrorExt;
#[cfg(feature = "v2")]
use crate::types::domain::MerchantConnectorAccount;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::types::transformers::ForeignFrom;
use crate::{
    core::errors::{self, RouterResult},
    db::StorageInterface,
    routes::SessionState,
    types::{domain, storage},
    utils::StringExt,
};
#[cfg(feature = "v1")]
use crate::{
    core::payments::{
        routing::utils::{self as routing_utils, DecisionEngineApiHandler},
        OperationSessionGetters, OperationSessionSetters,
    },
    services,
};
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::{
    core::{metrics as core_metrics, routing},
    routes::app::SessionStateInfo,
    types::transformers::ForeignInto,
};
pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Success rate based dynamic routing algorithm";
pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Elimination based dynamic routing algorithm";
pub const CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Contract based dynamic routing algorithm";

pub const DECISION_ENGINE_RULE_CREATE_ENDPOINT: &str = "rule/create";
pub const DECISION_ENGINE_RULE_UPDATE_ENDPOINT: &str = "rule/update";
pub const DECISION_ENGINE_RULE_GET_ENDPOINT: &str = "rule/get";
pub const DECISION_ENGINE_RULE_DELETE_ENDPOINT: &str = "rule/delete";
pub const DECISION_ENGINE_MERCHANT_BASE_ENDPOINT: &str = "merchant-account";
pub const DECISION_ENGINE_MERCHANT_CREATE_ENDPOINT: &str = "merchant-account/create";

/// Provides us with all the configured configs of the Merchant in the ascending time configured
/// manner and chooses the first of them
pub async fn get_merchant_default_config(
    db: &dyn StorageInterface,
    // Cannot make this as merchant id domain type because, we are passing profile id also here
    merchant_id: &str,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<Vec<routing_types::RoutableConnectorChoice>> {
    let key = get_default_config_key(merchant_id, transaction_type);
    let maybe_config = db.find_config_by_key(&key).await;

    match maybe_config {
        Ok(config) => config
            .config
            .parse_struct("Vec<RoutableConnectors>")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Merchant default config has invalid structure"),

        Err(e) if e.current_context().is_db_not_found() => {
            let new_config_conns = Vec::<routing_types::RoutableConnectorChoice>::new();
            let serialized = new_config_conns
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "Error while creating and serializing new merchant default config",
                )?;

            let new_config = configs::ConfigNew {
                key,
                config: serialized,
            };

            db.insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error inserting new default routing config into DB")?;

            Ok(new_config_conns)
        }

        Err(e) => Err(e)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error fetching default config for merchant"),
    }
}

/// Merchant's already created config can be updated and this change will be reflected
/// in DB as well for the particular updated config
pub async fn update_merchant_default_config(
    db: &dyn StorageInterface,
    merchant_id: &str,
    connectors: Vec<routing_types::RoutableConnectorChoice>,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<()> {
    let key = get_default_config_key(merchant_id, transaction_type);
    let config_str = connectors
        .encode_to_string_of_json()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to serialize merchant default routing config during update")?;

    let config_update = configs::ConfigUpdate::Update {
        config: Some(config_str),
    };

    db.update_config_by_key(&key, config_update)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error updating the default routing config in DB")?;

    Ok(())
}

pub async fn update_merchant_routing_dictionary(
    db: &dyn StorageInterface,
    merchant_id: &str,
    dictionary: routing_types::RoutingDictionary,
) -> RouterResult<()> {
    let key = get_routing_dictionary_key(merchant_id);
    let dictionary_str = dictionary
        .encode_to_string_of_json()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to serialize routing dictionary during update")?;

    let config_update = configs::ConfigUpdate::Update {
        config: Some(dictionary_str),
    };

    db.update_config_by_key(&key, config_update)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error saving routing dictionary to DB")?;

    Ok(())
}

/// This will help make one of all configured algorithms to be in active state for a particular
/// merchant
#[cfg(feature = "v1")]
pub async fn update_merchant_active_algorithm_ref(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    config_key: cache::CacheKind<'_>,
    algorithm_id: routing_types::RoutingAlgorithmRef,
) -> RouterResult<()> {
    let ref_value = algorithm_id
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed converting routing algorithm ref to json value")?;

    let merchant_account_update = storage::MerchantAccountUpdate::Update {
        merchant_name: None,
        merchant_details: None,
        return_url: None,
        webhook_details: None,
        sub_merchants_enabled: None,
        parent_merchant_id: None,
        enable_payment_response_hash: None,
        payment_response_hash_key: None,
        redirect_to_merchant_with_http_post: None,
        publishable_key: None,
        locker_id: None,
        metadata: None,
        routing_algorithm: Some(ref_value),
        primary_business_details: None,
        intent_fulfillment_time: None,
        frm_routing_algorithm: None,
        payout_routing_algorithm: None,
        default_profile: None,
        payment_link_config: None,
        pm_collect_link_config: None,
    };

    let db = &*state.store;
    db.update_specific_fields_in_merchant(
        &state.into(),
        &key_store.merchant_id,
        merchant_account_update,
        key_store,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update routing algorithm ref in merchant account")?;

    cache::redact_from_redis_and_publish(db.get_cache_store().as_ref(), [config_key])
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to invalidate the config cache")?;

    Ok(())
}

#[cfg(feature = "v1")]
pub async fn update_profile_active_algorithm_ref(
    db: &dyn StorageInterface,
    key_manager_state: &KeyManagerState,
    merchant_key_store: &domain::MerchantKeyStore,
    current_business_profile: domain::Profile,
    algorithm_id: routing_types::RoutingAlgorithmRef,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<()> {
    let ref_val = algorithm_id
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to convert routing ref to value")?;

    let merchant_id = current_business_profile.merchant_id.clone();

    let profile_id = current_business_profile.get_id().to_owned();

    let (routing_algorithm, payout_routing_algorithm, three_ds_decision_rule_algorithm) =
        match transaction_type {
            storage::enums::TransactionType::Payment => (Some(ref_val), None, None),
            #[cfg(feature = "payouts")]
            storage::enums::TransactionType::Payout => (None, Some(ref_val), None),
            storage::enums::TransactionType::ThreeDsAuthentication => (None, None, Some(ref_val)),
        };

    let business_profile_update = domain::ProfileUpdate::RoutingAlgorithmUpdate {
        routing_algorithm,
        payout_routing_algorithm,
        three_ds_decision_rule_algorithm,
    };

    db.update_profile_by_profile_id(
        key_manager_state,
        merchant_key_store,
        current_business_profile,
        business_profile_update,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update routing algorithm ref in business profile")?;

    // Invalidate the routing cache for Payments and Payouts transaction types
    if !transaction_type.is_three_ds_authentication() {
        let routing_cache_key = cache::CacheKind::Routing(
            format!(
                "routing_config_{}_{}",
                merchant_id.get_string_repr(),
                profile_id.get_string_repr(),
            )
            .into(),
        );

        cache::redact_from_redis_and_publish(db.get_cache_store().as_ref(), [routing_cache_key])
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to invalidate routing cache")?;
    }
    Ok(())
}

#[cfg(feature = "v1")]
pub async fn update_business_profile_active_dynamic_algorithm_ref(
    db: &dyn StorageInterface,
    key_manager_state: &KeyManagerState,
    merchant_key_store: &domain::MerchantKeyStore,
    current_business_profile: domain::Profile,
    dynamic_routing_algorithm_ref: routing_types::DynamicRoutingAlgorithmRef,
) -> RouterResult<()> {
    let ref_val = dynamic_routing_algorithm_ref
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to convert dynamic routing ref to value")?;
    let business_profile_update = domain::ProfileUpdate::DynamicRoutingAlgorithmUpdate {
        dynamic_routing_algorithm: Some(ref_val),
    };
    db.update_profile_by_profile_id(
        key_manager_state,
        merchant_key_store,
        current_business_profile,
        business_profile_update,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update dynamic routing algorithm ref in business profile")?;
    Ok(())
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug)]
pub struct RoutingAlgorithmHelpers<'h> {
    pub name_mca_id_set: ConnectNameAndMCAIdForProfile<'h>,
    pub name_set: ConnectNameForProfile<'h>,
    pub routing_algorithm: &'h routing_types::StaticRoutingAlgorithm,
}

#[cfg(feature = "v1")]
pub enum RoutingDecisionData {
    DebitRouting(DebitRoutingDecisionData),
}
#[cfg(feature = "v1")]
pub struct DebitRoutingDecisionData {
    pub card_network: common_enums::enums::CardNetwork,
    pub debit_routing_result: Option<open_router::DebitRoutingOutput>,
}
#[cfg(feature = "v1")]
impl RoutingDecisionData {
    pub fn apply_routing_decision<F, D>(&self, payment_data: &mut D)
    where
        F: Send + Clone,
        D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    {
        match self {
            Self::DebitRouting(data) => data.apply_debit_routing_decision(payment_data),
        }
    }

    pub fn get_debit_routing_decision_data(
        network: common_enums::enums::CardNetwork,
        debit_routing_result: Option<open_router::DebitRoutingOutput>,
    ) -> Self {
        Self::DebitRouting(DebitRoutingDecisionData {
            card_network: network,
            debit_routing_result,
        })
    }
}
#[cfg(feature = "v1")]
impl DebitRoutingDecisionData {
    pub fn apply_debit_routing_decision<F, D>(&self, payment_data: &mut D)
    where
        F: Send + Clone,
        D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    {
        payment_data.set_card_network(self.card_network.clone());
        self.debit_routing_result
            .as_ref()
            .map(|data| payment_data.set_co_badged_card_data(data));
    }
}
#[derive(Clone, Debug)]
pub struct ConnectNameAndMCAIdForProfile<'a>(
    pub  FxHashSet<(
        &'a common_enums::connector_enums::Connector,
        id_type::MerchantConnectorAccountId,
    )>,
);
#[derive(Clone, Debug)]
pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a common_enums::connector_enums::Connector>);

#[cfg(feature = "v2")]
impl RoutingAlgorithmHelpers<'_> {
    fn connector_choice(
        &self,
        choice: &routing_types::RoutableConnectorChoice,
    ) -> RouterResult<()> {
        if let Some(ref mca_id) = choice.merchant_connector_id {
            let connector_choice = common_enums::connector_enums::Connector::from(choice.connector);
            error_stack::ensure!(
                self.name_mca_id_set.0.contains(&(&connector_choice, mca_id.clone())),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{connector_choice}' and merchant connector account id '{mca_id:?}' not found for the given profile",
                    )
                }
            );
        } else {
            let connector_choice = common_enums::connector_enums::Connector::from(choice.connector);
            error_stack::ensure!(
                self.name_set.0.contains(&connector_choice),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{connector_choice}' not found for the given profile",
                    )
                }
            );
        };
        Ok(())
    }

    pub fn validate_connectors_in_routing_config(&self) -> RouterResult<()> {
        match self.routing_algorithm {
            routing_types::StaticRoutingAlgorithm::Single(choice) => {
                self.connector_choice(choice)?;
            }

            routing_types::StaticRoutingAlgorithm::Priority(list) => {
                for choice in list {
                    self.connector_choice(choice)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => {
                for split in splits {
                    self.connector_choice(&split.connector)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::Advanced(program) => {
                let check_connector_selection =
                    |selection: &routing_types::ConnectorSelection| -> RouterResult<()> {
                        match selection {
                            routing_types::ConnectorSelection::VolumeSplit(splits) => {
                                for split in splits {
                                    self.connector_choice(&split.connector)?;
                                }
                            }

                            routing_types::ConnectorSelection::Priority(list) => {
                                for choice in list {
                                    self.connector_choice(choice)?;
                                }
                            }
                        }

                        Ok(())
                    };

                check_connector_selection(&program.default_selection)?;

                for rule in &program.rules {
                    check_connector_selection(&rule.connector_selection)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::ThreeDsDecisionRule(_) => {
                return Err(errors::ApiErrorResponse::InternalServerError).attach_printable(
                    "Invalid routing algorithm three_ds decision rule received",
                )?;
            }
        }

        Ok(())
    }
}

#[cfg(feature = "v1")]
pub async fn validate_connectors_in_routing_config(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    merchant_id: &id_type::MerchantId,
    profile_id: &id_type::ProfileId,
    routing_algorithm: &routing_types::StaticRoutingAlgorithm,
) -> RouterResult<()> {
    let all_mcas = state
        .store
        .find_merchant_connector_account_by_merchant_id_and_disabled_list(
            &state.into(),
            merchant_id,
            true,
            key_store,
        )
        .await
        .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: merchant_id.get_string_repr().to_owned(),
        })?;
    let name_mca_id_set = all_mcas
        .iter()
        .filter(|mca| mca.profile_id == *profile_id)
        .map(|mca| (&mca.connector_name, mca.get_id()))
        .collect::<FxHashSet<_>>();

    let name_set = all_mcas
        .iter()
        .filter(|mca| mca.profile_id == *profile_id)
        .map(|mca| &mca.connector_name)
        .collect::<FxHashSet<_>>();

    let connector_choice = |choice: &routing_types::RoutableConnectorChoice| {
        if let Some(ref mca_id) = choice.merchant_connector_id {
            error_stack::ensure!(
                name_mca_id_set.contains(&(&choice.connector.to_string(), mca_id.clone())),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile",
                        choice.connector,
                        mca_id,
                    )
                }
            );
        } else {
            error_stack::ensure!(
                name_set.contains(&choice.connector.to_string()),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{}' not found for the given profile",
                        choice.connector,
                    )
                }
            );
        }

        Ok(())
    };

    match routing_algorithm {
        routing_types::StaticRoutingAlgorithm::Single(choice) => {
            connector_choice(choice)?;
        }

        routing_types::StaticRoutingAlgorithm::Priority(list) => {
            for choice in list {
                connector_choice(choice)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => {
            for split in splits {
                connector_choice(&split.connector)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::Advanced(program) => {
            let check_connector_selection =
                |selection: &routing_types::ConnectorSelection| -> RouterResult<()> {
                    match selection {
                        routing_types::ConnectorSelection::VolumeSplit(splits) => {
                            for split in splits {
                                connector_choice(&split.connector)?;
                            }
                        }

                        routing_types::ConnectorSelection::Priority(list) => {
                            for choice in list {
                                connector_choice(choice)?;
                            }
                        }
                    }

                    Ok(())
                };

            check_connector_selection(&program.default_selection)?;

            for rule in &program.rules {
                check_connector_selection(&rule.connector_selection)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::ThreeDsDecisionRule(_) => {
            Err(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Invalid routing algorithm three_ds decision rule received")?
        }
    }

    Ok(())
}

/// Provides the identifier for the specific merchant's routing_dictionary_key
#[inline(always)]
pub fn get_routing_dictionary_key(merchant_id: &str) -> String {
    format!("routing_dict_{merchant_id}")
}

/// Provides the identifier for the specific merchant's default_config
#[inline(always)]
pub fn get_default_config_key(
    merchant_id: &str,
    transaction_type: &storage::enums::TransactionType,
) -> String {
    match transaction_type {
        storage::enums::TransactionType::Payment => format!("routing_default_{merchant_id}"),
        #[cfg(feature = "payouts")]
        storage::enums::TransactionType::Payout => format!("routing_default_po_{merchant_id}"),
        storage::enums::TransactionType::ThreeDsAuthentication => {
            format!("three_ds_authentication_{merchant_id}")
        }
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
pub trait DynamicRoutingCache {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>>;

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send;
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::SuccessBasedRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::ContractBasedRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::EliminationRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

/// Cfetch dynamic routing configs
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn fetch_dynamic_routing_configs<T>(
    state: &SessionState,
    profile_id: &id_type::ProfileId,
    routing_id: id_type::RoutingId,
) -> RouterResult<T>
where
    T: serde::de::DeserializeOwned
        + Clone
        + DynamicRoutingCache
        + Cacheable
        + serde::Serialize
        + Debug,
{
    let key = format!(
        "{}_{}",
        profile_id.get_string_repr(),
        routing_id.get_string_repr()
    );

    if let Some(config) =
        T::get_cached_dynamic_routing_config_for_profile(state, key.as_str()).await
    {
        Ok(config.as_ref().clone())
    } else {
        let func = || async {
            let routing_algorithm = state
                .store
                .find_routing_algorithm_by_profile_id_algorithm_id(profile_id, &routing_id)
                .await
                .change_context(errors::StorageError::ValueNotFound(
                    "RoutingAlgorithm".to_string(),
                ))
                .attach_printable("unable to retrieve routing_algorithm for profile from db")?;

            let dynamic_routing_config = routing_algorithm
                .algorithm_data
                .parse_value::<T>("dynamic_routing_config")
                .change_context(errors::StorageError::DeserializationFailed)
                .attach_printable("unable to parse dynamic_routing_config")?;

            Ok(dynamic_routing_config)
        };

        let dynamic_routing_config =
            T::refresh_dynamic_routing_cache(state, key.as_str(), func).await?;

        Ok(dynamic_routing_config)
    }
}

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn update_gateway_score_helper_with_open_router(
    state: &SessionState,
    payment_attempt: &storage::PaymentAttempt,
    profile_id: &id_type::ProfileId,
    dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef,
) -> RouterResult<()> {
    let is_success_rate_routing_enabled =
        dynamic_routing_algo_ref.is_success_rate_routing_enabled();
    let is_elimination_enabled = dynamic_routing_algo_ref.is_elimination_enabled();

    if is_success_rate_routing_enabled || is_elimination_enabled {
        let payment_connector = &payment_attempt.connector.clone().ok_or(
            errors::ApiErrorResponse::GenericNotFoundError {
                message: "unable to derive payment connector from payment attempt".to_string(),
            },
        )?;

        let routable_connector = routing_types::RoutableConnectorChoice {
            choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
            connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("unable to infer routable_connector from connector")?,
            merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
        };

        logger::debug!(
            "performing update-gateway-score for gateway with id {} in open_router for profile: {}",
            routable_connector,
            profile_id.get_string_repr()
        );
        routing::payments_routing::update_gateway_score_with_open_router(
            state,
            routable_connector.clone(),
            profile_id,
            &payment_attempt.merchant_id,
            &payment_attempt.payment_id,
            payment_attempt.status,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to update gateway score in open_router service")?;
    }

    Ok(())
}

/// metrics for success based dynamic routing
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn push_metrics_with_update_window_for_success_based_routing(
    state: &SessionState,
    payment_attempt: &storage::PaymentAttempt,
    routable_connectors: Vec<routing_types::RoutableConnectorChoice>,
    profile_id: &id_type::ProfileId,
    dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef,
    dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator,
) -> RouterResult<()> {
    if let Some(success_based_algo_ref) = dynamic_routing_algo_ref.success_based_algorithm {
        if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None {
            let client = &state
                .grpc_client
                .dynamic_routing
                .as_ref()
                .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
                    message: "dynamic routing gRPC client not found".to_string(),
                })?
                .success_rate_client;

            let payment_connector = &payment_attempt.connector.clone().ok_or(
                errors::ApiErrorResponse::GenericNotFoundError {
                    message: "unable to derive payment connector from payment attempt".to_string(),
                },
            )?;

            let routable_connector = routing_types::RoutableConnectorChoice {
                choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
                connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("unable to infer routable_connector from connector")?,
                merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
            };

            let payment_status_attribute =
                get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status);

            let success_based_routing_configs = fetch_dynamic_routing_configs::<
                routing_types::SuccessBasedRoutingConfig,
            >(
                state,
                profile_id,
                success_based_algo_ref
                    .algorithm_id_with_timestamp
                    .algorithm_id
                    .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
                        message: "success_rate algorithm_id not found".to_string(),
                    })
                    .attach_printable(
                        "success_based_routing_algorithm_id not found in business_profile",
                    )?,
            )
            .await
            .change_context(errors::ApiErrorResponse::GenericNotFoundError {
                message: "success_rate based dynamic routing configs not found".to_string(),
            })
            .attach_printable("unable to retrieve success_rate based dynamic routing configs")?;

            let success_based_routing_config_params = dynamic_routing_config_params_interpolator
                .get_string_val(
                    success_based_routing_configs
                        .params
                        .as_ref()
                        .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)
                        .change_context(errors::ApiErrorResponse::InternalServerError)?,
                );

            let success_based_connectors = client
                .calculate_entity_and_global_success_rate(
                    profile_id.get_string_repr().into(),
                    success_based_routing_configs.clone(),
                    success_based_routing_config_params.clone(),
                    routable_connectors.clone(),
                    state.get_grpc_headers(),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to calculate/fetch success rate from dynamic routing service",
                )?;

            let first_merchant_success_based_connector = &success_based_connectors
                .entity_scores_with_labels
                .first()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to fetch the first connector from list of connectors obtained from dynamic routing service",
                )?;

            let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label
                .split_once(':')
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(format!(
                    "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service",
                    first_merchant_success_based_connector.label
                ))?;

            let first_global_success_based_connector = &success_based_connectors
                .global_scores_with_labels
                .first()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to fetch the first global connector from list of connectors obtained from dynamic routing service",
                )?;

            let outcome = get_dynamic_routing_based_metrics_outcome_for_payment(
                payment_status_attribute,
                payment_connector.to_string(),
                first_merchant_success_based_connector_label.to_string(),
            );

            core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add(
                1,
                router_env::metric_attributes!(
                    (
                        "tenant",
                        state.tenant.tenant_id.get_string_repr().to_owned(),
                    ),
                    (
                        "merchant_profile_id",
                        format!(
                            "{}:{}",
                            payment_attempt.merchant_id.get_string_repr(),
                            payment_attempt.profile_id.get_string_repr()
                        ),
                    ),
                    (
                        "merchant_specific_success_based_routing_connector",
                        first_merchant_success_based_connector_label.to_string(),
                    ),
                    (
                        "merchant_specific_success_based_routing_connector_score",
                        first_merchant_success_based_connector.score.to_string(),
                    ),
                    (
                        "global_success_based_routing_connector",
                        first_global_success_based_connector.label.to_string(),
                    ),
                    (
                        "global_success_based_routing_connector_score",
                        first_global_success_based_connector.score.to_string(),
                    ),
                    ("payment_connector", payment_connector.to_string()),
                    (
                        "currency",
                        payment_attempt
                            .currency
                            .map_or_else(|| "None".to_string(), |currency| currency.to_string()),
                    ),
                    (
                        "payment_method",
                        payment_attempt.payment_method.map_or_else(
                            || "None".to_string(),
                            |payment_method| payment_method.to_string(),
                        ),
                    ),
                    (
                        "payment_method_type",
                        payment_attempt.payment_method_type.map_or_else(
                            || "None".to_string(),
                            |payment_method_type| payment_method_type.to_string(),
                        ),
                    ),
                    (
                        "capture_method",
                        payment_attempt.capture_method.map_or_else(
                            || "None".to_string(),
                            |capture_method| capture_method.to_string(),
                        ),
                    ),
                    (
                        "authentication_type",
                        payment_attempt.authentication_type.map_or_else(
                            || "None".to_string(),
                            |authentication_type| authentication_type.to_string(),
                        ),
                    ),
                    ("payment_status", payment_attempt.status.to_string()),
                    ("conclusive_classification", outcome.to_string()),
                ),
            );
            logger::debug!("successfully pushed success_based_routing metrics");

            let duplicate_stats = state
                .store
                .find_dynamic_routing_stats_optional_by_attempt_id_merchant_id(
                    payment_attempt.attempt_id.clone(),
                    &payment_attempt.merchant_id.to_owned(),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to fetch dynamic_routing_stats entry")?;

            if duplicate_stats.is_some() {
                let dynamic_routing_update = DynamicRoutingStatsUpdate {
                    amount: payment_attempt.get_total_amount(),
                    success_based_routing_connector: first_merchant_success_based_connector_label
                        .to_string(),
                    payment_connector: payment_connector.to_string(),
                    payment_method_type: payment_attempt.payment_method_type,
                    currency: payment_attempt.currency,
                    payment_method: payment_attempt.payment_method,
                    capture_method: payment_attempt.capture_method,
                    authentication_type: payment_attempt.authentication_type,
                    payment_status: payment_attempt.status,
                    conclusive_classification: outcome,
                    global_success_based_connector: Some(
                        first_global_success_based_connector.label.to_string(),
                    ),
                };

                state
                    .store
                    .update_dynamic_routing_stats(
                        payment_attempt.attempt_id.clone(),
                        &payment_attempt.merchant_id.to_owned(),
                        dynamic_routing_update,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Unable to update dynamic routing stats to db")?;
            } else {
                let dynamic_routing_stats = DynamicRoutingStatsNew {
                    payment_id: payment_attempt.payment_id.to_owned(),
                    attempt_id: payment_attempt.attempt_id.clone(),
                    merchant_id: payment_attempt.merchant_id.to_owned(),
                    profile_id: payment_attempt.profile_id.to_owned(),
                    amount: payment_attempt.get_total_amount(),
                    success_based_routing_connector: first_merchant_success_based_connector_label
                        .to_string(),
                    payment_connector: payment_connector.to_string(),
                    payment_method_type: payment_attempt.payment_method_type,
                    currency: payment_attempt.currency,


impl Hash for UserRole {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.user_id.hash(state);
        if let Some((entity_id, entity_type)) = self.get_entity_id_and_type() {
            entity_id.hash(state);
            entity_type.hash(state);
        }
    }
}


pub struct WebhookPaymentInformation {
    pub credit_debit_indicator: CreditDebitIndicator,
    pub references: WebhookReferences,
    pub status: WebhookStatus,
    pub amount: WebhookAmount,
    pub status_reason_information: Option<StatusReasonInformation>,
}


impl Fiserv {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &FloatMajorUnitForConnector,
        }
    }
    pub fn generate_authorization_signature(
        &self,
        auth: fiserv::FiservAuthType,
        request_id: &str,
        payload: &str,
        timestamp: i128,
    ) -> CustomResult<String, errors::ConnectorError> {
        let fiserv::FiservAuthType {
            api_key,
            api_secret,
            ..
        } = auth;
        let raw_signature = format!("{}{request_id}{timestamp}{payload}", api_key.peek());

        let key = hmac::Key::new(hmac::HMAC_SHA256, api_secret.expose().as_bytes());
        let signature_value = common_utils::consts::BASE64_ENGINE
            .encode(hmac::sign(&key, raw_signature.as_bytes()).as_ref());
        Ok(signature_value)
    }
}


pub fn open_router_config_default() -> Self {
        Self {
            params: None,
            config: None,
            decision_engine_configs: Some(open_router::DecisionEngineSuccessRateData {
                default_latency_threshold: Some(DEFAULT_LATENCY_THRESHOLD),
                default_bucket_size: Some(DEFAULT_BUCKET_SIZE),
                default_hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
                default_lower_reset_factor: None,
                default_upper_reset_factor: None,
                default_gateway_extra_score: None,
                sub_level_input_config: Some(vec![
                    open_router::DecisionEngineSRSubLevelInputConfig {
                        payment_method_type: Some(DEFAULT_PAYMENT_METHOD.to_string()),
                        payment_method: None,
                        latency_threshold: None,
                        bucket_size: Some(DEFAULT_BUCKET_SIZE),
                        hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
                        lower_reset_factor: None,
                        upper_reset_factor: None,
                        gateway_extra_score: None,
                    },
                ]),
            }),
        }
    }


OpenAPI Block Path: components.schemas.PayoutUpdateRequest
{
  "type": "object",
  "properties": {
    "merchant_order_reference_id": {
      "type": "string",
      "description": "Your unique identifier for this payout or order. This ID helps you reconcile payouts on your system. If provided, it is passed to the connector if supported.",
      "example": "merchant_order_ref_123",
      "nullable": true,
      "maxLength": 255
    },
    "amount": {
      "type": "integer",
      "format": "int64",
      "description": "The payout amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,",
      "example": 1000,
      "nullable": true,
      "minimum": 0
    },
    "currency": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Currency"
        }
      ],
      "nullable": true
    },
    "routing": {
      "allOf": [
        {
          "$ref": "#/components/schemas/StaticRoutingAlgorithm"
        }
      ],
      "nullable": true
    },
    "connector": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/PayoutConnectors"
      },
      "description": "This field allows the merchant to manually select a connector with which the payout can go through.",
      "example": [
        "wise",
        "adyen"
      ],
      "nullable": true
    },
    "confirm": {
      "type": "boolean",
      "description": "This field is used when merchant wants to confirm the payout, thus useful for the payout _Confirm_ request. Ideally merchants should _Create_ a payout, _Update_ it (if required), then _Confirm_ it.",
      "default": false,
      "example": true,
      "nullable": true
    },
    "payout_type": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PayoutType"
        }
      ],
      "nullable": true
    },
    "payout_method_data": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PayoutMethodData"
        }
      ],
      "nullable": true
    },
    "billing": {
      "allOf": [
        {
          "$ref": "#/components/schemas/Address"
        }
      ],
      "nullable": true
    },
    "auto_fulfill": {
      "type": "boolean",
      "description": "Set to true to confirm the payout without review, no further action required",
      "default": false,
      "example": true,
      "nullable": true
    },
    "customer_id": {
      "type": "string",
      "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated. _Deprecated: Use customer_id instead._",
      "deprecated": true,
      "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44",
      "nullable": true,
      "maxLength": 255
    },
    "customer": {
      "allOf": [
        {
          "$ref": "#/components/schemas/CustomerDetails"
        }
      ],
      "nullable": true
    },
    "client_secret": {
      "type": "string",
      "description": "It's a token used for client side verification.",
      "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo",
      "nullable": true
    },
    "return_url": {
      "type": "string",
      "description": "The URL to redirect after the completion of the operation",
      "example": "https://hyperswitch.io",
      "nullable": true
    },
    "business_country": {
      "allOf": [
        {
          "$ref": "#/components/schemas/CountryAlpha2"
        }
      ],
      "nullable": true
    },
    "business_label": {
      "type": "string",
      "description": "Business label of the merchant for this payout. _Deprecated: Use profile_id instead._",
      "deprecated": true,
      "example": "food",
      "nullable": true
    },
    "description": {
      "type": "string",
      "description": "A description of the payout",
      "example": "It's my first payout request",
      "nullable": true
    },
    "entity_type": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PayoutEntityType"
        }
      ],
      "nullable": true
    },
    "recurring": {
      "type": "boolean",
      "description": "Specifies whether or not the payout request is recurring",
      "default": false,
      "nullable": true
    },
    "metadata": {
      "type": "object",
      "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.",
      "nullable": true
    },
    "payout_token": {
      "type": "string",
      "description": "Provide a reference to a stored payout method, used to process the payout.",
      "example": "187282ab-40ef-47a9-9206-5099ba31e432",
      "nullable": true
    },
    "profile_id": {
      "type": "string",
      "description": "The business profile to use for this payout, especially if there are multiple business profiles associated with the account, otherwise default business profile associated with the merchant account will be used.",
      "nullable": true
    },
    "priority": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PayoutSendPriority"
        }
      ],
      "nullable": true
    },
    "payout_link": {
      "type": "boolean",
      "description": "Whether to get the payout link (if applicable). Merchant need to specify this during the Payout _Create_, this field can not be updated during Payout _Update_.",
      "default": false,
      "example": true,
      "nullable": true
    },
    "payout_link_config": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PayoutCreatePayoutLinkConfig"
        }
      ],
      "nullable": true
    },
    "session_expiry": {
      "type": "integer",
      "format": "int32",
      "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins",
      "example": 900,
      "nullable": true,
      "minimum": 0
    },
    "email": {
      "type": "string",
      "description": "Customer's email. _Deprecated: Use customer object instead._",
      "deprecated": true,
      "example": "johntest@test.com",
      "nullable": true,
      "maxLength": 255
    },
    "name": {
      "type": "string",
      "description": "Customer's name. _Deprecated: Use customer object instead._",
      "deprecated": true,
      "example": "John Test",
      "nullable": true,
      "maxLength": 255
    },
    "phone": {
      "type": "string",
      "description": "Customer's phone. _Deprecated: Use customer object instead._",
      "deprecated": true,
      "example": "9123456789",
      "nullable": true,
      "maxLength": 255
    },
    "phone_country_code": {
      "type": "string",
      "description": "Customer's phone country code. _Deprecated: Use customer object instead._",
      "deprecated": true,
      "example": "+1",
      "nullable": true,
      "maxLength": 255
    },
    "payout_method_id": {
      "type": "string",
      "description": "Identifier for payout method",
      "nullable": true
    }
  }
}