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

use analytics::errors::AnalyticsError;
use api_models::analytics::AnalyticsRequest;
use common_utils::errors::CustomResult;
use currency_conversion::types::ExchangeRates;
use router_env::logger;

use crate::core::currency::get_forex_exchange_rates;

pub async fn request_validator(
    req_type: AnalyticsRequest,
    state: &crate::routes::SessionState,
) -> CustomResult<Option<ExchangeRates>, AnalyticsError> {
    let forex_enabled = state.conf.analytics.get_inner().get_forex_enabled();
    let require_forex_functionality = req_type.requires_forex_functionality();

    let ex_rates = if forex_enabled && require_forex_functionality {
        logger::info!("Fetching forex exchange rates");
        Some(get_forex_exchange_rates(state.clone()).await?)
    } else {
        None
    };

    Ok(ex_rates)
}


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

pub use common_utils::newtype;

#[macro_export]
macro_rules! get_payment_link_config_value_based_on_priority {
    ($config:expr, $business_config:expr, $field:ident, $default:expr) => {
        $config
            .as_ref()
            .and_then(|pc_config| pc_config.theme_config.$field.clone())
            .or_else(|| {
                $business_config
                    .as_ref()
                    .and_then(|business_config| business_config.$field.clone())
            })
            .unwrap_or($default)
    };
}

#[macro_export]
macro_rules! get_payment_link_config_value {
    ($config:expr, $business_config:expr, $(($field:ident, $default:expr)),*) => {
        (
            $(get_payment_link_config_value_based_on_priority!($config, $business_config, $field, $default)),*
        )
    };
    ($config:expr, $business_config:expr, $(($field:ident)),*) => {
        (
            $(
                $config
                    .as_ref()
                    .and_then(|pc_config| pc_config.theme_config.$field.clone())
                    .or_else(|| {
                        $business_config
                            .as_ref()
                            .and_then(|business_config| business_config.$field.clone())
                    })
            ),*
        )
    };
    ($config:expr, $business_config:expr, $(($field:ident $(, $transform:expr)?)),* $(,)?) => {
        (
            $(
                $config
                    .as_ref()
                    .and_then(|pc_config| pc_config.theme_config.$field.clone())
                    .or_else(|| {
                        $business_config
                            .as_ref()
                            .and_then(|business_config| {
                                let value = business_config.$field.clone();
                                $(let value = value.map($transform);)?
                                value
                            })
                    })
            ),*
        )
    };

}


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

#[cfg(feature = "email")]
pub mod api_key_expiry;
#[cfg(feature = "payouts")]
pub mod attach_payout_account_workflow;
pub mod outgoing_webhook_retry;
pub mod payment_method_status_update;
pub mod payment_sync;

pub mod refund_router;

pub mod tokenized_data;

pub mod revenue_recovery;

pub mod process_dispute;

pub mod dispute_list;

pub mod invoice_sync;


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

use rust_i18n::i18n;

i18n!("locales", fallback = "en");


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

pub mod utils;

#[cfg(feature = "dummy_connector")]
pub use hyperswitch_connectors::connectors::DummyConnector;
pub use hyperswitch_connectors::connectors::{
    aci, aci::Aci, adyen, adyen::Adyen, adyenplatform, adyenplatform::Adyenplatform, affirm,
    affirm::Affirm, airwallex, airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, archipel,
    archipel::Archipel, authipay, authipay::Authipay, authorizedotnet,
    authorizedotnet::Authorizedotnet, bambora, bambora::Bambora, bamboraapac,
    bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, barclaycard,
    barclaycard::Barclaycard, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay,
    blackhawknetwork, blackhawknetwork::Blackhawknetwork, bluesnap, bluesnap::Bluesnap, boku,
    boku::Boku, braintree, braintree::Braintree, breadpay, breadpay::Breadpay, calida,
    calida::Calida, cashtocode, cashtocode::Cashtocode, celero, celero::Celero, chargebee,
    chargebee::Chargebee, checkbook, checkbook::Checkbook, checkout, checkout::Checkout, coinbase,
    coinbase::Coinbase, coingate, coingate::Coingate, cryptopay, cryptopay::Cryptopay,
    ctp_mastercard, ctp_mastercard::CtpMastercard, custombilling, custombilling::Custombilling,
    cybersource, cybersource::Cybersource, datatrans, datatrans::Datatrans, deutschebank,
    deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal,
    dwolla, dwolla::Dwolla, ebanx, ebanx::Ebanx, elavon, elavon::Elavon, envoy, envoy::Envoy,
    facilitapay, facilitapay::Facilitapay, finix, finix::Finix, fiserv, fiserv::Fiserv, fiservemea,
    fiservemea::Fiservemea, fiuu, fiuu::Fiuu, flexiti, flexiti::Flexiti, forte, forte::Forte,
    getnet, getnet::Getnet, gigadat, gigadat::Gigadat, globalpay, globalpay::Globalpay, globepay,
    globepay::Globepay, gocardless, gocardless::Gocardless, gpayments, gpayments::Gpayments,
    helcim, helcim::Helcim, hipay, hipay::Hipay, hyperswitch_vault,
    hyperswitch_vault::HyperswitchVault, hyperwallet, hyperwallet::Hyperwallet, iatapay,
    iatapay::Iatapay, inespay, inespay::Inespay, itaubank, itaubank::Itaubank, jpmorgan,
    jpmorgan::Jpmorgan, juspaythreedsserver, juspaythreedsserver::Juspaythreedsserver, katapult,
    katapult::Katapult, klarna, klarna::Klarna, loonio, loonio::Loonio, mifinity,
    mifinity::Mifinity, mollie, mollie::Mollie, moneris, moneris::Moneris, mpgs, mpgs::Mpgs,
    multisafepay, multisafepay::Multisafepay, netcetera, netcetera::Netcetera, nexinets,
    nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nmi, nmi::Nmi, nomupay, nomupay::Nomupay,
    noon, noon::Noon, nordea, nordea::Nordea, novalnet, novalnet::Novalnet, nuvei, nuvei::Nuvei,
    opayo, opayo::Opayo, opennode, opennode::Opennode, paybox, paybox::Paybox, payeezy,
    payeezy::Payeezy, payjustnow, payjustnow::Payjustnow, payload, payload::Payload, payme,
    payme::Payme, payone, payone::Payone, paypal, paypal::Paypal, paysafe, paysafe::Paysafe,
    paystack, paystack::Paystack, paytm, paytm::Paytm, payu, payu::Payu, peachpayments,
    peachpayments::Peachpayments, phonepe, phonepe::Phonepe, placetopay, placetopay::Placetopay,
    plaid, plaid::Plaid, powertranz, powertranz::Powertranz, prophetpay, prophetpay::Prophetpay,
    rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, recurly, recurly::Recurly, redsys,
    redsys::Redsys, riskified, riskified::Riskified, santander, santander::Santander, shift4,
    shift4::Shift4, sift, sift::Sift, signifyd, signifyd::Signifyd, silverflow,
    silverflow::Silverflow, square, square::Square, stax, stax::Stax, stripe, stripe::Stripe,
    stripebilling, stripebilling::Stripebilling, taxjar, taxjar::Taxjar, tesouro, tesouro::Tesouro,
    threedsecureio, threedsecureio::Threedsecureio, thunes, thunes::Thunes, tokenex,
    tokenex::Tokenex, tokenio, tokenio::Tokenio, trustpay, trustpay::Trustpay, trustpayments,
    trustpayments::Trustpayments, tsys, tsys::Tsys, unified_authentication_service,
    unified_authentication_service::UnifiedAuthenticationService, vgs, vgs::Vgs, volt, volt::Volt,
    wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, wellsfargopayout::Wellsfargopayout, wise,
    wise::Wise, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, worldpayvantiv,
    worldpayvantiv::Worldpayvantiv, worldpayxml, worldpayxml::Worldpayxml, xendit, xendit::Xendit,
    zen, zen::Zen, zift, zift::Zift, zsl, zsl::Zsl,
};


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

use bb8::PooledConnection;
use diesel::PgConnection;
use error_stack::ResultExt;
use storage_impl::errors as storage_errors;

use crate::errors;

pub type PgPool = bb8::Pool<async_bb8_diesel::ConnectionManager<PgConnection>>;

