pub async fn link_routing_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    authentication_profile_id: Option<common_utils::id_type::ProfileId>,
    algorithm_id: common_utils::id_type::RoutingId,
    transaction_type: enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
    metrics::ROUTING_LINK_CONFIG.add(1, &[]);
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let routing_algorithm = db
        .find_routing_algorithm_by_algorithm_id_merchant_id(
            &algorithm_id,
            merchant_context.get_merchant_account().get_id(),
        )
        .await
        .change_context(errors::ApiErrorResponse::ResourceIdNotFound)?;

    let business_profile = core_utils::validate_and_get_business_profile(
        db,
        key_manager_state,
        merchant_context.get_merchant_key_store(),
        Some(&routing_algorithm.profile_id),
        merchant_context.get_merchant_account().get_id(),
    )
    .await?
    .get_required_value("Profile")
    .change_context(errors::ApiErrorResponse::ProfileNotFound {
        id: routing_algorithm.profile_id.get_string_repr().to_owned(),
    })?;

    core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;

    match routing_algorithm.kind {
        diesel_models::enums::RoutingAlgorithmKind::Dynamic => {
            let mut dynamic_routing_ref: routing_types::DynamicRoutingAlgorithmRef =
                business_profile
                    .dynamic_routing_algorithm
                    .clone()
                    .map(|val| val.parse_value("DynamicRoutingAlgorithmRef"))
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "unable to deserialize Dynamic routing algorithm ref from business profile",
                    )?
                    .unwrap_or_default();

            utils::when(
                matches!(
                    dynamic_routing_ref.success_based_algorithm,
                    Some(routing::SuccessBasedAlgorithm {
                        algorithm_id_with_timestamp:
                        routing_types::DynamicAlgorithmWithTimestamp {
                            algorithm_id: Some(ref id),
                            timestamp: _
                        },
                        enabled_feature: _
                    }) if id == &algorithm_id
                ) || matches!(
                    dynamic_routing_ref.elimination_routing_algorithm,
                    Some(routing::EliminationRoutingAlgorithm {
                        algorithm_id_with_timestamp:
                        routing_types::DynamicAlgorithmWithTimestamp {
                            algorithm_id: Some(ref id),
                            timestamp: _
                        },
                        enabled_feature: _
                    }) if id == &algorithm_id
                ) || matches!(
                    dynamic_routing_ref.contract_based_routing,
                    Some(routing::ContractRoutingAlgorithm {
                        algorithm_id_with_timestamp:
                        routing_types::DynamicAlgorithmWithTimestamp {
                            algorithm_id: Some(ref id),
                            timestamp: _
                        },
                        enabled_feature: _
                    }) if id == &algorithm_id
                ),
                || {
                    Err(errors::ApiErrorResponse::PreconditionFailed {
                        message: "Algorithm is already active".to_string(),
                    })
                },
            )?;

            if routing_algorithm.name == helpers::SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM {
                dynamic_routing_ref.update_algorithm_id(
                algorithm_id,
                dynamic_routing_ref
                    .success_based_algorithm
                    .clone()
                    .ok_or(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "missing success_based_algorithm in dynamic_algorithm_ref from business_profile table",
                    )?
                    .enabled_feature,
                routing_types::DynamicRoutingType::SuccessRateBasedRouting,
            );

                // Call to DE here to update SR configs
                #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                {
                    if state.conf.open_router.dynamic_routing_enabled {
                        let existing_config = helpers::get_decision_engine_active_dynamic_routing_algorithm(
                        &state,
                        business_profile.get_id(),
                        api_models::open_router::DecisionEngineDynamicAlgorithmType::SuccessRate,
                    )
                    .await;

                        if let Ok(Some(_config)) = existing_config {
                            update_decision_engine_dynamic_routing_setup(
                            &state,
                            business_profile.get_id(),
                            routing_algorithm.algorithm_data.clone(),
                            routing_types::DynamicRoutingType::SuccessRateBasedRouting,
                            &mut dynamic_routing_ref,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "Failed to update the success rate routing config in Decision Engine",
                        )?;
                        } else {
                            let data: routing_types::SuccessBasedRoutingConfig =
                            routing_algorithm.algorithm_data
                                .clone()
                                .parse_value("SuccessBasedRoutingConfig")
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable(
                                    "unable to deserialize SuccessBasedRoutingConfig from routing algorithm data",
                                )?;

                            enable_decision_engine_dynamic_routing_setup(
                            &state,
                            business_profile.get_id(),
                            routing_types::DynamicRoutingType::SuccessRateBasedRouting,
                            &mut dynamic_routing_ref,
                            Some(routing_types::DynamicRoutingPayload::SuccessBasedRoutingPayload(data)),
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Unable to setup decision engine dynamic routing")?;
                        }
                    }
                }
            } else if routing_algorithm.name == helpers::ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM
            {
                dynamic_routing_ref.update_algorithm_id(
                algorithm_id,
                dynamic_routing_ref
                    .elimination_routing_algorithm
                    .clone()
                    .ok_or(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "missing elimination_routing_algorithm in dynamic_algorithm_ref from business_profile table",
                    )?
                    .enabled_feature,
                routing_types::DynamicRoutingType::EliminationRouting,
            );
                #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                {
                    if state.conf.open_router.dynamic_routing_enabled {
                        let existing_config = helpers::get_decision_engine_active_dynamic_routing_algorithm(
                            &state,
                            business_profile.get_id(),
                            api_models::open_router::DecisionEngineDynamicAlgorithmType::Elimination,
                        )
                        .await;

                        if let Ok(Some(_config)) = existing_config {
                            update_decision_engine_dynamic_routing_setup(
                                &state,
                                business_profile.get_id(),
                                routing_algorithm.algorithm_data.clone(),
                                routing_types::DynamicRoutingType::EliminationRouting,
                                &mut dynamic_routing_ref,
                            )
                            .await
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable(
                                "Failed to update the elimination routing config in Decision Engine",
                            )?;
                        } else {
                            let data: routing_types::EliminationRoutingConfig =
                                routing_algorithm.algorithm_data
                                    .clone()
                                    .parse_value("EliminationRoutingConfig")
                                    .change_context(errors::ApiErrorResponse::InternalServerError)
                                    .attach_printable(
                                        "unable to deserialize EliminationRoutingConfig from routing algorithm data",
                                    )?;

                            enable_decision_engine_dynamic_routing_setup(
                                &state,
                                business_profile.get_id(),
                                routing_types::DynamicRoutingType::EliminationRouting,
                                &mut dynamic_routing_ref,
                                Some(
                                    routing_types::DynamicRoutingPayload::EliminationRoutingPayload(
                                        data,
                                    ),
                                ),
                            )
                            .await
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Unable to setup decision engine dynamic routing")?;
                        }
                    }
                }
            } else if routing_algorithm.name == helpers::CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM {
                dynamic_routing_ref.update_algorithm_id(
                algorithm_id,
                dynamic_routing_ref
                    .contract_based_routing
                    .clone()
                    .ok_or(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "missing contract_based_routing in dynamic_algorithm_ref from business_profile table",
                    )?
                    .enabled_feature,
                routing_types::DynamicRoutingType::ContractBasedRouting,
            );
            }

            helpers::update_business_profile_active_dynamic_algorithm_ref(
                db,
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                business_profile.clone(),
                dynamic_routing_ref,
            )
            .await?;
        }
        diesel_models::enums::RoutingAlgorithmKind::Single
        | diesel_models::enums::RoutingAlgorithmKind::Priority
        | diesel_models::enums::RoutingAlgorithmKind::Advanced
        | diesel_models::enums::RoutingAlgorithmKind::VolumeSplit
        | diesel_models::enums::RoutingAlgorithmKind::ThreeDsDecisionRule => {
            let mut routing_ref: routing_types::RoutingAlgorithmRef = business_profile
                .routing_algorithm
                .clone()
                .map(|val| val.parse_value("RoutingAlgorithmRef"))
                .transpose()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to deserialize routing algorithm ref from business profile",
                )?
                .unwrap_or_default();

            utils::when(routing_algorithm.algorithm_for != transaction_type, || {
                Err(errors::ApiErrorResponse::PreconditionFailed {
                    message: format!(
                        "Cannot use {}'s routing algorithm for {} operation",
                        routing_algorithm.algorithm_for, transaction_type
                    ),
                })
            })?;

            utils::when(
                routing_ref.algorithm_id == Some(algorithm_id.clone()),
                || {
                    Err(errors::ApiErrorResponse::PreconditionFailed {
                        message: "Algorithm is already active".to_string(),
                    })
                },
            )?;
            routing_ref.update_algorithm_id(algorithm_id);
            helpers::update_profile_active_algorithm_ref(
                db,
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                business_profile.clone(),
                routing_ref,
                &transaction_type,
            )
            .await?;
        }
    };
    if let Some(euclid_routing_id) = routing_algorithm.decision_engine_routing_id.clone() {
        let routing_algo = ActivateRoutingConfigRequest {
            created_by: business_profile.get_id().get_string_repr().to_string(),
            routing_algorithm_id: euclid_routing_id,
        };
        let link_result = link_de_euclid_routing_algorithm(&state, routing_algo).await;
        match link_result {
            Ok(_) => {
                router_env::logger::info!(
                    routing_flow=?"link_routing_algorithm",
                    is_equal=?true,
                    "decision_engine_euclid"
                );
            }
            Err(e) => {
                router_env::logger::info!(
                    routing_flow=?"link_routing_algorithm",
                    is_equal=?false,
                    error=?e,
                    "decision_engine_euclid"
                );
            }
        }
    }
    metrics::ROUTING_LINK_CONFIG_SUCCESS_RESPONSE.add(1, &[]);
    Ok(service_api::ApplicationResponse::Json(
        routing_algorithm.foreign_into(),
    ))
}