pub type PgPooledConn = async_bb8_diesel::Connection<PgConnection>;

/// Creates a Redis connection pool for the specified Redis settings
/// # Panics
///
/// Panics if failed to create a redis pool
#[allow(clippy::expect_used)]
pub async fn redis_connection(
    conf: &crate::configs::Settings,
) -> redis_interface::RedisConnectionPool {
    redis_interface::RedisConnectionPool::new(&conf.redis)
        .await
        .expect("Failed to create Redis Connection Pool")
}

pub async fn pg_connection_read<T: storage_impl::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    storage_errors::StorageError,
> {
    // If only OLAP is enabled get replica pool.
    #[cfg(all(feature = "olap", not(feature = "oltp")))]
    let pool = store.get_replica_pool();

    // If either one of these are true we need to get master pool.
    //  1. Only OLTP is enabled.
    //  2. Both OLAP and OLTP is enabled.
    //  3. Both OLAP and OLTP is disabled.
    #[cfg(any(
        all(not(feature = "olap"), feature = "oltp"),
        all(feature = "olap", feature = "oltp"),
        all(not(feature = "olap"), not(feature = "oltp"))
    ))]
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(storage_errors::StorageError::DatabaseConnectionError)
}

pub async fn pg_accounts_connection_read<T: storage_impl::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    storage_errors::StorageError,
> {
    // If only OLAP is enabled get replica pool.
    #[cfg(all(feature = "olap", not(feature = "oltp")))]
    let pool = store.get_accounts_replica_pool();

    // If either one of these are true we need to get master pool.
    //  1. Only OLTP is enabled.
    //  2. Both OLAP and OLTP is enabled.
    //  3. Both OLAP and OLTP is disabled.
    #[cfg(any(
        all(not(feature = "olap"), feature = "oltp"),
        all(feature = "olap", feature = "oltp"),
        all(not(feature = "olap"), not(feature = "oltp"))
    ))]
    let pool = store.get_accounts_master_pool();

    pool.get()
        .await
        .change_context(storage_errors::StorageError::DatabaseConnectionError)
}

pub async fn pg_connection_write<T: storage_impl::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    storage_errors::StorageError,
> {
    // Since all writes should happen to master DB only choose master DB.
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(storage_errors::StorageError::DatabaseConnectionError)
}

pub async fn pg_accounts_connection_write<T: storage_impl::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    storage_errors::StorageError,
> {
    // Since all writes should happen to master DB only choose master DB.
    let pool = store.get_accounts_master_pool();

    pool.get()
        .await
        .change_context(storage_errors::StorageError::DatabaseConnectionError)
}


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

pub mod chat;
#[cfg(feature = "olap")]
pub mod connector_onboarding;
pub mod currency;
pub mod db_utils;
pub mod ext_traits;
#[cfg(feature = "kv_store")]
pub mod storage_partitioning;
#[cfg(feature = "olap")]
pub mod user;
#[cfg(feature = "olap")]
pub mod user_role;
#[cfg(feature = "olap")]
pub mod verify_connector;
use std::fmt::Debug;

use api_models::{
    enums,
    payments::{self},
    subscription as subscription_types, webhooks,
};
pub use common_utils::{
    crypto::{self, Encryptable},
    ext_traits::{ByteSliceExt, BytesExt, Encode, StringExt, ValueExt},
    fp_utils::when,
    id_type, pii,
    validation::validate_email,
};
#[cfg(feature = "v1")]
use common_utils::{
    type_name,
    types::keymanager::{Identifier, ToEncryptable},
};
use error_stack::ResultExt;
pub use hyperswitch_connectors::utils::QrImage;
use hyperswitch_domain_models::payments::PaymentIntent;
#[cfg(feature = "v1")]
use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
use masking::{ExposeInterface, SwitchStrategy};
use nanoid::nanoid;
use serde::de::DeserializeOwned;
use serde_json::Value;
#[cfg(feature = "v1")]
use subscriptions::{subscription_handler::SubscriptionHandler, workflows::InvoiceSyncHandler};
use tracing_futures::Instrument;

pub use self::ext_traits::{OptionExt, ValidateCall};
use crate::{
    consts,
    core::{
        authentication::types::ExternalThreeDSConnectorMetadata,
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        payments as payments_core,
    },
    headers::ACCEPT_LANGUAGE,
    logger,
    routes::{metrics, SessionState},
    services::{self, authentication::get_header_value_by_key},
    types::{self, domain, transformers::ForeignInto},
};
#[cfg(feature = "v1")]
use crate::{core::webhooks as webhooks_core, types::storage};

pub mod error_parser {
    use std::fmt::Display;

    use actix_web::{
        error::{Error, JsonPayloadError},
        http::StatusCode,
        HttpRequest, ResponseError,
    };

    #[derive(Debug)]
    struct CustomJsonError {
        err: JsonPayloadError,
    }

    // Display is a requirement defined by the actix crate for implementing ResponseError trait
    impl Display for CustomJsonError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.write_str(
                serde_json::to_string(&serde_json::json!({
                    "error": {
                        "error_type": "invalid_request",
                        "message": self.err.to_string(),
                        "code": "IR_06",
                    }
                }))
                .as_deref()
                .unwrap_or("Invalid Json Error"),
            )
        }
    }

    impl ResponseError for CustomJsonError {
        fn status_code(&self) -> StatusCode {
            StatusCode::BAD_REQUEST
        }

        fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
            use actix_web::http::header;

            actix_web::HttpResponseBuilder::new(self.status_code())
                .insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON))
                .body(self.to_string())
        }
    }

    pub fn custom_json_error_handler(err: JsonPayloadError, _req: &HttpRequest) -> Error {
        Error::from(CustomJsonError { err })
    }
}

#[inline]
pub fn generate_id(length: usize, prefix: &str) -> String {
    format!("{}_{}", prefix, nanoid!(length, &consts::ALPHABETS))
}

pub trait ConnectorResponseExt: Sized {
    fn get_response(self) -> RouterResult<types::Response>;
    fn get_error_response(self) -> RouterResult<types::Response>;
    fn get_response_inner<T: DeserializeOwned>(self, type_name: &'static str) -> RouterResult<T> {
        self.get_response()?
            .response
            .parse_struct(type_name)
            .change_context(errors::ApiErrorResponse::InternalServerError)
    }
}

impl<E> ConnectorResponseExt
    for Result<Result<types::Response, types::Response>, error_stack::Report<E>>
{
    fn get_error_response(self) -> RouterResult<types::Response> {
        self.map_err(|error| error.change_context(errors::ApiErrorResponse::InternalServerError))
            .attach_printable("Error while receiving response")
            .and_then(|inner| match inner {
                Ok(res) => {
                    logger::error!(response=?res);
                    Err(errors::ApiErrorResponse::InternalServerError).attach_printable(format!(
                        "Expecting error response, received response: {res:?}"
                    ))
                }
                Err(err_res) => Ok(err_res),
            })
    }

    fn get_response(self) -> RouterResult<types::Response> {
        self.map_err(|error| error.change_context(errors::ApiErrorResponse::InternalServerError))
            .attach_printable("Error while receiving response")
            .and_then(|inner| match inner {
                Err(err_res) => {
                    logger::error!(error_response=?err_res);
                    Err(errors::ApiErrorResponse::InternalServerError).attach_printable(format!(
                        "Expecting response, received error response: {err_res:?}"
                    ))
                }
                Ok(res) => Ok(res),
            })
    }
}

#[inline]
pub fn get_payout_attempt_id(payout_id: &str, attempt_count: i16) -> String {
    format!("{payout_id}_{attempt_count}")
}

#[cfg(feature = "v1")]
pub async fn find_payment_intent_from_payment_id_type(
    state: &SessionState,
    payment_id_type: payments::PaymentIdType,
    platform: &domain::Platform,
) -> CustomResult<PaymentIntent, errors::ApiErrorResponse> {
    let db = &*state.store;
    match payment_id_type {
        payments::PaymentIdType::PaymentIntentId(payment_id) => db
            .find_payment_intent_by_payment_id_merchant_id(
                &payment_id,
                platform.get_processor().get_account().get_id(),
                platform.get_processor().get_key_store(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound),
        payments::PaymentIdType::ConnectorTransactionId(connector_transaction_id) => {
            let attempt = db
                .find_payment_attempt_by_merchant_id_connector_txn_id(
                    platform.get_processor().get_account().get_id(),
                    &connector_transaction_id,
                    platform.get_processor().get_account().storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            db.find_payment_intent_by_payment_id_merchant_id(
                &attempt.payment_id,
                platform.get_processor().get_account().get_id(),
                platform.get_processor().get_key_store(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
        }
        payments::PaymentIdType::PaymentAttemptId(attempt_id) => {
            let attempt = db
                .find_payment_attempt_by_attempt_id_merchant_id(
                    &attempt_id,
                    platform.get_processor().get_account().get_id(),
                    platform.get_processor().get_account().storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            db.find_payment_intent_by_payment_id_merchant_id(
                &attempt.payment_id,
                platform.get_processor().get_account().get_id(),
                platform.get_processor().get_key_store(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
        }
        payments::PaymentIdType::PreprocessingId(_) => {
            Err(errors::ApiErrorResponse::PaymentNotFound)?
        }
    }
}

#[cfg(feature = "v1")]
pub async fn find_payment_intent_from_refund_id_type(
    state: &SessionState,
    refund_id_type: webhooks::RefundIdType,
    platform: &domain::Platform,
    connector_name: &str,
) -> CustomResult<PaymentIntent, errors::ApiErrorResponse> {
    let db = &*state.store;
    let refund = match refund_id_type {
        webhooks::RefundIdType::RefundId(id) => db
            .find_refund_by_merchant_id_refund_id(
                platform.get_processor().get_account().get_id(),
                &id,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?,
        webhooks::RefundIdType::ConnectorRefundId(id) => db
            .find_refund_by_merchant_id_connector_refund_id_connector(
                platform.get_processor().get_account().get_id(),
                &id,
                connector_name,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?,
    };
    let attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &refund.attempt_id,
            platform.get_processor().get_account().get_id(),
            platform.get_processor().get_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
    db.find_payment_intent_by_payment_id_merchant_id(
        &attempt.payment_id,
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_key_store(),
        platform.get_processor().get_account().storage_scheme,
    )
    .await
    .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
}

#[cfg(feature = "v1")]
pub async fn find_payment_intent_from_mandate_id_type(
    state: &SessionState,
    mandate_id_type: webhooks::MandateIdType,
    platform: &domain::Platform,
) -> CustomResult<PaymentIntent, errors::ApiErrorResponse> {
    let db = &*state.store;
    let mandate = match mandate_id_type {
        webhooks::MandateIdType::MandateId(mandate_id) => db
            .find_mandate_by_merchant_id_mandate_id(
                platform.get_processor().get_account().get_id(),
                mandate_id.as_str(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?,
        webhooks::MandateIdType::ConnectorMandateId(connector_mandate_id) => db
            .find_mandate_by_merchant_id_connector_mandate_id(
                platform.get_processor().get_account().get_id(),
                connector_mandate_id.as_str(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?,
    };
    db.find_payment_intent_by_payment_id_merchant_id(
        &mandate
            .original_payment_id
            .ok_or(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("original_payment_id not present in mandate record")?,
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_key_store(),
        platform.get_processor().get_account().storage_scheme,
    )
    .await
    .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
}

#[cfg(feature = "v1")]
pub async fn find_mca_from_authentication_id_type(
    state: &SessionState,
    authentication_id_type: webhooks::AuthenticationIdType,
    platform: &domain::Platform,
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
    let db = &*state.store;
    let authentication = match authentication_id_type {
        webhooks::AuthenticationIdType::AuthenticationId(authentication_id) => db
            .find_authentication_by_merchant_id_authentication_id(
                platform.get_processor().get_account().get_id(),
                &authentication_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?,
        webhooks::AuthenticationIdType::ConnectorAuthenticationId(connector_authentication_id) => {
            db.find_authentication_by_merchant_id_connector_authentication_id(
                platform.get_processor().get_account().get_id().clone(),
                connector_authentication_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
        }
    };
    #[cfg(feature = "v1")]
    {
        // raise error if merchant_connector_id is not present since it should we be present in the current flow
        let mca_id = authentication
            .merchant_connector_id
            .ok_or(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("merchant_connector_id not present in authentication record")?;
        db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            platform.get_processor().get_account().get_id(),
            &mca_id,
            platform.get_processor().get_key_store(),
        )
        .await
        .to_not_found_response(
            errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                id: mca_id.get_string_repr().to_string(),
            },
        )
    }
    #[cfg(feature = "v2")]
    //get mca using id
    {
        let _ = key_store;
        let _ = authentication;
        todo!()
    }
}

#[cfg(feature = "v1")]
pub async fn get_mca_from_payment_intent(
    state: &SessionState,
    platform: &domain::Platform,
    payment_intent: PaymentIntent,
    connector_name: &str,
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
    let db = &*state.store;

    #[cfg(feature = "v1")]
    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &payment_intent.active_attempt.get_id(),
            platform.get_processor().get_account().get_id(),
            platform.get_processor().get_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    #[cfg(feature = "v2")]
    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            key_manager_state,
            key_store,
            &payment_intent.active_attempt.get_id(),
            merchant_account.get_id(),
            merchant_account.storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    match payment_attempt.merchant_connector_id {
        Some(merchant_connector_id) => {
            #[cfg(feature = "v1")]
            {
                db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                    platform.get_processor().get_account().get_id(),
                    &merchant_connector_id,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )
            }
            #[cfg(feature = "v2")]
            {
                //get mca using id
                let _id = merchant_connector_id;
                let _ = key_store;
                let _ = key_manager_state;
                let _ = connector_name;
                todo!()
            }
        }
        None => {
            let profile_id = payment_intent
                .profile_id
                .as_ref()
                .get_required_value("profile_id")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("profile_id is not set in payment_intent")?
                .clone();

            #[cfg(feature = "v1")]
            {
                db.find_merchant_connector_account_by_profile_id_connector_name(
                    &profile_id,
                    connector_name,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: format!(
                            "profile_id {} and connector_name {connector_name}",
                            profile_id.get_string_repr()
                        ),
                    },
                )
            }
            #[cfg(feature = "v2")]
            {
                //get mca using id
                let _ = profile_id;
                todo!()
            }
        }
    }
}
#[cfg(feature = "payouts")]
pub async fn get_mca_from_payout_attempt(
    state: &SessionState,
    platform: &domain::Platform,
    payout_id_type: webhooks::PayoutIdType,
    connector_name: &str,
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
    let db = &*state.store;
    let payout = match payout_id_type {
        webhooks::PayoutIdType::PayoutAttemptId(payout_attempt_id) => db
            .find_payout_attempt_by_merchant_id_payout_attempt_id(
                platform.get_processor().get_account().get_id(),
                &payout_attempt_id,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?,
        webhooks::PayoutIdType::ConnectorPayoutId(connector_payout_id) => db
            .find_payout_attempt_by_merchant_id_connector_payout_id(
                platform.get_processor().get_account().get_id(),
                &connector_payout_id,
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?,
    };
    match payout.merchant_connector_id {
        Some(merchant_connector_id) => {
            #[cfg(feature = "v1")]
            {
                db.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                    platform.get_processor().get_account().get_id(),
                    &merchant_connector_id,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )
            }
            #[cfg(feature = "v2")]
            {
                //get mca using id
                let _id = merchant_connector_id;
                let _ = platform.get_processor().get_key_store();
                let _ = connector_name;
                todo!()
            }
        }
        None => {
            #[cfg(feature = "v1")]
            {
                db.find_merchant_connector_account_by_profile_id_connector_name(
                    &payout.profile_id,
                    connector_name,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: format!(
                            "profile_id {} and connector_name {}",
                            payout.profile_id.get_string_repr(),
                            connector_name
                        ),
                    },
                )
            }
            #[cfg(feature = "v2")]
            {
                todo!()
            }
        }
    }
}

#[cfg(feature = "v1")]
pub async fn get_mca_from_object_reference_id(
    state: &SessionState,
    object_reference_id: webhooks::ObjectReferenceId,
    platform: &domain::Platform,
    connector_name: &str,
) -> CustomResult<domain::MerchantConnectorAccount, errors::ApiErrorResponse> {
    let db = &*state.store;

    #[cfg(feature = "v1")]
    let default_profile_id = platform
        .get_processor()
        .get_account()
        .default_profile
        .as_ref();

    #[cfg(feature = "v2")]
    let default_profile_id = Option::<&String>::None;

    match default_profile_id {
        Some(profile_id) => {
            #[cfg(feature = "v1")]
            {
                db.find_merchant_connector_account_by_profile_id_connector_name(
                    profile_id,
                    connector_name,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: format!(
                            "profile_id {} and connector_name {connector_name}",
                            profile_id.get_string_repr()
                        ),
                    },
                )
            }
            #[cfg(feature = "v2")]
            {
                let _db = db;
                let _profile_id = profile_id;
                todo!()
            }
        }
        _ => match object_reference_id {
            webhooks::ObjectReferenceId::PaymentId(payment_id_type) => {
                get_mca_from_payment_intent(
                    state,
                    platform,
                    find_payment_intent_from_payment_id_type(state, payment_id_type, platform)
                        .await?,
                    connector_name,
                )
                .await
            }
            webhooks::ObjectReferenceId::RefundId(refund_id_type) => {
                get_mca_from_payment_intent(
                    state,
                    platform,
                    find_payment_intent_from_refund_id_type(
                        state,
                        refund_id_type,
                        platform,
                        connector_name,
                    )
                    .await?,
                    connector_name,
                )
                .await
            }
            webhooks::ObjectReferenceId::MandateId(mandate_id_type) => {
                get_mca_from_payment_intent(
                    state,
                    platform,
                    find_payment_intent_from_mandate_id_type(state, mandate_id_type, platform)
                        .await?,
                    connector_name,
                )
                .await
            }
            webhooks::ObjectReferenceId::ExternalAuthenticationID(authentication_id_type) => {
                find_mca_from_authentication_id_type(state, authentication_id_type, platform).await
            }
            webhooks::ObjectReferenceId::SubscriptionId(subscription_id_type) => {
                #[cfg(feature = "v1")]
                {
                    let subscription_state = state.clone().into();
                    let subscription_handler =
                        SubscriptionHandler::new(&subscription_state, platform);
                    let mut subscription_with_handler = subscription_handler
                        .find_subscription(subscription_id_type)
                        .await?;

                    subscription_with_handler.get_mca(connector_name).await
                }
                #[cfg(feature = "v2")]
                {
                    let _db = db;
                    todo!()
                }
            }
            #[cfg(feature = "payouts")]
            webhooks::ObjectReferenceId::PayoutId(payout_id_type) => {
                get_mca_from_payout_attempt(state, platform, payout_id_type, connector_name).await
            }
        },
    }
}

// validate json format for the error
pub fn handle_json_response_deserialization_failure(
    res: types::Response,
    connector: &'static str,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
    metrics::RESPONSE_DESERIALIZATION_FAILURE
        .add(1, router_env::metric_attributes!(("connector", connector)));

    let response_data = String::from_utf8(res.response.to_vec())
        .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

    // check for whether the response is in json format
    match serde_json::from_str::<Value>(&response_data) {
        // in case of unexpected response but in json format
        Ok(_) => Err(errors::ConnectorError::ResponseDeserializationFailed)?,
        // in case of unexpected response but in html or string format
        Err(error_msg) => {
            logger::error!(deserialization_error=?error_msg);
            logger::error!("UNEXPECTED RESPONSE FROM CONNECTOR: {}", response_data);
            Ok(types::ErrorResponse {
                status_code: res.status_code,
                code: consts::NO_ERROR_CODE.to_string(),
                message: consts::UNSUPPORTED_ERROR_MESSAGE.to_string(),
                reason: Some(response_data),
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            })
        }
    }
}

pub fn get_http_status_code_type(
    status_code: u16,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let status_code_type = match status_code {
        100..=199 => "1xx",
        200..=299 => "2xx",
        300..=399 => "3xx",
        400..=499 => "4xx",
        500..=599 => "5xx",
        _ => Err(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Invalid http status code")?,
    };
    Ok(status_code_type.to_string())
}

pub fn add_connector_http_status_code_metrics(option_status_code: Option<u16>) {
    if let Some(status_code) = option_status_code {
        let status_code_type = get_http_status_code_type(status_code).ok();
        match status_code_type.as_deref() {
            Some("1xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT.add(1, &[]),
            Some("2xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT.add(1, &[]),
            Some("3xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_3XX_COUNT.add(1, &[]),
            Some("4xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_4XX_COUNT.add(1, &[]),
            Some("5xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT.add(1, &[]),
            _ => logger::info!("Skip metrics as invalid http status code received from connector"),
        };
    } else {
        logger::info!("Skip metrics as no http status code received from connector")
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
pub trait CustomerAddress {
    async fn get_address_update(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
        merchant_id: id_type::MerchantId,
    ) -> CustomResult<storage::AddressUpdate, common_utils::errors::CryptoError>;

    async fn get_domain_address(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
    ) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError>;
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerAddress for api_models::customers::CustomerRequest {
    async fn get_address_update(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
        merchant_id: id_type::MerchantId,
    ) -> CustomResult<storage::AddressUpdate, common_utils::errors::CryptoError> {
        let encrypted_data = crypto_operation(
            &state.into(),
            type_name!(storage::Address),
            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable(
                domain::FromRequestEncryptableAddress {
                    line1: address_details.line1.clone(),
                    line2: address_details.line2.clone(),
                    line3: address_details.line3.clone(),
                    state: address_details.state.clone(),
                    first_name: address_details.first_name.clone(),
                    last_name: address_details.last_name.clone(),
                    zip: address_details.zip.clone(),
                    phone_number: self.phone.clone(),
                    email: self
                        .email
                        .as_ref()
                        .map(|a| a.clone().expose().switch_strategy()),
                    origin_zip: address_details.origin_zip.clone(),
                },
            )),
            Identifier::Merchant(merchant_id.to_owned()),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())?;

        let encryptable_address =
            domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data)
                .change_context(common_utils::errors::CryptoError::EncodingFailed)?;

        Ok(storage::AddressUpdate::Update {
            city: address_details.city,
            country: address_details.country,
            line1: encryptable_address.line1,
            line2: encryptable_address.line2,
            line3: encryptable_address.line3,
            zip: encryptable_address.zip,
            state: encryptable_address.state,
            first_name: encryptable_address.first_name,
            last_name: encryptable_address.last_name,
            phone_number: encryptable_address.phone_number,
            country_code: self.phone_country_code.clone(),
            updated_by: storage_scheme.to_string(),
            email: encryptable_address.email.map(|email| {
                let encryptable: Encryptable<masking::Secret<String, pii::EmailStrategy>> =
                    Encryptable::new(
                        email.clone().into_inner().switch_strategy(),
                        email.into_encrypted(),
                    );
                encryptable
            }),
            origin_zip: encryptable_address.origin_zip,
        })
    }

    async fn get_domain_address(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
    ) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError> {
        let encrypted_data = crypto_operation(
            &state.into(),
            type_name!(storage::Address),
            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable(
                domain::FromRequestEncryptableAddress {
                    line1: address_details.line1.clone(),
                    line2: address_details.line2.clone(),
                    line3: address_details.line3.clone(),
                    state: address_details.state.clone(),
                    first_name: address_details.first_name.clone(),
                    last_name: address_details.last_name.clone(),
                    zip: address_details.zip.clone(),
                    phone_number: self.phone.clone(),
                    email: self
                        .email
                        .as_ref()
                        .map(|a| a.clone().expose().switch_strategy()),
                    origin_zip: address_details.origin_zip.clone(),
                },
            )),
            Identifier::Merchant(merchant_id.to_owned()),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())?;

        let encryptable_address =
            domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data)
                .change_context(common_utils::errors::CryptoError::EncodingFailed)?;

        let address = domain::Address {
            city: address_details.city,
            country: address_details.country,
            line1: encryptable_address.line1,
            line2: encryptable_address.line2,
            line3: encryptable_address.line3,
            zip: encryptable_address.zip,
            state: encryptable_address.state,
            first_name: encryptable_address.first_name,
            last_name: encryptable_address.last_name,
            phone_number: encryptable_address.phone_number,
            country_code: self.phone_country_code.clone(),
            merchant_id: merchant_id.to_owned(),
            address_id: generate_id(consts::ID_LENGTH, "add"),
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            updated_by: storage_scheme.to_string(),
            email: encryptable_address.email.map(|email| {
                let encryptable: Encryptable<masking::Secret<String, pii::EmailStrategy>> =
                    Encryptable::new(
                        email.clone().into_inner().switch_strategy(),
                        email.into_encrypted(),
                    );
                encryptable
            }),
            origin_zip: encryptable_address.origin_zip,
        };

        Ok(domain::CustomerAddress {
            address,
            customer_id: customer_id.to_owned(),
        })
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerAddress for api_models::customers::CustomerUpdateRequest {
    async fn get_address_update(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
        merchant_id: id_type::MerchantId,
    ) -> CustomResult<storage::AddressUpdate, common_utils::errors::CryptoError> {
        let encrypted_data = crypto_operation(
            &state.into(),
            type_name!(storage::Address),
            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable(
                domain::FromRequestEncryptableAddress {
                    line1: address_details.line1.clone(),
                    line2: address_details.line2.clone(),
                    line3: address_details.line3.clone(),
                    state: address_details.state.clone(),
                    first_name: address_details.first_name.clone(),
                    last_name: address_details.last_name.clone(),
                    zip: address_details.zip.clone(),
                    phone_number: self.phone.clone(),
                    email: self
                        .email
                        .as_ref()
                        .map(|a| a.clone().expose().switch_strategy()),
                    origin_zip: address_details.origin_zip.clone(),
                },
            )),
            Identifier::Merchant(merchant_id.to_owned()),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())?;

        let encryptable_address =
            domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data)
                .change_context(common_utils::errors::CryptoError::EncodingFailed)?;
        Ok(storage::AddressUpdate::Update {
            city: address_details.city,
            country: address_details.country,
            line1: encryptable_address.line1,
            line2: encryptable_address.line2,
            line3: encryptable_address.line3,
            zip: encryptable_address.zip,
            state: encryptable_address.state,
            first_name: encryptable_address.first_name,
            last_name: encryptable_address.last_name,
            phone_number: encryptable_address.phone_number,
            country_code: self.phone_country_code.clone(),
            updated_by: storage_scheme.to_string(),
            email: encryptable_address.email.map(|email| {
                let encryptable: Encryptable<masking::Secret<String, pii::EmailStrategy>> =
                    Encryptable::new(
                        email.clone().into_inner().switch_strategy(),
                        email.into_encrypted(),
                    );
                encryptable
            }),
            origin_zip: encryptable_address.origin_zip,
        })
    }

    async fn get_domain_address(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
    ) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError> {
        let encrypted_data = crypto_operation(
            &state.into(),
            type_name!(storage::Address),
            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable(
                domain::FromRequestEncryptableAddress {
                    line1: address_details.line1.clone(),
                    line2: address_details.line2.clone(),
                    line3: address_details.line3.clone(),
                    state: address_details.state.clone(),
                    first_name: address_details.first_name.clone(),
                    last_name: address_details.last_name.clone(),
                    zip: address_details.zip.clone(),
                    phone_number: self.phone.clone(),
                    email: self
                        .email
                        .as_ref()
                        .map(|a| a.clone().expose().switch_strategy()),
                    origin_zip: address_details.origin_zip.clone(),
                },
            )),
            Identifier::Merchant(merchant_id.to_owned()),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())?;

        let encryptable_address =
            domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data)
                .change_context(common_utils::errors::CryptoError::EncodingFailed)?;
        let address = domain::Address {
            city: address_details.city,
            country: address_details.country,
            line1: encryptable_address.line1,
            line2: encryptable_address.line2,
            line3: encryptable_address.line3,
            zip: encryptable_address.zip,
            state: encryptable_address.state,
            first_name: encryptable_address.first_name,
            last_name: encryptable_address.last_name,
            phone_number: encryptable_address.phone_number,
            country_code: self.phone_country_code.clone(),
            merchant_id: merchant_id.to_owned(),
            address_id: generate_id(consts::ID_LENGTH, "add"),
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            updated_by: storage_scheme.to_string(),
            email: encryptable_address.email.map(|email| {
                let encryptable: Encryptable<masking::Secret<String, pii::EmailStrategy>> =
                    Encryptable::new(
                        email.clone().into_inner().switch_strategy(),
                        email.into_encrypted(),
                    );
                encryptable
            }),
            origin_zip: encryptable_address.origin_zip,
        };

        Ok(domain::CustomerAddress {
            address,
            customer_id: customer_id.to_owned(),
        })
    }
}

pub fn add_apple_pay_flow_metrics(
    apple_pay_flow: &Option<domain::ApplePayFlow>,
    connector: Option<String>,
    merchant_id: id_type::MerchantId,
) {
    if let Some(flow) = apple_pay_flow {
        match flow {
            domain::ApplePayFlow::Simplified(_) => metrics::APPLE_PAY_SIMPLIFIED_FLOW.add(
                1,
                router_env::metric_attributes!(
                    (
                        "connector",
                        connector.to_owned().unwrap_or("null".to_string()),
                    ),
                    ("merchant_id", merchant_id.clone()),
                ),
            ),
            domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW.add(
                1,
                router_env::metric_attributes!(
                    (
                        "connector",
                        connector.to_owned().unwrap_or("null".to_string()),
                    ),
                    ("merchant_id", merchant_id.clone()),
                ),
            ),
        }
    }
}

pub fn add_apple_pay_payment_status_metrics(
    payment_attempt_status: enums::AttemptStatus,
    apple_pay_flow: Option<domain::ApplePayFlow>,
    connector: Option<String>,
    merchant_id: id_type::MerchantId,
) {
    if payment_attempt_status == enums::AttemptStatus::Charged {
        if let Some(flow) = apple_pay_flow {
            match flow {
                domain::ApplePayFlow::Simplified(_) => {
                    metrics::APPLE_PAY_SIMPLIFIED_FLOW_SUCCESSFUL_PAYMENT.add(
                        1,
                        router_env::metric_attributes!(
                            (
                                "connector",
                                connector.to_owned().unwrap_or("null".to_string()),
                            ),
                            ("merchant_id", merchant_id.clone()),
                        ),
                    )
                }
                domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_SUCCESSFUL_PAYMENT
                    .add(
                        1,
                        router_env::metric_attributes!(
                            (
                                "connector",
                                connector.to_owned().unwrap_or("null".to_string()),
                            ),
                            ("merchant_id", merchant_id.clone()),
                        ),
                    ),
            }
        }
    } else if payment_attempt_status == enums::AttemptStatus::Failure {
        if let Some(flow) = apple_pay_flow {
            match flow {
                domain::ApplePayFlow::Simplified(_) => {
                    metrics::APPLE_PAY_SIMPLIFIED_FLOW_FAILED_PAYMENT.add(
                        1,
                        router_env::metric_attributes!(
                            (
                                "connector",
                                connector.to_owned().unwrap_or("null".to_string()),
                            ),
                            ("merchant_id", merchant_id.clone()),
                        ),
                    )
                }
                domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_FAILED_PAYMENT.add(
                    1,
                    router_env::metric_attributes!(
                        (
                            "connector",
                            connector.to_owned().unwrap_or("null".to_string()),
                        ),
                        ("merchant_id", merchant_id.clone()),
                    ),
                ),
            }
        }
    }
}

pub fn check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata(
    metadata: Option<Value>,
) -> bool {
    let external_three_ds_connector_metadata: Option<ExternalThreeDSConnectorMetadata> = metadata
            .parse_value("ExternalThreeDSConnectorMetadata")
            .map_err(|err| logger::warn!(parsing_error=?err,"Error while parsing ExternalThreeDSConnectorMetadata"))
            .ok();
    external_three_ds_connector_metadata
        .and_then(|metadata| metadata.pull_mechanism_for_external_3ds_enabled)
        .unwrap_or(true)
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn trigger_payments_webhook<F, Op, D>(
    platform: &domain::Platform,
    business_profile: domain::Profile,
    payment_data: D,
    customer: Option<domain::Customer>,
    state: &SessionState,
    operation: Op,
) -> RouterResult<()>
where
    F: Send + Clone + Sync,
    Op: Debug,
    D: payments_core::OperationSessionGetters<F>,
{
    todo!()
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn trigger_payments_webhook<F, Op, D>(
    platform: domain::Platform,
    business_profile: domain::Profile,
    payment_data: D,
    customer: Option<domain::Customer>,
    state: &SessionState,
    operation: Op,
) -> RouterResult<()>
where
    F: Send + Clone + Sync,
    Op: Debug,
    D: payments_core::OperationSessionGetters<F>,
{
    let status = payment_data.get_payment_intent().status;
    let should_trigger_webhook = business_profile
        .get_payment_webhook_statuses()
        .contains(&status);

    if should_trigger_webhook {
        let captures = payment_data
            .get_multiple_capture_data()
            .map(|multiple_capture_data| {
                multiple_capture_data
                    .get_all_captures()
                    .into_iter()
                    .cloned()
                    .collect()
            });
        let payment_id = payment_data.get_payment_intent().get_id().to_owned();
        let payments_response = crate::core::payments::transformers::payments_to_payments_response(
            payment_data,
            captures,
            customer,
            services::AuthFlow::Merchant,
            &state.base_url,
            &operation,
            &state.conf.connector_request_reference_id_config,
            None,
            None,
            None,
        )?;

        let event_type = status.into();

        if let services::ApplicationResponse::JsonWithHeaders((payments_response_json, _)) =
            payments_response
        {
            let cloned_state = state.clone();
            // This spawns this futures in a background thread, the exception inside this future won't affect
            // the current thread and the lifecycle of spawn thread is not handled by runtime.
            // So when server shutdown won't wait for this thread's completion.

            if let Some(event_type) = event_type {
                tokio::spawn(
                    async move {
                        let primary_object_created_at = payments_response_json.created;
                        Box::pin(webhooks_core::create_event_and_trigger_outgoing_webhook(
                            cloned_state,
                            platform.clone(),
                            business_profile,
                            event_type,
                            diesel_models::enums::EventClass::Payments,
                            payment_id.get_string_repr().to_owned(),
                            common_enums::EventObjectType::PaymentDetails,
                            webhooks::OutgoingWebhookContent::PaymentDetails(Box::new(
                                payments_response_json,
                            )),
                            primary_object_created_at,
                        ))
                        .await
                    }
                    .in_current_span(),
                );
            } else {
                logger::warn!(
                    "Outgoing webhook not sent because of missing event type status mapping"
                );
            }
        }
    }

    Ok(())
}

type Handle<T> = tokio::task::JoinHandle<RouterResult<T>>;

pub async fn flatten_join_error<T>(handle: Handle<T>) -> RouterResult<T> {
    match handle.await {
        Ok(Ok(t)) => Ok(t),
        Ok(Err(err)) => Err(err),
        Err(err) => Err(err)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Join Error"),
    }
}

#[cfg(feature = "v1")]
pub async fn trigger_refund_outgoing_webhook(
    state: &SessionState,
    platform: &domain::Platform,
    refund: &diesel_models::Refund,
    profile_id: id_type::ProfileId,
) -> RouterResult<()> {
    let refund_status = refund.refund_status;

    let business_profile = state
        .store
        .find_business_profile_by_profile_id(platform.get_processor().get_key_store(), &profile_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let should_trigger_webhook = business_profile
        .get_refund_webhook_statuses()
        .contains(&refund_status);

    if should_trigger_webhook {
        let event_type = refund_status.into();
        let refund_response: api_models::refunds::RefundResponse = refund.clone().foreign_into();
        let refund_id = refund_response.refund_id.clone();
        let cloned_state = state.clone();
        let cloned_platform = platform.clone();
        let primary_object_created_at = refund_response.created_at;
        if let Some(outgoing_event_type) = event_type {
            tokio::spawn(
                async move {
                    Box::pin(webhooks_core::create_event_and_trigger_outgoing_webhook(
                        cloned_state,
                        cloned_platform,
                        business_profile,
                        outgoing_event_type,
                        diesel_models::enums::EventClass::Refunds,
                        refund_id.to_string(),
                        common_enums::EventObjectType::RefundDetails,
                        webhooks::OutgoingWebhookContent::RefundDetails(Box::new(refund_response)),
                        primary_object_created_at,
                    ))
                    .await
                }
                .in_current_span(),
            );
        } else {
            logger::warn!("Outgoing webhook not sent because of missing event type status mapping");
        };
    }
    Ok(())
}

#[cfg(feature = "v2")]
pub async fn trigger_refund_outgoing_webhook(
    state: &SessionState,
    merchant_account: &domain::MerchantAccount,
    refund: &diesel_models::Refund,
    profile_id: id_type::ProfileId,
    key_store: &domain::MerchantKeyStore,
) -> RouterResult<()> {
    todo!()
}

pub fn get_locale_from_header(headers: &actix_web::http::header::HeaderMap) -> String {
    get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers)
        .ok()
        .flatten()
        .map(|val| val.to_string())
        .unwrap_or(common_utils::consts::DEFAULT_LOCALE.to_string())
}

#[cfg(all(feature = "payouts", feature = "v1"))]
pub async fn trigger_payouts_webhook(
    state: &SessionState,
    platform: &domain::Platform,
    payout_response: &api_models::payouts::PayoutCreateResponse,
) -> RouterResult<()> {
    let profile_id = &payout_response.profile_id;
    let business_profile = state
        .store
        .find_business_profile_by_profile_id(platform.get_processor().get_key_store(), profile_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let status = &payout_response.status;
    let should_trigger_webhook = business_profile
        .get_payout_webhook_statuses()
        .contains(status);

    if should_trigger_webhook {
        let event_type = (*status).into();
        if let Some(event_type) = event_type {
            let cloned_platform = platform.clone();
            let cloned_state = state.clone();
            let cloned_response = payout_response.clone();

            // This spawns this futures in a background thread, the exception inside this future won't affect
            // the current thread and the lifecycle of spawn thread is not handled by runtime.
            // So when server shutdown won't wait for this thread's completion.
            tokio::spawn(
                async move {
                    let primary_object_created_at = cloned_response.created;
                    Box::pin(webhooks_core::create_event_and_trigger_outgoing_webhook(
                        cloned_state,
                        cloned_platform,
                        business_profile,
                        event_type,
                        diesel_models::enums::EventClass::Payouts,
                        cloned_response.payout_id.get_string_repr().to_owned(),
                        common_enums::EventObjectType::PayoutDetails,
                        webhooks::OutgoingWebhookContent::PayoutDetails(Box::new(cloned_response)),
                        primary_object_created_at,
                    ))
                    .await
                }
                .in_current_span(),
            );
        } else {
            logger::warn!("Outgoing webhook not sent because of missing event type status mapping");
        }
    }
    Ok(())
}

#[cfg(all(feature = "payouts", feature = "v2"))]
pub async fn trigger_payouts_webhook(
    state: &SessionState,
    platform: &domain::Platform,
    payout_response: &api_models::payouts::PayoutCreateResponse,
) -> RouterResult<()> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn trigger_subscriptions_outgoing_webhook(
    state: &SessionState,
    payment_response: subscription_types::PaymentResponseData,
    invoice: &hyperswitch_domain_models::invoice::Invoice,
    subscription: &hyperswitch_domain_models::subscription::Subscription,
    merchant_account: &domain::MerchantAccount,
    key_store: &domain::MerchantKeyStore,
    profile: &domain::Profile,
) -> RouterResult<()> {
    if invoice.status != common_enums::enums::InvoiceStatus::InvoicePaid {
        logger::info!("Invoice not paid, skipping outgoing webhook trigger");
        return Ok(());
    }
    let response = InvoiceSyncHandler::generate_response(subscription, invoice, &payment_response)
        .attach_printable("Subscriptions: Failed to generate response for outgoing webhook")?;

    let platform = domain::Platform::new(
        merchant_account.clone(),
        key_store.clone(),
        merchant_account.clone(),
        key_store.clone(),
    );

    let cloned_state = state.clone();
    let cloned_profile = profile.clone();
    let invoice_id = invoice.id.get_string_repr().to_owned();
    let created_at = subscription.created_at;

    tokio::spawn(async move {
        Box::pin(webhooks_core::create_event_and_trigger_outgoing_webhook(
            cloned_state,
            platform,
            cloned_profile,
            common_enums::enums::EventType::InvoicePaid,
            common_enums::enums::EventClass::Subscriptions,
            invoice_id,
            common_enums::EventObjectType::SubscriptionDetails,
            webhooks::OutgoingWebhookContent::SubscriptionDetails(Box::new(response)),
            Some(created_at),
        ))
        .await
    });

    Ok(())
}


// File: crates/router/src/compatibility/wrap.rs

use std::{future::Future, sync::Arc, time::Instant};

use actix_web::{HttpRequest, HttpResponse, Responder};
use common_utils::errors::{CustomResult, ErrorSwitch};
use router_env::{instrument, tracing, Tag};
use serde::Serialize;

use crate::{
    core::{api_locking, errors},
    events::api_logs::ApiEventMetric,
    routes::{
        app::{AppStateInfo, ReqState},
        AppState, SessionState,
    },
    services::{self, api, authentication as auth, logger},
};

#[instrument(skip(request, payload, state, func, api_authentication))]
pub async fn compatibility_api_wrap<'a, 'b, U, T, Q, F, Fut, S, E, E2>(
    flow: impl router_env::types::FlowMetric,
    state: Arc<AppState>,
    request: &'a HttpRequest,
    payload: T,
    func: F,
    api_authentication: &dyn auth::AuthenticateAndFetch<U, SessionState>,
    lock_action: api_locking::LockAction,
) -> HttpResponse
where
    F: Fn(SessionState, U, T, ReqState) -> Fut,
    Fut: Future<Output = CustomResult<api::ApplicationResponse<Q>, E2>>,
    E2: ErrorSwitch<E> + std::error::Error + Send + Sync + 'static,
    Q: Serialize + std::fmt::Debug + 'a + ApiEventMetric,
    S: TryFrom<Q> + Serialize,
    E: Serialize + error_stack::Context + actix_web::ResponseError + Clone,
    error_stack::Report<E>: services::EmbedError,
    errors::ApiErrorResponse: ErrorSwitch<E>,
    T: std::fmt::Debug + Serialize + ApiEventMetric,
{
    let request_method = request.method().as_str();
    let url_path = request.path();
    tracing::Span::current().record("request_method", request_method);
    tracing::Span::current().record("request_url_path", url_path);

    let start_instant = Instant::now();
    logger::info!(tag = ?Tag::BeginRequest, payload = ?payload);

    let server_wrap_util_res = api::server_wrap_util(
        &flow,
        state.clone().into(),
        request.headers(),
        request,
        payload,
        func,
        api_authentication,
        lock_action,
    )
    .await
    .map(|response| {
        logger::info!(api_response =? response);
        response
    });

    let res = match server_wrap_util_res {
        Ok(api::ApplicationResponse::Json(response)) => {
            let response = S::try_from(response);
            match response {
                Ok(response) => match serde_json::to_string(&response) {
                    Ok(res) => api::http_response_json(res),
                    Err(_) => api::http_response_err(
                        r#"{
                                "error": {
                                    "message": "Error serializing response from connector"
                                }
                            }"#,
                    ),
                },
                Err(_) => api::http_response_err(
                    r#"{
                        "error": {
                            "message": "Error converting juspay response to stripe response"
                        }
                    }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::JsonWithHeaders((response, headers))) => {
            let response = S::try_from(response);
            match response {
                Ok(response) => match serde_json::to_string(&response) {
                    Ok(res) => api::http_response_json_with_headers(res, headers, None, None),
                    Err(_) => api::http_response_err(
                        r#"{
                                "error": {
                                    "message": "Error serializing response from connector"
                                }
                            }"#,
                    ),
                },
                Err(_) => api::http_response_err(
                    r#"{
                        "error": {
                            "message": "Error converting juspay response to stripe response"
                        }
                    }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::StatusOk) => api::http_response_ok(),
        Ok(api::ApplicationResponse::TextPlain(text)) => api::http_response_plaintext(text),
        Ok(api::ApplicationResponse::FileData((file_data, content_type))) => {
            api::http_response_file_data(file_data, content_type)
        }
        Ok(api::ApplicationResponse::JsonForRedirection(response)) => {
            match serde_json::to_string(&response) {
                Ok(res) => api::http_redirect_response(res, response),
                Err(_) => api::http_response_err(
                    r#"{
                    "error": {
                        "message": "Error serializing response from connector"
                    }
                }"#,
                ),
            }
        }
        Ok(api::ApplicationResponse::Form(redirection_data)) => {
            let config = state.conf();
            api::build_redirection_form(
                &redirection_data.redirect_form,
                redirection_data.payment_method_data,
                redirection_data.amount,
                redirection_data.currency,
                config,
            )
            .respond_to(request)
            .map_into_boxed_body()
        }

        Ok(api::ApplicationResponse::GenericLinkForm(boxed_generic_link_data)) => {
            let link_type = (boxed_generic_link_data).data.to_string();
            match services::generic_link_response::build_generic_link_html(
                boxed_generic_link_data.data,
                boxed_generic_link_data.locale,
            ) {
                Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                Err(_) => {
                    api::http_response_err(format!("Error while rendering {link_type} HTML page"))
                }
            }
        }

        Ok(api::ApplicationResponse::PaymentLinkForm(boxed_payment_link_data)) => {
            match *boxed_payment_link_data {
                api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
                    match api::build_payment_link_html(payment_link_data) {
                        Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                        Err(_) => api::http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link html page"
                                }
                            }"#,
                        ),
                    }
                }
                api::PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
                    match api::get_payment_link_status(payment_link_data) {
                        Ok(rendered_html) => api::http_response_html_data(rendered_html, None),
                        Err(_) => api::http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link status page"
                                }
                            }"#,
                        ),
                    }
                }
            }
        }
        Err(error) => api::log_and_return_error_response(error),
    };

    let response_code = res.status().as_u16();
    let end_instant = Instant::now();
    let request_duration = end_instant.saturating_duration_since(start_instant);
    logger::info!(
        tag = ?Tag::EndRequest,
        status_code = response_code,
        time_taken_ms = request_duration.as_millis(),
    );

    res
}


// File: crates/router/src/compatibility/stripe.rs

pub mod app;
pub mod customers;
pub mod payment_intents;
pub mod refunds;
pub mod setup_intents;
pub mod webhooks;
#[cfg(feature = "v1")]
use actix_web::{web, Scope};
pub mod errors;
#[cfg(feature = "v1")]
use crate::routes;

#[cfg(feature = "v1")]
pub struct StripeApis;

#[cfg(feature = "v1")]
impl StripeApis {
    pub fn server(state: routes::AppState) -> Scope {
        let max_depth = 10;
        let strict = false;
        web::scope("/vs/v1")
            .app_data(web::Data::new(serde_qs::Config::new(max_depth, strict)))
            .service(app::SetupIntents::server(state.clone()))
            .service(app::PaymentIntents::server(state.clone()))
            .service(app::Refunds::server(state.clone()))
            .service(app::Customers::server(state.clone()))
            .service(app::Webhooks::server(state.clone()))
            .service(app::Mandates::server(state))
    }
}


// File: crates/router/src/compatibility/stripe/payment_intents.rs

pub mod types;

use actix_web::{web, HttpRequest, HttpResponse};
use api_models::payments as payment_types;
#[cfg(feature = "v1")]
use error_stack::report;
#[cfg(feature = "v1")]
use router_env::Tag;
use router_env::{instrument, tracing, Flow};

use crate::{
    compatibility::{stripe::errors, wrap},
    core::payments,
    routes::{self},
    services::{api, authentication as auth},
};
#[cfg(feature = "v1")]
use crate::{
    core::api_locking::GetLockingInput, logger, routes::payments::get_or_generate_payment_id,
    types::api as api_types,
};

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate, payment_id))]
pub async fn payment_intents_create(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let payload: types::StripePaymentIntentRequest = match qs_config
        .deserialize_bytes(&form_payload)
        .map_err(|err| report!(errors::StripeErrorCode::from(err)))
    {
        Ok(p) => p,
        Err(err) => return api::log_and_return_error_response(err),
    };

    tracing::Span::current().record(
        "payment_id",
        payload
            .id
            .as_ref()
            .map(|payment_id| payment_id.get_string_repr())
            .unwrap_or_default(),
    );

    logger::info!(tag = ?Tag::CompatibilityLayerRequest, payload = ?payload);

    let mut create_payment_req: payment_types::PaymentsRequest = match payload.try_into() {
        Ok(req) => req,
        Err(err) => return api::log_and_return_error_response(err),
    };

    if let Err(err) = get_or_generate_payment_id(&mut create_payment_req) {
        return api::log_and_return_error_response(err);
    }
    let flow = Flow::PaymentsCreate;
    let locking_action = create_payment_req.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        create_payment_req,
        |state, auth: auth::AuthenticationData, req, req_state| {
            let platform = auth.into();
            let eligible_connectors = req.connector.clone();
            payments::payments_core::<
                api_types::Authorize,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Authorize>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentCreate,
                req,
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                eligible_connectors,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsRetrieveForceSync))]
pub async fn payment_intents_retrieve(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::PaymentId>,
    query_payload: web::Query<types::StripePaymentRetrieveBody>,
) -> HttpResponse {
    let payload = payment_types::PaymentsRetrieveRequest {
        resource_id: api_types::PaymentIdType::PaymentIntentId(path.into_inner()),
        merchant_id: None,
        force_sync: true,
        connector: None,
        param: None,
        merchant_connector_details: None,
        client_secret: query_payload.client_secret.clone(),
        expand_attempts: None,
        expand_captures: None,
        all_keys_required: None,
    };

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

    let (auth_type, auth_flow) =
        match auth::check_client_secret_and_get_auth(req.headers(), &payload, api_auth) {
            Ok(auth) => auth,
            Err(err) => return api::log_and_return_error_response(report!(err)),
        };

    let flow = Flow::PaymentsRetrieveForceSync;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, payload, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::PSync,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::PSync>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentStatus,
                payload,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow))]
pub async fn payment_intents_retrieve_with_gateway_creds(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
) -> HttpResponse {
    let json_payload: payment_types::PaymentRetrieveBodyWithCredentials = match qs_config
        .deserialize_bytes(&form_payload)
        .map_err(|err| report!(errors::StripeErrorCode::from(err)))
    {
        Ok(p) => p,
        Err(err) => return api::log_and_return_error_response(err),
    };

    let payload = payment_types::PaymentsRetrieveRequest {
        resource_id: payment_types::PaymentIdType::PaymentIntentId(json_payload.payment_id),
        merchant_id: json_payload.merchant_id.clone(),
        force_sync: json_payload.force_sync.unwrap_or(false),
        merchant_connector_details: json_payload.merchant_connector_details.clone(),
        ..Default::default()
    };

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

    let (auth_type, _auth_flow) = match auth::get_auth_type_and_flow(req.headers(), api_auth) {
        Ok(auth) => auth,
        Err(err) => return api::log_and_return_error_response(report!(err)),
    };

    let flow = match json_payload.force_sync {
        Some(true) => Flow::PaymentsRetrieveForceSync,
        _ => Flow::PaymentsRetrieve,
    };

    tracing::Span::current().record("flow", flow.to_string());

    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::PSync,
                payment_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::PSync>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentStatus,
                req,
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdate))]
pub async fn payment_intents_update(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let payment_id = path.into_inner();
    let stripe_payload: types::StripePaymentIntentRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    let mut payload: payment_types::PaymentsRequest = match stripe_payload.try_into() {
        Ok(req) => req,
        Err(err) => return api::log_and_return_error_response(err),
    };

    payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(payment_id));

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

    let (auth_type, auth_flow) = match auth::get_auth_type_and_flow(req.headers(), api_auth) {
        Ok(auth) => auth,
        Err(err) => return api::log_and_return_error_response(report!(err)),
    };

    let flow = Flow::PaymentsUpdate;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            let eligible_connectors = req.connector.clone();
            payments::payments_core::<
                api_types::Authorize,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Authorize>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentUpdate,
                req,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                eligible_connectors,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm, payment_id))]