impl ConnectorCommon for Tokenio {
    fn id(&self) -> &'static str {
        "tokenio"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.tokenio.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        _auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        Ok(vec![])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: tokenio::TokenioErrorResponse = res
            .response
            .parse_struct("TokenioErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.get_error_code(),
            message: response.get_message(),
            reason: Some(response.get_message()),
            attempt_status: None,
            connector_transaction_id: None,
            network_decline_code: None,
            network_advice_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}


pub struct CheckoutRefundResponse {
    pub(super) status: u16,
    pub(super) response: RefundResponse,
}


pub async fn relay(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    payload: web::Json<api_models::relay::RelayRequest>,
) -> impl Responder {
    let flow = Flow::Relay;
    let payload = payload.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = crate::types::domain::MerchantContext::NormalMerchant(Box::new(
                crate::types::domain::Context(auth.merchant_account, auth.key_store),
            ));
            relay::relay_flow_decider(
                state,
                merchant_context,
                #[cfg(feature = "v1")]
                auth.profile_id,
                #[cfg(feature = "v2")]
                Some(auth.profile.get_id().clone()),
                req,
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl RecoveryPaymentAttempt {
    pub fn get_attempt_triggered_by(&self) -> Option<common_enums::TriggeredBy> {
        self.feature_metadata.as_ref().and_then(|metadata| {
            metadata
                .revenue_recovery
                .as_ref()
                .map(|recovery| recovery.attempt_triggered_by)
        })
    }
}


pub fn identifier(input: &str) -> ParseResult<&str, String> {
    error::context(
        "identifier",
        combinator::map(
            sequence::pair(
                complete::take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
                complete::take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
            ),
            |out: (&str, &str)| out.0.to_string() + out.1,
        ),
    )(input)
}


File: crates/redis_interface/src/lib.rs
 Public functions: 7
 Public structs: 4

//! Intermediate module for encapsulate all the redis related functionality
//!
//! Provides structs to represent redis connection and all functions that redis provides and
//! are used in the `router` crate. Abstractions for creating a new connection while also facilitating
//! redis connection pool and configuration based types.
//!
//!  # Examples
//! ```
//! use redis_interface::{types::RedisSettings, RedisConnectionPool};
//!
//! #[tokio::main]
//! async fn main() {
//!     let redis_conn = RedisConnectionPool::new(&RedisSettings::default()).await;
//!     // ... redis_conn ready to use
//! }
//! ```

pub mod commands;
pub mod errors;
pub mod types;

use std::sync::{atomic, Arc};

use common_utils::errors::CustomResult;
use error_stack::ResultExt;
pub use fred::interfaces::PubsubInterface;
use fred::{
    clients::Transaction,
    interfaces::ClientLike,
    prelude::{EventInterface, TransactionInterface},
};

pub use self::types::*;

pub struct RedisConnectionPool {
    pub pool: Arc<fred::prelude::RedisPool>,
    pub key_prefix: String,
    pub config: Arc<RedisConfig>,
    pub subscriber: Arc<SubscriberClient>,
    pub publisher: Arc<RedisClient>,
    pub is_redis_available: Arc<atomic::AtomicBool>,
}

pub struct RedisClient {
    inner: fred::prelude::RedisClient,
}

impl std::ops::Deref for RedisClient {
    type Target = fred::prelude::RedisClient;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl RedisClient {
    pub async fn new(
        config: fred::types::RedisConfig,
        reconnect_policy: fred::types::ReconnectPolicy,
        perf: fred::types::PerformanceConfig,
    ) -> CustomResult<Self, errors::RedisError> {
        let client =
            fred::prelude::RedisClient::new(config, Some(perf), None, Some(reconnect_policy));
        client.connect();
        client
            .wait_for_connect()
            .await
            .change_context(errors::RedisError::RedisConnectionError)?;
        Ok(Self { inner: client })
    }
}

pub struct SubscriberClient {
    inner: fred::clients::SubscriberClient,
    pub is_subscriber_handler_spawned: Arc<atomic::AtomicBool>,
}

impl SubscriberClient {
    pub async fn new(
        config: fred::types::RedisConfig,
        reconnect_policy: fred::types::ReconnectPolicy,
        perf: fred::types::PerformanceConfig,
    ) -> CustomResult<Self, errors::RedisError> {
        let client =
            fred::clients::SubscriberClient::new(config, Some(perf), None, Some(reconnect_policy));
        client.connect();
        client
            .wait_for_connect()
            .await
            .change_context(errors::RedisError::RedisConnectionError)?;
        Ok(Self {
            inner: client,
            is_subscriber_handler_spawned: Arc::new(atomic::AtomicBool::new(false)),
        })
    }
}

impl std::ops::Deref for SubscriberClient {
    type Target = fred::clients::SubscriberClient;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl RedisConnectionPool {
    /// Create a new Redis connection
    pub async fn new(conf: &RedisSettings) -> CustomResult<Self, errors::RedisError> {
        let redis_connection_url = match conf.cluster_enabled {
            // Fred relies on this format for specifying cluster where the host port is ignored & only query parameters are used for node addresses
            // redis-cluster://username:password@host:port?node=bar.com:30002&node=baz.com:30003
            true => format!(
                "redis-cluster://{}:{}?{}",
                conf.host,
                conf.port,
                conf.cluster_urls
                    .iter()
                    .flat_map(|url| vec!["&", url])
                    .skip(1)
                    .collect::<String>()
            ),
            false => format!(
                "redis://{}:{}", //URI Schema
                conf.host, conf.port,
            ),
        };
        let mut config = fred::types::RedisConfig::from_url(&redis_connection_url)
            .change_context(errors::RedisError::RedisConnectionError)?;

        let perf = fred::types::PerformanceConfig {
            auto_pipeline: conf.auto_pipeline,
            default_command_timeout: std::time::Duration::from_secs(conf.default_command_timeout),
            max_feed_count: conf.max_feed_count,
            backpressure: fred::types::BackpressureConfig {
                disable_auto_backpressure: conf.disable_auto_backpressure,
                max_in_flight_commands: conf.max_in_flight_commands,
                policy: fred::types::BackpressurePolicy::Drain,
            },
        };

        let connection_config = fred::types::ConnectionConfig {
            unresponsive_timeout: std::time::Duration::from_secs(conf.unresponsive_timeout),
            ..fred::types::ConnectionConfig::default()
        };

        if !conf.use_legacy_version {
            config.version = fred::types::RespVersion::RESP3;
        }
        config.tracing = fred::types::TracingConfig::new(true);
        config.blocking = fred::types::Blocking::Error;
        let reconnect_policy = fred::types::ReconnectPolicy::new_constant(
            conf.reconnect_max_attempts,
            conf.reconnect_delay,
        );

        let subscriber =
            SubscriberClient::new(config.clone(), reconnect_policy.clone(), perf.clone()).await?;

        let publisher =
            RedisClient::new(config.clone(), reconnect_policy.clone(), perf.clone()).await?;

        let pool = fred::prelude::RedisPool::new(
            config,
            Some(perf),
            Some(connection_config),
            Some(reconnect_policy),
            conf.pool_size,
        )
        .change_context(errors::RedisError::RedisConnectionError)?;

        pool.connect();
        pool.wait_for_connect()
            .await
            .change_context(errors::RedisError::RedisConnectionError)?;

        let config = RedisConfig::from(conf);

        Ok(Self {
            pool: Arc::new(pool),
            config: Arc::new(config),
            is_redis_available: Arc::new(atomic::AtomicBool::new(true)),
            subscriber: Arc::new(subscriber),
            publisher: Arc::new(publisher),
            key_prefix: String::default(),
        })
    }
    pub fn clone(&self, key_prefix: &str) -> Self {
        Self {
            pool: Arc::clone(&self.pool),
            key_prefix: key_prefix.to_string(),
            config: Arc::clone(&self.config),
            subscriber: Arc::clone(&self.subscriber),
            publisher: Arc::clone(&self.publisher),
            is_redis_available: Arc::clone(&self.is_redis_available),
        }
    }
    pub async fn on_error(&self, tx: tokio::sync::oneshot::Sender<()>) {
        use futures::StreamExt;
        use tokio_stream::wrappers::BroadcastStream;

        let error_rxs: Vec<BroadcastStream<fred::error::RedisError>> = self
            .pool
            .clients()
            .iter()
            .map(|client| BroadcastStream::new(client.error_rx()))
            .collect();

        let mut error_rx = futures::stream::select_all(error_rxs);
        loop {
            if let Some(Ok(error)) = error_rx.next().await {
                tracing::error!(?error, "Redis protocol or connection error");
                if self.pool.state() == fred::types::ClientState::Disconnected {
                    if tx.send(()).is_err() {
                        tracing::error!("The redis shutdown signal sender failed to signal");
                    }
                    self.is_redis_available
                        .store(false, atomic::Ordering::SeqCst);
                    break;
                }
            }
        }
    }

    pub async fn on_unresponsive(&self) {
        let _ = self.pool.clients().iter().map(|client| {
            client.on_unresponsive(|server| {
                tracing::warn!(redis_server =?server.host, "Redis server is unresponsive");
                Ok(())
            })
        });
    }

    pub fn get_transaction(&self) -> Transaction {
        self.pool.next().multi()
    }
}

pub struct RedisConfig {
    default_ttl: u32,
    default_stream_read_count: u64,
    default_hash_ttl: u32,
    cluster_enabled: bool,
}

impl From<&RedisSettings> for RedisConfig {
    fn from(config: &RedisSettings) -> Self {
        Self {
            default_ttl: config.default_ttl,
            default_stream_read_count: config.stream_read_count,
            default_hash_ttl: config.default_hash_ttl,
            cluster_enabled: config.cluster_enabled,
        }
    }
}

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

    #[test]
    fn test_redis_error() {
        let x = errors::RedisError::ConsumerGroupClaimFailed.to_string();

        assert_eq!(x, "Failed to set Redis stream message owner".to_string())
    }
}


pub async fn profile_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::ProfileId>,
) -> HttpResponse {
    let flow = Flow::ProfileRetrieve;
    let profile_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        profile_id,
        |state, auth::AuthenticationDataWithoutProfile { key_store, .. }, profile_id, _| {
            retrieve_profile(state, profile_id, key_store)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: permissions::Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub fn build_payout_link_html(
    link_data: &GenericLinkFormData,
    locale: &str,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let document = include_str!("../../core/generic_link/payout_link/initiate/index.html");
    let styles = include_str!("../../core/generic_link/payout_link/initiate/styles.css");
    let (mut tera, mut context) = build_html_template(link_data, document, styles)
        .attach_printable("Failed to build context for payout link's HTML template")?;

    // Insert dynamic context in JS
    let script = include_str!("../../core/generic_link/payout_link/initiate/script.js");
    let js_template = script.to_string();
    let js_dynamic_context = "{{ script_data }}";
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("document_scripts", &final_js);
    context.insert("script_data", &link_data.js_data);
    context::insert_locales_in_context_for_payout_link(&mut context, locale);
    let js_script_tag = tera
        .render("document_scripts", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render JS template")?;
    context.insert("js_script_tag", &js_script_tag);
    context.insert(
        "hyper_sdk_loader_script_tag",
        &format!(
            r#"<script src="{}" onload="initializePayoutSDK()"></script>"#,
            link_data.sdk_url
        ),
    );

    // Render HTML template
    tera.render("html_template", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link's HTML template")
}


pub struct AcceptDisputeRequestData {
    pub dispute_id: String,
    pub connector_dispute_id: String,
    pub dispute_status: storage_enums::DisputeStatus,
}


File: crates/router/src/types/storage/kv.rs

pub use diesel_models::kv::{
    AddressUpdateMems, DBOperation, Insertable, PaymentAttemptUpdateMems, PaymentIntentUpdateMems,
    RefundUpdateMems, TypedSql, Updateable,
};


impl WebhookResponseTracker {
    #[cfg(feature = "v1")]
    pub fn get_payment_id(&self) -> Option<common_utils::id_type::PaymentId> {
        match self {
            Self::Payment { payment_id, .. }
            | Self::Refund { payment_id, .. }
            | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
            Self::NoEffect | Self::Mandate { .. } | Self::PaymentMethod { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
            Self::Relay { .. } => None,
        }
    }

    #[cfg(feature = "v1")]
    pub fn get_payment_method_id(&self) -> Option<String> {
        match self {
            Self::PaymentMethod {
                payment_method_id, ..
            } => Some(payment_method_id.to_owned()),
            Self::Payment { .. }
            | Self::Refund { .. }
            | Self::Dispute { .. }
            | Self::NoEffect
            | Self::Mandate { .. }
            | Self::Relay { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
        }
    }

    #[cfg(feature = "v2")]
    pub fn get_payment_id(&self) -> Option<common_utils::id_type::GlobalPaymentId> {
        match self {
            Self::Payment { payment_id, .. }
            | Self::Refund { payment_id, .. }
            | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
            Self::NoEffect | Self::Mandate { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
            Self::Relay { .. } => None,
        }
    }
}


pub fn payments_update_metadata() {}


impl api::PaymentAuthorize for Worldpayxml {}

impl api::PaymentAuthorize for for Worldpayxml


pub struct HipayErrorResponse {
    pub code: u8,
    pub message: String,
    pub description: Option<String>,
}


impl api::Refund for Celero {}


pub fn is_pending(self) -> bool {
        matches!(
            self,
            Self::ChallengeRequired | Self::ChallengeRequiredDecoupledAuthentication
        )
    }


pub async fn payment_methods_session_retrieve(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    payment_method_session_id: id_type::GlobalPaymentMethodSessionId,
) -> RouterResponse<payment_methods::PaymentMethodSessionResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let payment_method_session_domain_model = db
        .get_payment_methods_session(
            key_manager_state,
            merchant_context.get_merchant_key_store(),
            &payment_method_session_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
            message: "payment methods session does not exist or has expired".to_string(),
        })
        .attach_printable("Failed to retrieve payment methods session from db")?;

    let response = transformers::generate_payment_method_session_response(
        payment_method_session_domain_model,
        Secret::new("CLIENT_SECRET_REDACTED".to_string()),
        None, // TODO: send associated payments response based on the expandable param
        None,
    );

    Ok(services::ApplicationResponse::Json(response))
}


OpenAPI Block Path: components.schemas.ThirdPartySdkSessionResponse
{
  "type": "object",
  "required": [
    "secrets"
  ],
  "properties": {
    "secrets": {
      "$ref": "#/components/schemas/SecretInfoToInitiateSdk"
    }
  }
}


pub struct RedsysCardData {
    card_number: cards::CardNumber,
    expiry_date: Secret<String>,
    cvv2: Secret<String>,
}


File: crates/storage_impl/src/cards_info.rs

pub use diesel_models::{CardInfo, UpdateCardInfo};
use error_stack::report;
use hyperswitch_domain_models::cards_info::CardsInfoInterface;
use router_env::{instrument, tracing};

use crate::{
    errors::StorageError,
    kv_router_store::KVRouterStore,
    redis::kv_store::KvStorePartition,
    utils::{pg_connection_read, pg_connection_write},
    CustomResult, DatabaseStore, MockDb, RouterStore,
};

impl KvStorePartition for CardInfo {}

#[async_trait::async_trait]
impl<T: DatabaseStore> CardsInfoInterface for RouterStore<T> {
    type Error = StorageError;
    #[instrument(skip_all)]
    async fn get_card_info(&self, card_iin: &str) -> CustomResult<Option<CardInfo>, StorageError> {
        let conn = pg_connection_read(self).await?;
        CardInfo::find_by_iin(&conn, card_iin)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
    #[instrument(skip_all)]
    async fn add_card_info(&self, data: CardInfo) -> CustomResult<CardInfo, StorageError> {
        let conn = pg_connection_write(self).await?;
        data.insert(&conn)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
    #[instrument(skip_all)]
    async fn update_card_info(
        &self,
        card_iin: String,
        data: UpdateCardInfo,
    ) -> CustomResult<CardInfo, StorageError> {
        let conn = pg_connection_write(self).await?;
        CardInfo::update(&conn, card_iin, data)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl<T: DatabaseStore> CardsInfoInterface for KVRouterStore<T> {
    type Error = StorageError;
    #[instrument(skip_all)]
    async fn get_card_info(&self, card_iin: &str) -> CustomResult<Option<CardInfo>, StorageError> {
        let conn = pg_connection_read(self).await?;
        CardInfo::find_by_iin(&conn, card_iin)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
    #[instrument(skip_all)]
    async fn add_card_info(&self, data: CardInfo) -> CustomResult<CardInfo, StorageError> {
        let conn = pg_connection_write(self).await?;
        data.insert(&conn)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
    #[instrument(skip_all)]
    async fn update_card_info(
        &self,
        card_iin: String,
        data: UpdateCardInfo,
    ) -> CustomResult<CardInfo, StorageError> {
        let conn = pg_connection_write(self).await?;
        CardInfo::update(&conn, card_iin, data)
            .await
            .map_err(|error| report!(StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl CardsInfoInterface for MockDb {
    type Error = StorageError;
    #[instrument(skip_all)]
    async fn get_card_info(&self, card_iin: &str) -> CustomResult<Option<CardInfo>, StorageError> {
        Ok(self
            .cards_info
            .lock()
            .await
            .iter()
            .find(|ci| ci.card_iin == card_iin)
            .cloned())
    }

    async fn add_card_info(&self, _data: CardInfo) -> CustomResult<CardInfo, StorageError> {
        Err(StorageError::MockDbError)?
    }

    async fn update_card_info(
        &self,
        _card_iin: String,
        _data: UpdateCardInfo,
    ) -> CustomResult<CardInfo, StorageError> {
        Err(StorageError::MockDbError)?
    }
}


pub struct ArchipelCaptureOrderRequest {
    amount: MinorUnit,
}


impl Payments {
    pub fn server(state: AppState) -> Scope {
        let mut route = web::scope("/v2/payments").app_data(web::Data::new(state));
        route = route
            .service(
                web::resource("/create-intent")
                    .route(web::post().to(payments::payments_create_intent)),
            )
            .service(web::resource("/filter").route(web::get().to(payments::get_payment_filters)))
            .service(
                web::resource("/profile/filter")
                    .route(web::get().to(payments::get_payment_filters_profile)),
            )
            .service(
                web::resource("")
                    .route(web::post().to(payments::payments_create_and_confirm_intent)),
            )
            .service(web::resource("/list").route(web::get().to(payments::payments_list)))
            .service(
                web::resource("/aggregate").route(web::get().to(payments::get_payments_aggregates)),
            )
            .service(
                web::resource("/recovery")
                    .route(web::post().to(payments::recovery_payments_create)),
            )
            .service(
                web::resource("/profile/aggregate")
                    .route(web::get().to(payments::get_payments_aggregates_profile)),
            );

        route =
            route
                .service(web::resource("/ref/{merchant_reference_id}").route(
                    web::get().to(payments::payment_get_intent_using_merchant_reference_id),
                ));

        route = route.service(
            web::scope("/{payment_id}")
                .service(
                    web::resource("/confirm-intent")
                        .route(web::post().to(payments::payment_confirm_intent)),
                )
                // TODO: Deprecated. Remove this in favour of /list-attempts
                .service(
                    web::resource("/list_attempts")
                        .route(web::get().to(payments::list_payment_attempts)),
                )
                .service(
                    web::resource("/list-attempts")
                        .route(web::get().to(payments::list_payment_attempts)),
                )
                .service(
                    web::resource("/proxy-confirm-intent")
                        .route(web::post().to(payments::proxy_confirm_intent)),
                )
                .service(
                    web::resource("/confirm-intent/external-vault-proxy")
                        .route(web::post().to(payments::confirm_intent_with_external_vault_proxy)),
                )
                .service(
                    web::resource("/get-intent")
                        .route(web::get().to(payments::payments_get_intent)),
                )
                .service(
                    web::resource("/update-intent")
                        .route(web::put().to(payments::payments_update_intent)),
                )
                .service(
                    web::resource("/create-external-sdk-tokens")
                        .route(web::post().to(payments::payments_connector_session)),
                )
                .service(
                    web::resource("")
                        .route(web::get().to(payments::payment_status))
                        .route(web::post().to(payments::payments_status_with_gateway_creds)),
                )
                .service(
                    web::resource("/start-redirection")
                        .route(web::get().to(payments::payments_start_redirection)),
                )
                .service(
                    web::resource("/payment-methods")
                        .route(web::get().to(payments::list_payment_methods)),
                )
                .service(
                    web::resource("/finish-redirection/{publishable_key}/{profile_id}")
                        .route(web::get().to(payments::payments_finish_redirection)),
                )
                .service(
                    web::resource("/capture").route(web::post().to(payments::payments_capture)),
                ),
        );

        route
    }
}


pub struct KlarnaRouterData<T> {
    amount: MinorUnit,
    router_data: T,
}


OpenAPI Block Path: components.schemas.CustomerDefaultPaymentMethodResponse
{
  "type": "object",
  "required": [
    "customer_id",
    "payment_method"
  ],
  "properties": {
    "default_payment_method_id": {
      "type": "string",
      "description": "The unique identifier of the Payment method",
      "example": "card_rGK4Vi5iSW70MY7J2mIg",
      "nullable": true
    },
    "customer_id": {
      "type": "string",
      "description": "The unique identifier of the customer.",
      "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44",
      "maxLength": 64,
      "minLength": 1
    },
    "payment_method": {
      "$ref": "#/components/schemas/PaymentMethod"
    },
    "payment_method_type": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PaymentMethodType"
        }
      ],
      "nullable": true
    }
  }
}


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


impl masking::SerializableSecret for OrderDetailsWithAmount {}

impl masking::SerializableSecret for for OrderDetailsWithAmount


pub fn create_payment_method_metadata(
    metadata: Option<&pii::SecretSerdeValue>,
    connector_token: Option<(String, String)


impl DefaultFallbackRoutingConfigUpdate<'_> {
    async fn retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists(
        &self,
    ) -> RouterResult<()> {
        let profile_wrapper = ProfileWrapper::new(self.business_profile.clone());
        let default_routing_config_for_profile =
            &mut profile_wrapper.get_default_fallback_list_of_connector_under_profile()?;
        if let Some(routable_connector_val) = self.routable_connector {
            let choice = routing_types::RoutableConnectorChoice {
                choice_kind: routing_types::RoutableChoiceKind::FullStruct,
                connector: *routable_connector_val,
                merchant_connector_id: Some(self.merchant_connector_id.clone()),
            };
            if !default_routing_config_for_profile.contains(&choice.clone()) {
                default_routing_config_for_profile.push(choice);

                profile_wrapper
                    .update_default_fallback_routing_of_connectors_under_profile(
                        self.store,
                        default_routing_config_for_profile,
                        self.key_manager_state,
                        &self.key_store,
                    )
                    .await?
            }
        }
        Ok(())
    }

    async fn retrieve_and_delete_from_default_fallback_routing_algorithm_if_routable_connector_exists(
        &self,
    ) -> RouterResult<()> {
        let profile_wrapper = ProfileWrapper::new(self.business_profile.clone());
        let default_routing_config_for_profile =
            &mut profile_wrapper.get_default_fallback_list_of_connector_under_profile()?;
        if let Some(routable_connector_val) = self.routable_connector {
            let choice = routing_types::RoutableConnectorChoice {
                choice_kind: routing_types::RoutableChoiceKind::FullStruct,
                connector: *routable_connector_val,
                merchant_connector_id: Some(self.merchant_connector_id.clone()),
            };
            if default_routing_config_for_profile.contains(&choice.clone()) {
                default_routing_config_for_profile.retain(|mca| {
                    mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id)
                });

                profile_wrapper
                    .update_default_fallback_routing_of_connectors_under_profile(
                        self.store,
                        default_routing_config_for_profile,
                        self.key_manager_state,
                        &self.key_store,
                    )
                    .await?
            }
        }
        Ok(())
    }
}


pub async fn proxy_for_payments_operation_core<F, Req, Op, FData, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile_id_from_auth_layer: Option<id_type::ProfileId>,
    operation: Op,
    req: Req,
    call_connector_action: CallConnectorAction,
    auth_flow: services::AuthFlow,
    header_payload: HeaderPayload,
    return_raw_connector_response: Option<bool>,
) -> RouterResult<(D, Req, Option<domain::Customer>, Option<u16>, Option<u128>)>
where
    F: Send + Clone + Sync,
    Req: Authenticate + Clone,
    Op: Operation<F, Req, Data = D> + Send + Sync,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,

    // To create connector flow specific interface data
    D: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
    RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,

    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,

    // To perform router related operation for PaymentResponse
    PaymentResponse: Operation<F, FData, Data = D>,
    FData: Send + Sync + Clone,
{
    let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation);

    tracing::Span::current().record(
        "merchant_id",
        merchant_context
            .get_merchant_account()
            .get_id()
            .get_string_repr(),
    );
    let (operation, validate_result) = operation
        .to_validate_request()?
        .validate_request(&req, &merchant_context)?;

    tracing::Span::current().record("payment_id", format!("{}", validate_result.payment_id));

    let operations::GetTrackerResponse {
        operation,
        customer_details: _,
        mut payment_data,
        business_profile,
        mandate_type: _,
    } = operation
        .to_get_tracker()?
        .get_trackers(
            state,
            &validate_result.payment_id,
            &req,
            &merchant_context,
            auth_flow,
            &header_payload,
        )
        .await?;

    core_utils::validate_profile_id_from_auth_layer(
        profile_id_from_auth_layer,
        &payment_data.get_payment_intent().clone(),
    )?;

    common_utils::fp_utils::when(!should_call_connector(&operation, &payment_data), || {
        Err(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration).attach_printable(format!(
            "Nti and card details based mit flow is not support for this {operation:?} payment operation"
        ))
    })?;

    let connector_choice = operation
        .to_domain()?
        .get_connector(
            &merchant_context,
            &state.clone(),
            &req,
            payment_data.get_payment_intent(),
        )
        .await?;

    let connector = set_eligible_connector_for_nti_in_payment_data(
        state,
        &business_profile,
        merchant_context.get_merchant_key_store(),
        &mut payment_data,
        connector_choice,
    )
    .await?;

    let should_add_task_to_process_tracker = should_add_task_to_process_tracker(&payment_data);

    let locale = header_payload.locale.clone();

    let schedule_time = if should_add_task_to_process_tracker {
        payment_sync::get_sync_process_schedule_time(
            &*state.store,
            connector.connector.id(),
            merchant_context.get_merchant_account().get_id(),
            0,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while getting process schedule time")?
    } else {
        None
    };

    let (router_data, mca) = proxy_for_call_connector_service(
        state,
        req_state.clone(),
        &merchant_context,
        connector.clone(),
        &operation,
        &mut payment_data,
        &None,
        call_connector_action.clone(),
        &validate_result,
        schedule_time,
        header_payload.clone(),
        &business_profile,
        return_raw_connector_response,
    )
    .await?;

    let op_ref = &operation;
    let should_trigger_post_processing_flows = is_operation_confirm(&operation);

    let operation = Box::new(PaymentResponse);

    let connector_http_status_code = router_data.connector_http_status_code;
    let external_latency = router_data.external_latency;

    add_connector_http_status_code_metrics(connector_http_status_code);

    #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
    let routable_connectors =
        convert_connector_data_to_routable_connectors(&[connector.clone().into()])
            .map_err(|e| logger::error!(routable_connector_error=?e))
            .unwrap_or_default();

    let mut payment_data = operation
        .to_post_update_tracker()?
        .update_tracker(
            state,
            payment_data,
            router_data,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
            &locale,
            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
            routable_connectors,
            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
            &business_profile,
        )
        .await?;

    if should_trigger_post_processing_flows {
        complete_postprocessing_steps_if_required(
            state,
            &merchant_context,
            &None,
            &mca,
            &connector,
            &mut payment_data,
            op_ref,
            Some(header_payload.clone()),
        )
        .await?;
    }

    let cloned_payment_data = payment_data.clone();

    utils::trigger_payments_webhook(
        merchant_context.clone(),
        business_profile,
        cloned_payment_data,
        None,
        state,
        operation,
    )
    .await
    .map_err(|error| logger::warn!(payments_outgoing_webhook_error=?error))
    .ok();

    Ok((
        payment_data,
        req,
        None,
        connector_http_status_code,
        external_latency,
    ))
}


/// Derives validation functionality for structs with string-based fields that have
/// schema attributes specifying constraints like minimum and maximum lengths.
///
/// This macro generates a `validate()` method that checks if string based fields
/// meet the length requirements specified in their schema attributes.
///
/// ## Supported Types
///   - Option<T> or T: where T: String or Url
///
/// ## Supported Schema Attributes
///
/// - `min_length`: Specifies the minimum allowed character length
/// - `max_length`: Specifies the maximum allowed character length
///
/// ## Example
///
/// ```
/// use utoipa::ToSchema;
/// use router_derive::ValidateSchema;
/// use url::Url;
///
/// #[derive(Default, ToSchema, ValidateSchema)]
/// pub struct PaymentRequest {
///     #[schema(min_length = 10, max_length = 255)]
///     pub description: String,
///
///     #[schema(example = "https://example.com/return", max_length = 255)]
///     pub return_url: Option<Url>,
///
///     // Field without constraints
///     pub amount: u64,
/// }
///
/// let payment = PaymentRequest {
///     description: "Too short".to_string(),
///     return_url: Some(Url::parse("https://very-long-domain.com/callback").unwrap()),
///     amount: 1000,
/// };
///
/// let validation_result = payment.validate();
/// assert!(validation_result.is_err());
/// assert_eq!(
///     validation_result.unwrap_err(),
///     "description must be at least 10 characters long. Received 9 characters"
/// );
/// ```
///
/// ## Notes
/// - For `Option` fields, validation is only performed when the value is `Some`
/// - Fields without schema attributes or with unsupported types are ignored
/// - The validation stops on the first error encountered
/// - The generated `validate()` method returns `Ok(())` if all validations pass, or
///   `Err(String)` with an error message if any validations fail.
#[proc_macro_derive(ValidateSchema, attributes(schema))]
pub fn validate_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    macros::validate_schema_derive(input)
        .unwrap_or_else(|error| error.into_compile_error())
        .into()
}


pub struct ThreeDSecure {
    browser: Option<BamboraBrowserInfo>, //Needed only in case of 3Ds 2.0. Need to update request for this.
    enabled: bool,
    version: Option<i64>,
    auth_required: Option<bool>,
}


pub async fn update(
        self,
        conn: &PgPooledConn,
        merchant_connector_account: MerchantConnectorAccountUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.merchant_connector_id.to_owned(),
            merchant_connector_account,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }


impl HasSchemaParameters for Field {
    fn get_schema_parameters(&self) -> syn::Result<SchemaParameters> {
        let mut output = SchemaParameters::default();

        let mut value_type_keyword = None;
        let mut min_length_keyword = None;
        let mut max_length_keyword = None;
        let mut example_keyword = None;

        for meta in self.get_schema_metadata()? {
            match meta {
                SchemaParameterVariant::ValueType { keyword, value } => {
                    if let Some(first_keyword) = value_type_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "value_type"));
                    }

                    value_type_keyword = Some(keyword);
                    output.value_type = Some(value);
                }
                SchemaParameterVariant::MinLength { keyword, value } => {
                    if let Some(first_keyword) = min_length_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "min_length"));
                    }

                    min_length_keyword = Some(keyword);
                    let min_length = value.base10_parse::<usize>()?;
                    output.min_length = Some(min_length);
                }
                SchemaParameterVariant::MaxLength { keyword, value } => {
                    if let Some(first_keyword) = max_length_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "max_length"));
                    }

                    max_length_keyword = Some(keyword);
                    let max_length = value.base10_parse::<usize>()?;
                    output.max_length = Some(max_length);
                }
                SchemaParameterVariant::Example { keyword, value } => {
                    if let Some(first_keyword) = example_keyword {
                        return Err(occurrence_error(first_keyword, keyword, "example"));
                    }

                    example_keyword = Some(keyword);
                    output.example = Some(value.value());
                }
            }
        }

        Ok(output)
    }
}


impl api::PaymentSession for Getnet {}


pub fn make_all_aggregator<M: Metadata>(
        &mut self,
        nodes: &[(NodeId, Relation, Strength)


pub struct AciWebhookNotification {
    #[serde(rename = "type")]
    pub event_type: AciWebhookEventType,
    pub action: Option<AciWebhookAction>,
    pub payload: serde_json::Value,
}


impl ConnectorValidation for Paystack {
    //TODO: implement functions when support enabled
}

impl ConnectorValidation for for Paystack


pub struct ErrorDistributionRow {
    pub count: i64,
    pub total: i64,
    pub error_message: String,
}


pub struct VerifyTokenResponse {
    pub merchant_id: id_type::MerchantId,
    pub user_email: pii::Email,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acl: Option<String>,
}


/// Decryption functionality
    pub fn decrypt(&self, data: impl AsRef<[u8]>) -> Vec<u8> {
        data.as_ref().into()
    }


pub struct ErrorDetails {
    pub message: String,
    pub extensions: Option<AdditionalErrorDetails>,
}


pub struct Request {
    pub url: String,
    pub headers: Headers,
    pub method: Method,
    pub certificate: Option<Secret<String>>,
    pub certificate_key: Option<Secret<String>>,
    pub body: Option<RequestContent>,
    pub ca_certificate: Option<Secret<String>>,
}


pub fn make_dsl_input_for_surcharge(
    payment_attempt: &oss_storage::PaymentAttempt,
    payment_intent: &oss_storage::PaymentIntent,
    billing_address: Option<Address>,
) -> RoutingResult<dsl_inputs::BackendInput> {
    let mandate_data = dsl_inputs::MandateData {
        mandate_acceptance_type: None,
        mandate_type: None,
        payment_type: None,
    };

    let payment_input = dsl_inputs::PaymentInput {
        amount: payment_attempt.get_total_amount(),
        // currency is always populated in payment_attempt during payment create
        currency: payment_attempt
            .currency
            .get_required_value("currency")
            .change_context(errors::RoutingError::DslMissingRequiredField {
                field_name: "currency".to_string(),
            })?,
        authentication_type: payment_attempt.authentication_type,
        card_bin: None,
        capture_method: payment_attempt.capture_method,
        business_country: payment_intent
            .business_country
            .map(api_enums::Country::from_alpha2),
        billing_country: billing_address
            .and_then(|bic| bic.address)
            .and_then(|add| add.country)
            .map(api_enums::Country::from_alpha2),
        business_label: payment_intent.business_label.clone(),
        setup_future_usage: payment_intent.setup_future_usage,
    };

    let metadata = 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);
    let payment_method_input = dsl_inputs::PaymentMethodInput {
        payment_method: None,
        payment_method_type: None,
        card_network: None,
    };
    let backend_input = 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,
    };
    Ok(backend_input)
}


impl api::PaymentSync for Coingate {}


pub fn get_refund_sync_body(req: &types::RefundSyncRouterData) -> Result<Vec<u8>, Error> {
    let auth_details = BamboraapacAuthType::try_from(&req.connector_auth_type)?;

    let body = format!(
        r#"
            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:dts="http://www.ippayments.com.au/interface/api/dts">
                <soapenv:Header/>
                <soapenv:Body>
                    <dts:QueryTransaction>
                        <dts:queryXML>
                            <![CDATA[
                                <QueryTransaction>
                                    <Criteria>
                                        <AccountNumber>{}</AccountNumber>
                                        <TrnStartTimestamp>2024-06-23 00:00:00</TrnStartTimestamp>
                                        <TrnEndTimestamp>2099-12-31 23:59:59</TrnEndTimestamp>
                                        <CustRef>{}</CustRef>
                                    </Criteria>
                                    <Security>
                                        <UserName>{}</UserName>
                                        <Password>{}</Password>
                                    </Security>
                            </QueryTransaction>
                            ]]>
                        </dts:queryXML>
                    </dts:QueryTransaction>
                </soapenv:Body>
            </soapenv:Envelope>
        "#,
        auth_details.account_number.peek(),
        req.request.refund_id,
        auth_details.username.peek(),
        auth_details.password.peek(),
    );

    Ok(body.as_bytes().to_vec())
}


pub trait ActivePaymentsMetricAccumulator {
    type MetricOutput;

    fn add_metrics_bucket(&mut self, metrics: &ActivePaymentsMetricRow);

    fn collect(self) -> Self::MetricOutput;
}


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