pub async fn payment_intents_confirm(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let payment_id = path.into_inner();
    let stripe_payload: types::StripePaymentIntentRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

    tracing::Span::current().record(
        "payment_id",
        stripe_payload.id.as_ref().map(|id| id.get_string_repr()),
    );

    logger::info!(tag = ?Tag::CompatibilityLayerRequest, payload = ?stripe_payload);

    let mut payload: payment_types::PaymentsRequest = match stripe_payload.try_into() {
        Ok(req) => req,
        Err(err) => return api::log_and_return_error_response(err),
    };

    payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(payment_id));
    payload.confirm = Some(true);

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

    let (auth_type, auth_flow) =
        match auth::check_client_secret_and_get_auth(req.headers(), &payload, api_auth) {
            Ok(auth) => auth,
            Err(err) => return api::log_and_return_error_response(err),
        };

    let flow = Flow::PaymentsConfirm;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            let eligible_connectors = req.connector.clone();
            payments::payments_core::<
                api_types::Authorize,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Authorize>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentConfirm,
                req,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                eligible_connectors,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCapture, payment_id))]
pub async fn payment_intents_capture(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let stripe_payload: payment_types::PaymentsCaptureRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

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

    logger::info!(tag = ?Tag::CompatibilityLayerRequest, payload = ?stripe_payload);

    let payload = payment_types::PaymentsCaptureRequest {
        payment_id: path.into_inner(),
        ..stripe_payload
    };

    let flow = Flow::PaymentsCapture;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth: auth::AuthenticationData, payload, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::Capture,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Capture>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentCapture,
                payload,
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        locking_action,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCancel, payment_id))]
pub async fn payment_intents_cancel(
    state: web::Data<routes::AppState>,
    qs_config: web::Data<serde_qs::Config>,
    req: HttpRequest,
    form_payload: web::Bytes,
    path: web::Path<common_utils::id_type::PaymentId>,
) -> HttpResponse {
    let payment_id = path.into_inner();
    let stripe_payload: types::StripePaymentCancelRequest = match qs_config
        .deserialize_bytes(&form_payload)
    {
        Ok(p) => p,
        Err(err) => {
            return api::log_and_return_error_response(report!(errors::StripeErrorCode::from(err)))
        }
    };

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

    logger::info!(tag = ?Tag::CompatibilityLayerRequest, payload = ?stripe_payload);

    let mut payload: payment_types::PaymentsCancelRequest = stripe_payload.into();
    payload.payment_id = payment_id;

    let api_auth = auth::ApiKeyAuth {
        is_connected_allowed: false,
        is_platform_allowed: false,
    };

    let (auth_type, auth_flow) = match auth::get_auth_type_and_flow(req.headers(), api_auth) {
        Ok(auth) => auth,
        Err(err) => return api::log_and_return_error_response(report!(err)),
    };

    let flow = Flow::PaymentsCancel;
    let locking_action = payload.get_locking_input(flow.clone());
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth, req, req_state| {
            let platform = auth.into();
            payments::payments_core::<
                api_types::Void,
                api_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Void>,
            >(
                state,
                req_state,
                platform,
                None,
                payments::PaymentCancel,
                req,
                auth_flow,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                hyperswitch_domain_models::payments::HeaderPayload::default(),
            )
        },
        &*auth_type,
        locking_action,
    ))
    .await
}
#[instrument(skip_all, fields(flow = ?Flow::PaymentsList))]
#[cfg(feature = "olap")]
pub async fn payment_intent_list(
    state: web::Data<routes::AppState>,
    req: HttpRequest,
    payload: web::Query<types::StripePaymentListConstraints>,
) -> HttpResponse {
    let payload = match payment_types::PaymentListConstraints::try_from(payload.into_inner()) {
        Ok(p) => p,
        Err(err) => return api::log_and_return_error_response(err),
    };
    use crate::core::api_locking;
    let flow = Flow::PaymentsList;
    Box::pin(wrap::compatibility_api_wrap::<
        _,
        _,
        _,
        _,
        _,
        types::StripePaymentIntentListResponse,
        errors::StripeErrorCode,
        _,
    >(
        flow,
        state.into_inner(),
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let platform = auth.into();
            payments::list_payments(state, platform, None, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}