File: crates/router/src/core/unified_authentication_service.rs
 Public functions: 6

pub mod types;
use std::str::FromStr;

pub mod utils;
#[cfg(feature = "v1")]
use api_models::authentication::{
    AuthenticationEligibilityRequest, AuthenticationEligibilityResponse,
    AuthenticationSyncPostUpdateRequest, AuthenticationSyncRequest, AuthenticationSyncResponse,
};
use api_models::{
    authentication::{
        AcquirerDetails, AuthenticationAuthenticateRequest, AuthenticationAuthenticateResponse,
        AuthenticationCreateRequest, AuthenticationResponse,
    },
    payments,
};
#[cfg(feature = "v1")]
use common_utils::{ext_traits::ValueExt, types::keymanager::ToEncryptable};
use diesel_models::authentication::{Authentication, AuthenticationNew};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    errors::api_error_response::ApiErrorResponse,
    payment_method_data,
    router_request_types::{
        authentication::{MessageCategory, PreAuthenticationData},
        unified_authentication_service::{
            AuthenticationInfo, PaymentDetails, ServiceSessionIds, ThreeDsMetaData,
            TransactionDetails, UasAuthenticationRequestData, UasConfirmationRequestData,
            UasPostAuthenticationRequestData, UasPreAuthenticationRequestData,
        },
        BrowserInformation,
    },
    types::{
        UasAuthenticationRouterData, UasPostAuthenticationRouterData,
        UasPreAuthenticationRouterData,
    },
};
use masking::{ExposeInterface, PeekInterface};

use super::{
    errors::{RouterResponse, RouterResult},
    payments::helpers::MerchantConnectorAccountType,
};
use crate::{
    consts,
    core::{
        authentication::utils as auth_utils,
        errors::utils::StorageErrorExt,
        payments::helpers,
        unified_authentication_service::types::{
            ClickToPay, ExternalAuthentication, UnifiedAuthenticationService,
            UNIFIED_AUTHENTICATION_SERVICE,
        },
        utils as core_utils,
    },
    db::domain,
    routes::SessionState,
    services::AuthFlow,
    types::{domain::types::AsyncLift, transformers::ForeignTryFrom},
};
#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl UnifiedAuthenticationService for ClickToPay {
    fn get_pre_authentication_request_data(
        _payment_method_data: Option<&domain::PaymentMethodData>,
        service_details: Option<payments::CtpServiceDetails>,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRequestData> {
        let domain_service_details = hyperswitch_domain_models::router_request_types::unified_authentication_service::CtpServiceDetails {
            service_session_ids: Some(ServiceSessionIds {
                merchant_transaction_id: service_details
                    .as_ref()
                    .and_then(|details| details.merchant_transaction_id.clone()),
                correlation_id: service_details
                    .as_ref()
                    .and_then(|details| details.correlation_id.clone()),
                x_src_flow_id: service_details
                    .as_ref()
                    .and_then(|details| details.x_src_flow_id.clone()),
            }),
            payment_details: None,
        };

        let transaction_details = TransactionDetails {
            amount: Some(amount),
            currency,
            device_channel: None,
            message_category: None,
        };

        let authentication_info = Some(AuthenticationInfo {
            authentication_type: None,
            authentication_reasons: None,
            consent_received: false, // This is not relevant in this flow so keeping it as false
            is_authenticated: false, // This is not relevant in this flow so keeping it as false
            locale: None,
            supported_card_brands: None,
            encrypted_payload: service_details
                .as_ref()
                .and_then(|details| details.encrypted_payload.clone()),
        });
        Ok(UasPreAuthenticationRequestData {
            service_details: Some(domain_service_details),
            transaction_details: Some(transaction_details),
            payment_details: None,
            authentication_info,
            merchant_details: merchant_details.cloned(),
            billing_address: billing_address.cloned(),
            acquirer_bin,
            acquirer_merchant_id,
        })
    }

    async fn pre_authentication(
        state: &SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        payment_method_data: Option<&domain::PaymentMethodData>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRouterData> {
        let pre_authentication_data = Self::get_pre_authentication_request_data(
            payment_method_data,
            service_details,
            amount,
            currency,
            merchant_details,
            billing_address,
            acquirer_bin,
            acquirer_merchant_id,
        )?;

        let pre_auth_router_data: UasPreAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                pre_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            pre_auth_router_data,
        ))
        .await
    }

    async fn post_authentication(
        state: &SessionState,
        _business_profile: &domain::Profile,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        merchant_id: &common_utils::id_type::MerchantId,
        _authentication: Option<&Authentication>,
    ) -> RouterResult<UasPostAuthenticationRouterData> {
        let post_authentication_data = UasPostAuthenticationRequestData {
            threeds_server_transaction_id: None,
        };

        let post_auth_router_data: UasPostAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                post_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            post_auth_router_data,
        )
        .await
    }

    async fn confirmation(
        state: &SessionState,
        authentication_id: Option<&common_utils::id_type::AuthenticationId>,
        currency: Option<common_enums::Currency>,
        status: common_enums::AttemptStatus,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        payment_method: common_enums::PaymentMethod,
        net_amount: common_utils::types::MinorUnit,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> RouterResult<()> {
        let authentication_id = authentication_id
            .ok_or(ApiErrorResponse::InternalServerError)
            .attach_printable("Missing authentication id in tracker")?;

        let currency = currency.ok_or(ApiErrorResponse::MissingRequiredField {
            field_name: "currency",
        })?;

        let current_time = common_utils::date_time::date_as_yyyymmddthhmmssmmmz()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get current time")?;

        let payment_attempt_status = status;

        let (checkout_event_status, confirmation_reason) =
            utils::get_checkout_event_status_and_reason(payment_attempt_status);

        let click_to_pay_details = service_details.clone();

        let authentication_confirmation_data = UasConfirmationRequestData {
            x_src_flow_id: click_to_pay_details
                .as_ref()
                .and_then(|details| details.x_src_flow_id.clone()),
            transaction_amount: net_amount,
            transaction_currency: currency,
            checkout_event_type: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented
            checkout_event_status: checkout_event_status.clone(),
            confirmation_status: checkout_event_status.clone(),
            confirmation_reason,
            confirmation_timestamp: Some(current_time),
            network_authorization_code: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented
            network_transaction_identifier: Some("mastercard".to_string()), // hardcoded to 'mastercard' since only mastercard has confirmation flow requirement
            correlation_id: click_to_pay_details
                .clone()
                .and_then(|details| details.correlation_id),
            merchant_transaction_id: click_to_pay_details
                .and_then(|details| details.merchant_transaction_id),
        };

        let authentication_confirmation_router_data : hyperswitch_domain_models::types::UasAuthenticationConfirmationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method,
            merchant_id.clone(),
            None,
            authentication_confirmation_data,
            merchant_connector_account,
            Some(authentication_id.to_owned()),
            payment_id.cloned(),
        )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            authentication_confirmation_router_data,
        )
        .await
        .ok(); // marking this as .ok() since this is not a required step at our end for completing the transaction

        Ok(())
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl UnifiedAuthenticationService for ExternalAuthentication {
    fn get_pre_authentication_request_data(
        payment_method_data: Option<&domain::PaymentMethodData>,
        _service_details: Option<payments::CtpServiceDetails>,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRequestData> {
        let payment_method_data = payment_method_data
            .ok_or(ApiErrorResponse::InternalServerError)
            .attach_printable("payment_method_data is missing")?;
        let payment_details =
            if let payment_method_data::PaymentMethodData::Card(card) = payment_method_data {
                Some(PaymentDetails {
                    pan: card.card_number.clone(),
                    digital_card_id: None,
                    payment_data_type: None,
                    encrypted_src_card_details: None,
                    card_expiry_month: card.card_exp_month.clone(),
                    card_expiry_year: card.card_exp_year.clone(),
                    cardholder_name: card.card_holder_name.clone(),
                    card_token_number: None,
                    account_type: None,
                    card_cvc: Some(card.card_cvc.clone()),
                })
            } else {
                None
            };
        let transaction_details = TransactionDetails {
            amount: Some(amount),
            currency,
            device_channel: None,
            message_category: None,
        };
        Ok(UasPreAuthenticationRequestData {
            service_details: None,
            transaction_details: Some(transaction_details),
            payment_details,
            authentication_info: None,
            merchant_details: merchant_details.cloned(),
            billing_address: billing_address.cloned(),
            acquirer_bin,
            acquirer_merchant_id,
        })
    }

    #[allow(clippy::too_many_arguments)]
    async fn pre_authentication(
        state: &SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        payment_method_data: Option<&domain::PaymentMethodData>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRouterData> {
        let pre_authentication_data = Self::get_pre_authentication_request_data(
            payment_method_data,
            service_details,
            amount,
            currency,
            merchant_details,
            billing_address,
            acquirer_bin,
            acquirer_merchant_id,
        )?;

        let pre_auth_router_data: UasPreAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                pre_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            pre_auth_router_data,
        ))
        .await
    }

    fn get_authentication_request_data(
        browser_details: Option<BrowserInformation>,
        amount: Option<common_utils::types::MinorUnit>,
        currency: Option<common_enums::Currency>,
        message_category: MessageCategory,
        device_channel: payments::DeviceChannel,
        authentication: Authentication,
        return_url: Option<String>,
        sdk_information: Option<payments::SdkInformation>,
        threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        email: Option<common_utils::pii::Email>,
        webhook_url: String,
    ) -> RouterResult<UasAuthenticationRequestData> {
        Ok(UasAuthenticationRequestData {
            browser_details,
            transaction_details: TransactionDetails {
                amount,
                currency,
                device_channel: Some(device_channel),
                message_category: Some(message_category),
            },
            pre_authentication_data: PreAuthenticationData {
                threeds_server_transaction_id: authentication.threeds_server_transaction_id.ok_or(
                    ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.threeds_server_transaction_id",
                    },
                )?,
                message_version: authentication.message_version.ok_or(
                    ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.message_version",
                    },
                )?,
                acquirer_bin: authentication.acquirer_bin,
                acquirer_merchant_id: authentication.acquirer_merchant_id,
                acquirer_country_code: authentication.acquirer_country_code,
                connector_metadata: authentication.connector_metadata,
            },
            return_url,
            sdk_information,
            email,
            threeds_method_comp_ind,
            webhook_url,
        })
    }

    #[allow(clippy::too_many_arguments)]
    async fn authentication(
        state: &SessionState,
        business_profile: &domain::Profile,
        payment_method: &common_enums::PaymentMethod,
        browser_details: Option<BrowserInformation>,
        amount: Option<common_utils::types::MinorUnit>,
        currency: Option<common_enums::Currency>,
        message_category: MessageCategory,
        device_channel: payments::DeviceChannel,
        authentication: Authentication,
        return_url: Option<String>,
        sdk_information: Option<payments::SdkInformation>,
        threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        email: Option<common_utils::pii::Email>,
        webhook_url: String,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        payment_id: Option<common_utils::id_type::PaymentId>,
    ) -> RouterResult<UasAuthenticationRouterData> {
        let authentication_data =
            <Self as UnifiedAuthenticationService>::get_authentication_request_data(
                browser_details,
                amount,
                currency,
                message_category,
                device_channel,
                authentication.clone(),
                return_url,
                sdk_information,
                threeds_method_comp_ind,
                email,
                webhook_url,
            )?;
        let auth_router_data: UasAuthenticationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method.to_owned(),
            business_profile.merchant_id.clone(),
            None,
            authentication_data,
            merchant_connector_account,
            Some(authentication.authentication_id.to_owned()),
            payment_id,
        )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            auth_router_data,
        ))
        .await
    }

    fn get_post_authentication_request_data(
        authentication: Option<Authentication>,
    ) -> RouterResult<UasPostAuthenticationRequestData> {
        Ok(UasPostAuthenticationRequestData {
            // authentication.threeds_server_transaction_id is mandatory for post-authentication in ExternalAuthentication
            threeds_server_transaction_id: Some(
                authentication
                    .and_then(|auth| auth.threeds_server_transaction_id)
                    .ok_or(ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.threeds_server_transaction_id",
                    })?,
            ),
        })
    }

    async fn post_authentication(
        state: &SessionState,
        business_profile: &domain::Profile,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        _merchant_id: &common_utils::id_type::MerchantId,
        authentication: Option<&Authentication>,
    ) -> RouterResult<UasPostAuthenticationRouterData> {
        let authentication_data =
            <Self as UnifiedAuthenticationService>::get_post_authentication_request_data(
                authentication.cloned(),
            )?;
        let auth_router_data: UasPostAuthenticationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method,
            business_profile.merchant_id.clone(),
            None,
            authentication_data,
            merchant_connector_account,
            Some(authentication_id.clone()),
            payment_id.cloned(),
        )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            auth_router_data,
        )
        .await
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn create_new_authentication(
    state: &SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    authentication_connector: Option<String>,
    profile_id: common_utils::id_type::ProfileId,
    payment_id: Option<common_utils::id_type::PaymentId>,
    merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    authentication_id: &common_utils::id_type::AuthenticationId,
    service_details: Option<payments::CtpServiceDetails>,
    authentication_status: common_enums::AuthenticationStatus,
    network_token: Option<payment_method_data::NetworkTokenData>,
    organization_id: common_utils::id_type::OrganizationId,
    force_3ds_challenge: Option<bool>,
    psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    acquirer_bin: Option<String>,
    acquirer_merchant_id: Option<String>,
    acquirer_country_code: Option<String>,
    amount: Option<common_utils::types::MinorUnit>,
    currency: Option<common_enums::Currency>,
    return_url: Option<String>,
    profile_acquirer_id: Option<common_utils::id_type::ProfileAcquirerId>,
) -> RouterResult<Authentication> {
    let service_details_value = service_details
        .map(serde_json::to_value)
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable(
            "unable to parse service details into json value while inserting to DB",
        )?;
    let authentication_client_secret = Some(common_utils::generate_id_with_default_len(&format!(
        "{}_secret",
        authentication_id.get_string_repr()
    )));
    let new_authorization = AuthenticationNew {
        authentication_id: authentication_id.to_owned(),
        merchant_id,
        authentication_connector,
        connector_authentication_id: None,
        payment_method_id: "".to_string(),
        authentication_type: None,
        authentication_status,
        authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused,
        error_message: None,
        error_code: None,
        connector_metadata: None,
        maximum_supported_version: None,
        threeds_server_transaction_id: None,
        cavv: None,
        authentication_flow_type: None,
        message_version: None,
        eci: network_token.and_then(|data| data.eci),
        trans_status: None,
        acquirer_bin,
        acquirer_merchant_id,
        three_ds_method_data: None,
        three_ds_method_url: None,
        acs_url: None,
        challenge_request: None,
        acs_reference_number: None,
        acs_trans_id: None,
        acs_signed_content: None,
        profile_id,
        payment_id,
        merchant_connector_id,
        ds_trans_id: None,
        directory_server_id: None,
        acquirer_country_code,
        service_details: service_details_value,
        organization_id,
        authentication_client_secret,
        force_3ds_challenge,
        psd2_sca_exemption_type,
        return_url,
        amount,
        currency,
        billing_address: None,
        shipping_address: None,
        browser_info: None,
        email: None,
        profile_acquirer_id,
        challenge_code: None,
        challenge_cancel: None,
        challenge_code_reason: None,
        message_extension: None,
    };
    state
        .store
        .insert_authentication(new_authorization)
        .await
        .to_duplicate_response(ApiErrorResponse::GenericDuplicateError {
            message: format!(
                "Authentication with authentication_id {} already exists",
                authentication_id.get_string_repr()
            ),
        })
}

// Modular authentication
#[cfg(feature = "v1")]
pub async fn authentication_create_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationCreateRequest,
) -> RouterResponse<AuthenticationResponse> {
    let db = &*state.store;
    let merchant_account = merchant_context.get_merchant_account();
    let merchant_id = merchant_account.get_id();
    let key_manager_state = (&state).into();
    let profile_id = core_utils::get_profile_id_from_business_details(
        &key_manager_state,
        None,
        None,
        &merchant_context,
        req.profile_id.as_ref(),
        db,
        true,
    )
    .await?;

    let business_profile = db
        .find_business_profile_by_profile_id(
            &key_manager_state,
            merchant_context.get_merchant_key_store(),
            &profile_id,
        )
        .await
        .to_not_found_response(ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;
    let organization_id = merchant_account.organization_id.clone();
    let authentication_id = common_utils::id_type::AuthenticationId::generate_authentication_id(
        consts::AUTHENTICATION_ID_PREFIX,
    );

    let force_3ds_challenge = Some(
        req.force_3ds_challenge
            .unwrap_or(business_profile.force_3ds_challenge),
    );

    // Priority logic: First check req.acquirer_details, then fallback to profile_acquirer_id lookup
    let (acquirer_bin, acquirer_merchant_id, acquirer_country_code) =
        if let Some(acquirer_details) = &req.acquirer_details {
            // Priority 1: Use acquirer_details from request if present
            (
                acquirer_details.acquirer_bin.clone(),
                acquirer_details.acquirer_merchant_id.clone(),
                acquirer_details.merchant_country_code.clone(),
            )
        } else {
            // Priority 2: Fallback to profile_acquirer_id lookup
            let acquirer_details = req.profile_acquirer_id.clone().and_then(|acquirer_id| {
                business_profile
                    .acquirer_config_map
                    .and_then(|acquirer_config_map| {
                        acquirer_config_map.0.get(&acquirer_id).cloned()
                    })
            });

            acquirer_details
                .as_ref()
                .map(|details| {
                    (
                        Some(details.acquirer_bin.clone()),
                        Some(details.acquirer_assigned_merchant_id.clone()),
                        business_profile
                            .merchant_country_code
                            .map(|code| code.get_country_code().to_owned()),
                    )
                })
                .unwrap_or((None, None, None))
        };

    let new_authentication = create_new_authentication(
        &state,
        merchant_id.clone(),
        req.authentication_connector
            .map(|connector| connector.to_string()),
        profile_id.clone(),
        None,
        None,
        &authentication_id,
        None,
        common_enums::AuthenticationStatus::Started,
        None,
        organization_id,
        force_3ds_challenge,
        req.psd2_sca_exemption_type,
        acquirer_bin,
        acquirer_merchant_id,
        acquirer_country_code,
        Some(req.amount),
        Some(req.currency),
        req.return_url,
        req.profile_acquirer_id.clone(),
    )
    .await?;

    let acquirer_details = Some(AcquirerDetails {
        acquirer_bin: new_authentication.acquirer_bin.clone(),
        acquirer_merchant_id: new_authentication.acquirer_merchant_id.clone(),
        merchant_country_code: new_authentication.acquirer_country_code.clone(),
    });

    let amount = new_authentication
        .amount
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("amount failed to get amount from authentication table")?;
    let currency = new_authentication
        .currency
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("currency failed to get currency from authentication table")?;

    let response = AuthenticationResponse::foreign_try_from((
        new_authentication.clone(),
        amount,
        currency,
        profile_id,
        acquirer_details,
        new_authentication.profile_acquirer_id,
    ))?;

    Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
        response,
    ))
}

impl
    ForeignTryFrom<(
        Authentication,
        common_utils::types::MinorUnit,
        common_enums::Currency,
        common_utils::id_type::ProfileId,
        Option<AcquirerDetails>,
        Option<common_utils::id_type::ProfileAcquirerId>,
    )> for AuthenticationResponse
{
    type Error = error_stack::Report<ApiErrorResponse>;
    fn foreign_try_from(
        (authentication, amount, currency, profile_id, acquirer_details, profile_acquirer_id): (
            Authentication,
            common_utils::types::MinorUnit,
            common_enums::Currency,
            common_utils::id_type::ProfileId,
            Option<AcquirerDetails>,
            Option<common_utils::id_type::ProfileAcquirerId>,
        ),
    ) -> Result<Self, Self::Error> {
        let authentication_connector = authentication
            .authentication_connector
            .map(|connector| common_enums::AuthenticationConnectors::from_str(&connector))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Incorrect authentication connector stored in table")?;
        Ok(Self {
            authentication_id: authentication.authentication_id,
            client_secret: authentication
                .authentication_client_secret
                .map(masking::Secret::new),
            amount,
            currency,
            force_3ds_challenge: authentication.force_3ds_challenge,
            merchant_id: authentication.merchant_id,
            status: authentication.authentication_status,
            authentication_connector,
            return_url: authentication.return_url,
            created_at: Some(authentication.created_at),
            error_code: authentication.error_code,
            error_message: authentication.error_message,
            profile_id: Some(profile_id),
            psd2_sca_exemption_type: authentication.psd2_sca_exemption_type,
            acquirer_details,
            profile_acquirer_id,
        })
    }
}

#[cfg(feature = "v1")]
impl
    ForeignTryFrom<(
        Authentication,
        api_models::authentication::NextAction,
        common_utils::id_type::ProfileId,
        Option<payments::Address>,
        Option<payments::Address>,
        Option<payments::BrowserInformation>,
        common_utils::crypto::OptionalEncryptableEmail,
    )> for AuthenticationEligibilityResponse
{
    type Error = error_stack::Report<ApiErrorResponse>;
    fn foreign_try_from(
        (authentication, next_action, profile_id, billing, shipping, browser_information, email): (
            Authentication,
            api_models::authentication::NextAction,
            common_utils::id_type::ProfileId,
            Option<payments::Address>,
            Option<payments::Address>,
            Option<payments::BrowserInformation>,
            common_utils::crypto::OptionalEncryptableEmail,
        ),
    ) -> Result<Self, Self::Error> {
        let authentication_connector = authentication
            .authentication_connector
            .map(|connector| common_enums::AuthenticationConnectors::from_str(&connector))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Incorrect authentication connector stored in table")?;
        let three_ds_method_url = authentication
            .three_ds_method_url
            .map(|url| url::Url::parse(&url))
            .transpose()
            .map_err(error_stack::Report::from)
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse three_ds_method_url")?;

        let three_ds_data = Some(api_models::authentication::ThreeDsData {
            three_ds_server_transaction_id: authentication.threeds_server_transaction_id,
            maximum_supported_3ds_version: authentication.maximum_supported_version,
            connector_authentication_id: authentication.connector_authentication_id,
            three_ds_method_data: authentication.three_ds_method_data,
            three_ds_method_url,
            message_version: authentication.message_version,
            directory_server_id: authentication.directory_server_id,
        });
        let acquirer_details = AcquirerDetails {
            acquirer_bin: authentication.acquirer_bin,
            acquirer_merchant_id: authentication.acquirer_merchant_id,
            merchant_country_code: authentication.acquirer_country_code,
        };
        Ok(Self {
            authentication_id: authentication.authentication_id,
            next_action,
            status: authentication.authentication_status,
            eligibility_response_params: three_ds_data
                .map(api_models::authentication::EligibilityResponseParams::ThreeDsData),
            connector_metadata: authentication.connector_metadata,
            profile_id,
            error_message: authentication.error_message,
            error_code: authentication.error_code,
            billing,
            shipping,
            authentication_connector,
            browser_information,
            email,
            acquirer_details: Some(acquirer_details),
        })
    }
}

#[cfg(feature = "v1")]
pub async fn authentication_eligibility_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationEligibilityRequest,
    authentication_id: common_utils::id_type::AuthenticationId,
) -> RouterResponse<AuthenticationEligibilityResponse> {
    let merchant_account = merchant_context.get_merchant_account();
    let merchant_id = merchant_account.get_id();
    let db = &*state.store;
    let authentication = db
        .find_authentication_by_merchant_id_authentication_id(merchant_id, &authentication_id)
        .await
        .to_not_found_response(ApiErrorResponse::AuthenticationNotFound {
            id: authentication_id.get_string_repr().to_owned(),
        })?;

    req.client_secret
        .clone()
        .map(|client_secret| {
            utils::authenticate_authentication_client_secret_and_check_expiry(
                client_secret.peek(),
                &authentication,
            )
        })
        .transpose()?;

    ensure_not_terminal_status(authentication.trans_status.clone())?;

    let key_manager_state = (&state).into();

    let profile_id = core_utils::get_profile_id_from_business_details(
        &key_manager_state,
        None,
        None,
        &merchant_context,
        None,
        db,
        true,
    )
    .await?;

    let business_profile = db
        .find_business_profile_by_profile_id(
            &key_manager_state,
            merchant_context.get_merchant_key_store(),
            &profile_id,
        )
        .await
        .to_not_found_response(ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let (authentication_connector, three_ds_connector_account) =
        auth_utils::get_authentication_connector_data(
            &state,
            merchant_context.get_merchant_key_store(),
            &business_profile,
            authentication.authentication_connector.clone(),
        )
        .await?;

    let notification_url = match authentication_connector {
        common_enums::AuthenticationConnectors::Juspaythreedsserver => {
            Some(url::Url::parse(&format!(
                "{base_url}/authentication/{merchant_id}/{authentication_id}/sync",
                base_url = state.base_url,
                merchant_id = merchant_id.get_string_repr(),
                authentication_id = authentication_id.get_string_repr()
            )))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse notification url")?
        }
        _ => authentication
            .return_url
            .as_ref()
            .map(|url| url::Url::parse(url))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse return url")?,
    };

    let authentication_connector_name = authentication_connector.to_string();

    let payment_method_data = domain::PaymentMethodData::from(req.payment_method_data.clone());

    let amount = authentication
        .amount
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("no amount found in authentication table")?;

    let acquirer_details = authentication
        .profile_acquirer_id
        .clone()
        .and_then(|acquirer_id| {
            business_profile
                .acquirer_config_map
                .and_then(|acquirer_config_map| acquirer_config_map.0.get(&acquirer_id).cloned())
        });

    let metadata: Option<ThreeDsMetaData> = three_ds_connector_account
        .get_metadata()
        .map(|metadata| {
            metadata.expose().parse_value("ThreeDsMetaData").inspect_err(|err| {
            router_env::logger::warn!(parsing_error=?err,"Error while parsing ThreeDsMetaData");
        })
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)?;

    let merchant_country_code = authentication.acquirer_country_code.clone();

    let merchant_details = Some(hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails {
        merchant_id: Some(authentication.merchant_id.get_string_repr().to_string()),
        merchant_name: acquirer_details.clone().map(|detail| detail.merchant_name.clone()).or(metadata.clone().and_then(|metadata| metadata.merchant_name)),
        merchant_category_code: business_profile.merchant_category_code.or(metadata.clone().and_then(|metadata| metadata.merchant_category_code)),
        endpoint_prefix: metadata.clone().map(|metadata| metadata.endpoint_prefix),
        three_ds_requestor_url: business_profile.authentication_connector_details.map(|details| details.three_ds_requestor_url),
        three_ds_requestor_id: metadata.clone().and_then(|metadata| metadata.three_ds_requestor_id),
        three_ds_requestor_name: metadata.clone().and_then(|metadata| metadata.three_ds_requestor_name),
        merchant_country_code: merchant_country_code.map(common_types::payments::MerchantCountryCode::new),
        notification_url,
    });

    let domain_address = req
        .billing
        .clone()
        .map(hyperswitch_domain_models::address::Address::from);

    let pre_auth_response =
        <ExternalAuthentication as UnifiedAuthenticationService>::pre_authentication(
            &state,
            merchant_id,
            None,
            Some(&payment_method_data),
            &three_ds_connector_account,
            &authentication_connector_name,
            &authentication_id,
            req.payment_method,
            amount,
            authentication.currency,
            None,
            merchant_details.as_ref(),
            domain_address.as_ref(),
            authentication.acquirer_bin.clone(),
            authentication.acquirer_merchant_id.clone(),
        )
        .await?;

    let billing_details_encoded = req
        .billing
        .clone()
        .map(|billing| {
            common_utils::ext_traits::Encode::encode_to_value(&billing)
                .map(masking::Secret::<serde_json::Value>::new)
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encode billing details to serde_json::Value")?;

    let shipping_details_encoded = req
        .shipping
        .clone()
        .map(|shipping| {
            common_utils::ext_traits::Encode::encode_to_value(&shipping)
                .map(masking::Secret::<serde_json::Value>::new)
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encode shipping details to serde_json::Value")?;

    let encrypted_data = domain::types::crypto_operation(
        &key_manager_state,
        common_utils::type_name!(hyperswitch_domain_models::authentication::Authentication),
        domain::types::CryptoOperation::BatchEncrypt(
            hyperswitch_domain_models::authentication::UpdateEncryptableAuthentication::to_encryptable(
                hyperswitch_domain_models::authentication::UpdateEncryptableAuthentication {
                    billing_address: billing_details_encoded,
                    shipping_address: shipping_details_encoded,
                },
            ),
        ),
        common_utils::types::keymanager::Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        ),
        merchant_context.get_merchant_key_store().key.peek(),
    )
    .await
    .and_then(|val| val.try_into_batchoperation())
    .change_context(ApiErrorResponse::InternalServerError)
    .attach_printable("Unable to encrypt authentication data".to_string())?;

    let encrypted_data = hyperswitch_domain_models::authentication::FromRequestEncryptableAuthentication::from_encryptable(encrypted_data)
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to get encrypted data for authentication after encryption")?;

    let email_encrypted = req


pub struct DisputeMetricRow {
    pub dispute_stage: Option<DBEnumWrapper<storage_enums::DisputeStage>>,
    pub dispute_status: Option<DBEnumWrapper<storage_enums::DisputeStatus>>,
    pub connector: Option<String>,
    pub currency: Option<DBEnumWrapper<storage_enums::Currency>>,
    pub total: Option<bigdecimal::BigDecimal>,
    pub count: Option<i64>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}


        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantId {
                merchant_id: auth.merchant_account.get_id().clone(),
            },
        ))
    }
}

#[derive(Debug)]
#[cfg(feature = "v2")]
pub struct MerchantIdAndProfileIdAuth {
    pub merchant_id: id_type::MerchantId,
    pub profile_id: id_type::ProfileId,
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for MerchantIdAndProfileIdAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        if state.conf().platform.enabled {
            throw_error_if_platform_merchant_authentication_required(request_headers)?;
        }

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &self.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &self.merchant_id,
                &self.profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(key_manager_state, &self.merchant_id, &key_store)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantId {
                merchant_id: auth.merchant_account.get_id().clone(),
            },
        ))
    }
}

#[derive(Debug)]
#[cfg(feature = "v2")]
pub struct PublishableKeyAndProfileIdAuth {
    pub publishable_key: String,
    pub profile_id: id_type::ProfileId,
}

#[async_trait]
#[cfg(feature = "v2")]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for PublishableKeyAndProfileIdAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        _request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let key_manager_state = &(&state.session_state()).into();
        let (merchant_account, key_store) = state
            .store()
            .find_merchant_account_by_publishable_key(
                key_manager_state,
                self.publishable_key.as_str(),
            )
            .await
            .map_err(|e| {
                if e.current_context().is_db_not_found() {
                    e.change_context(errors::ApiErrorResponse::Unauthorized)
                } else {
                    e.change_context(errors::ApiErrorResponse::InternalServerError)
                }
            })?;

        let profile = state
            .store()
            .find_business_profile_by_profile_id(key_manager_state, &key_store, &self.profile_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: self.profile_id.get_string_repr().to_owned(),
            })?;

        let merchant_id = merchant_account.get_id().clone();

        Ok((
            AuthenticationData {
                merchant_account,
                key_store,
                profile,
                platform_merchant_account: None,
            },
            AuthenticationType::PublishableKey { merchant_id },
        ))
    }
}

/// Take api-key from `Authorization` header
#[cfg(feature = "v2")]
#[derive(Debug)]
pub struct V2ApiKeyAuth {
    pub is_connected_allowed: bool,
    pub is_platform_allowed: bool,
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for V2ApiKeyAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let header_map_struct = HeaderMapStruct::new(request_headers);
        let auth_string = header_map_struct.get_auth_string_from_header()?;

        let api_key = auth_string
            .split(',')
            .find_map(|part| part.trim().strip_prefix("api-key="))
            .ok_or_else(|| {
                report!(errors::ApiErrorResponse::Unauthorized)
                    .attach_printable("Unable to parse api_key")
            })?;
        if api_key.is_empty() {
            return Err(errors::ApiErrorResponse::Unauthorized)
                .attach_printable("API key is empty");
        }

        let profile_id = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::ProfileId>(headers::X_PROFILE_ID)?;

        let api_key = api_keys::PlaintextApiKey::from(api_key);
        let hash_key = {
            let config = state.conf();
            config.api_keys.get_inner().get_hash_key()?
        };
        let hashed_api_key = api_key.keyed_hash(hash_key.peek());

        let stored_api_key = state
            .store()
            .find_api_key_by_hash_optional(hashed_api_key.into())
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed
            .attach_printable("Failed to retrieve API key")?
            .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None`
            .attach_printable("Merchant not authenticated")?;

        if stored_api_key
            .expires_at
            .map(|expires_at| expires_at < date_time::now())
            .unwrap_or(false)
        {
            return Err(report!(errors::ApiErrorResponse::Unauthorized))
                .attach_printable("API key has expired");
        }

        let key_manager_state = &(&state.session_state()).into();

        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &stored_api_key.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .change_context(errors::ApiErrorResponse::Unauthorized)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &stored_api_key.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account
        let (merchant, platform_merchant_account) = if state.conf().platform.enabled {
            get_platform_merchant_account(state, request_headers, merchant).await?
        } else {
            (merchant, None)
        };

        if platform_merchant_account.is_some() && !self.is_platform_allowed {
            return Err(report!(
                errors::ApiErrorResponse::PlatformAccountAuthNotSupported
            ))
            .attach_printable("Platform not authorized to access the resource");
        }

        let key_store = if platform_merchant_account.is_some() {
            state
                .store()
                .get_merchant_key_store_by_merchant_id(
                    key_manager_state,
                    merchant.get_id(),
                    &state.store().get_master_key().to_vec().into(),
                )
                .await
                .change_context(errors::ApiErrorResponse::Unauthorized)
                .attach_printable("Failed to fetch merchant key store for the merchant id")?
        } else {
            key_store
        };

        let profile = state
            .store()
            .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account,
            key_store,
            profile,
        };
        Ok((
            auth.clone(),
            AuthenticationType::ApiKey {
                merchant_id: auth.merchant_account.get_id().clone(),
                key_id: stored_api_key.key_id,
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[derive(Debug)]
pub struct V2ClientAuth(pub common_utils::types::authentication::ResourceId);

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for V2ClientAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let header_map_struct = HeaderMapStruct::new(request_headers);
        let auth_string = header_map_struct.get_auth_string_from_header()?;

        let publishable_key = auth_string
            .split(',')
            .find_map(|part| part.trim().strip_prefix("publishable-key="))
            .ok_or_else(|| {
                report!(errors::ApiErrorResponse::Unauthorized)
                    .attach_printable("Unable to parse publishable_key")
            })?;

        let client_secret = auth_string
            .split(',')
            .find_map(|part| part.trim().strip_prefix("client-secret="))
            .ok_or_else(|| {
                report!(errors::ApiErrorResponse::Unauthorized)
                    .attach_printable("Unable to parse client_secret")
            })?;

        let key_manager_state: &common_utils::types::keymanager::KeyManagerState =
            &(&state.session_state()).into();

        let db_client_secret: diesel_models::ClientSecretType = state
            .store()
            .get_client_secret(client_secret)
            .await
            .change_context(errors::ApiErrorResponse::Unauthorized)
            .attach_printable("Invalid ephemeral_key")?;

        let profile_id =
            get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
                .get_required_value(headers::X_PROFILE_ID)?;

        match (&self.0, &db_client_secret.resource_id) {
            (
                common_utils::types::authentication::ResourceId::Payment(self_id),
                common_utils::types::authentication::ResourceId::Payment(db_id),
            ) => {
                fp_utils::when(self_id != db_id, || {
                    Err::<(), errors::ApiErrorResponse>(errors::ApiErrorResponse::Unauthorized)
                });
            }

            (
                common_utils::types::authentication::ResourceId::Customer(self_id),
                common_utils::types::authentication::ResourceId::Customer(db_id),
            ) => {
                fp_utils::when(self_id != db_id, || {
                    Err::<(), errors::ApiErrorResponse>(errors::ApiErrorResponse::Unauthorized)
                });
            }

            (
                common_utils::types::authentication::ResourceId::PaymentMethodSession(self_id),
                common_utils::types::authentication::ResourceId::PaymentMethodSession(db_id),
            ) => {
                fp_utils::when(self_id != db_id, || {
                    Err::<(), errors::ApiErrorResponse>(errors::ApiErrorResponse::Unauthorized)
                });
            }

            _ => {
                return Err(errors::ApiErrorResponse::Unauthorized.into());
            }
        }

        let (merchant_account, key_store) = state
            .store()
            .find_merchant_account_by_publishable_key(key_manager_state, publishable_key)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant_id = merchant_account.get_id().clone();

        if db_client_secret.merchant_id != merchant_id {
            return Err(errors::ApiErrorResponse::Unauthorized.into());
        }
        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        Ok((
            AuthenticationData {
                merchant_account,
                key_store,
                profile,
                platform_merchant_account: None,
            },
            AuthenticationType::PublishableKey { merchant_id },
        ))
    }
}

#[cfg(feature = "v2")]
pub fn api_or_client_auth<'a, T, A>(
    api_auth: &'a dyn AuthenticateAndFetch<T, A>,
    client_auth: &'a dyn AuthenticateAndFetch<T, A>,
    headers: &HeaderMap,
) -> &'a dyn AuthenticateAndFetch<T, A>
where
{
    if let Ok(val) = HeaderMapStruct::new(headers).get_auth_string_from_header() {
        if val.trim().starts_with("api-key=") {
            api_auth
        } else {
            client_auth
        }
    } else {
        api_auth
    }
}
#[cfg(feature = "v2")]
pub fn api_or_client_or_jwt_auth<'a, T, A>(
    api_auth: &'a dyn AuthenticateAndFetch<T, A>,
    client_auth: &'a dyn AuthenticateAndFetch<T, A>,
    jwt_auth: &'a dyn AuthenticateAndFetch<T, A>,
    headers: &HeaderMap,
) -> &'a dyn AuthenticateAndFetch<T, A>
where
{
    if let Ok(val) = HeaderMapStruct::new(headers).get_auth_string_from_header() {
        if val.trim().starts_with("api-key=") {
            api_auth
        } else if is_jwt_auth(headers) {
            jwt_auth
        } else {
            client_auth
        }
    } else {
        api_auth
    }
}
#[derive(Debug)]
pub struct PublishableKeyAuth;

#[cfg(feature = "partial-auth")]
impl GetAuthType for PublishableKeyAuth {
    fn get_auth_type(&self) -> detached::PayloadType {
        detached::PayloadType::PublishableKey
    }
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for PublishableKeyAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        if state.conf().platform.enabled {
            throw_error_if_platform_merchant_authentication_required(request_headers)?;
        }

        let publishable_key =
            get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
        let key_manager_state = &(&state.session_state()).into();
        state
            .store()
            .find_merchant_account_by_publishable_key(key_manager_state, publishable_key)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)
            .map(|(merchant_account, key_store)| {
                let merchant_id = merchant_account.get_id().clone();
                (
                    AuthenticationData {
                        merchant_account,
                        platform_merchant_account: None,
                        key_store,
                        profile_id: None,
                    },
                    AuthenticationType::PublishableKey { merchant_id },
                )
            })
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for PublishableKeyAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let publishable_key =
            get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
        let key_manager_state = &(&state.session_state()).into();
        let profile_id =
            get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
                .get_required_value(headers::X_PROFILE_ID)?;

        let (merchant_account, key_store) = state
            .store()
            .find_merchant_account_by_publishable_key(key_manager_state, publishable_key)
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant_id = merchant_account.get_id().clone();
        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        Ok((
            AuthenticationData {
                merchant_account,
                key_store,
                profile,
                platform_merchant_account: None,
            },
            AuthenticationType::PublishableKey { merchant_id },
        ))
    }
}

#[derive(Debug)]
pub(crate) struct JWTAuth {
    pub permission: Permission,
}

#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<((), AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }

        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        Ok((
            (),
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "olap")]
#[async_trait]
impl<A> AuthenticateAndFetch<UserFromToken, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(UserFromToken, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        Ok((
            UserFromToken {
                user_id: payload.user_id.clone(),
                merchant_id: payload.merchant_id.clone(),
                org_id: payload.org_id,
                role_id: payload.role_id,
                profile_id: payload.profile_id,
                tenant_id: payload.tenant_id,
            },
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "olap")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithMultipleProfiles, A> for JWTAuth
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationDataWithMultipleProfiles, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .change_context(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .change_context(errors::ApiErrorResponse::InvalidJwtToken)?;

        Ok((
            AuthenticationDataWithMultipleProfiles {
                key_store,
                merchant_account: merchant,
                profile_id_list: None,
            },
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

pub struct JWTAuthOrganizationFromRoute {
    pub organization_id: id_type::OrganizationId,
    pub required_permission: Permission,
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<Option<AuthenticationDataWithOrg>, A> for JWTAuthOrganizationFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(Option<AuthenticationDataWithOrg>, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        // Check if token has access to Organization that has been requested in the route
        if payload.org_id != self.organization_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }
        Ok((
            Some(AuthenticationDataWithOrg {
                organization_id: payload.org_id.clone(),
            }),
            AuthenticationType::OrganizationJwt {
                org_id: payload.org_id,
                user_id: payload.user_id,
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for JWTAuthOrganizationFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<((), AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        // Check if token has access to Organization that has been requested in the route
        if payload.org_id != self.organization_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }
        Ok((
            (),
            AuthenticationType::OrganizationJwt {
                org_id: payload.org_id,
                user_id: payload.user_id,
            },
        ))
    }
}

pub struct JWTAuthMerchantFromRoute {
    pub merchant_id: id_type::MerchantId,
    pub required_permission: Permission,
}

pub struct JWTAuthMerchantFromHeader {
    pub required_permission: Permission,
}

#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for JWTAuthMerchantFromHeader
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<((), AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let merchant_id_from_header = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;

        // Check if token has access to MerchantId that has been requested through headers
        if payload.merchant_id != merchant_id_from_header {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }
        Ok((
            (),
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantFromHeader
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;
        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let merchant_id_from_header = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;

        // Check if token has access to MerchantId that has been requested through headers
        if payload.merchant_id != merchant_id_from_header {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let key_manager_state = &(&state.session_state()).into();

        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };

        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<Option<AuthenticationDataWithOrg>, A> for JWTAuthMerchantFromHeader
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(Option<AuthenticationDataWithOrg>, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;
        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let merchant_id_from_header = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;

        // Check if token has access to MerchantId that has been requested through headers
        if payload.merchant_id != merchant_id_from_header {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let auth = Some(AuthenticationDataWithOrg {
            organization_id: payload.org_id,
        });

        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantFromHeader
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        let profile_id =
            get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
                .get_required_value(headers::X_PROFILE_ID)?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let merchant_id_from_header = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;

        // Check if token has access to MerchantId that has been requested through headers
        if payload.merchant_id != merchant_id_from_header {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let key_manager_state = &(&state.session_state()).into();

        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let profile = state
            .store()
            .find_business_profile_by_merchant_id_profile_id(
                key_manager_state,
                &key_store,
                &payload.merchant_id,
                &profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            key_store,
            profile,
            platform_merchant_account: None,
        };

        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A> for JWTAuthMerchantFromHeader
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let merchant_id_from_header = HeaderMapStruct::new(request_headers)
            .get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;

        // Check if token has access to MerchantId that has been requested through headers
        if payload.merchant_id != merchant_id_from_header {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let key_manager_state = &(&state.session_state()).into();

        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationDataWithoutProfile {
            merchant_account: merchant,
            key_store,
        };

        Ok((
            auth,
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<((), AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        // Check if token has access to MerchantId that has been requested through query param
        if payload.merchant_id != self.merchant_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }
        Ok((
            (),
            AuthenticationType::MerchantJwt {
                merchant_id: payload.merchant_id,
                user_id: Some(payload.user_id),
            },
        ))
    }
}

#[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantFromRoute
where
    A: SessionStateInfo + Sync,
{
    async fn authenticate_and_fetch(
        &self,
        request_headers: &HeaderMap,
        state: &A,
    ) -> RouterResult<(AuthenticationData, AuthenticationType)> {
        let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
        if payload.check_in_blacklist(state).await? {
            return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
        }
        authorization::check_tenant(
            payload.tenant_id.clone(),
            &state.session_state().tenant.tenant_id,
        )?;

        if payload.merchant_id != self.merchant_id {
            return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
        }

        let role_info = authorization::get_role_info(state, &payload).await?;
        authorization::check_permission(self.required_permission, &role_info)?;

        let key_manager_state = &(&state.session_state()).into();
        let key_store = state
            .store()
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &state.store().get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant key store for the merchant id")?;

        let merchant = state
            .store()
            .find_merchant_account_by_merchant_id(
                key_manager_state,
                &payload.merchant_id,
                &key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
            .attach_printable("Failed to fetch merchant account for the merchant id")?;

        let auth = AuthenticationData {
            merchant_account: merchant,
            platform_merchant_account: None,
            key_store,
            profile_id: Some(payload.profile_id),
        };
        Ok((
            auth.clone(),
            AuthenticationType::MerchantJwt {


pub fn if_not_create_change_operation<'a, Op, F>(
    status: storage_enums::IntentStatus,
    confirm: Option<bool>,
    current: &'a Op,
) -> BoxedOperation<'a, F, api::PaymentsRequest, PaymentData<F>>
where
    F: Send + Clone + Sync,
    Op: Operation<F, api::PaymentsRequest, Data = PaymentData<F>> + Send + Sync,
    &'a Op: Operation<F, api::PaymentsRequest, Data = PaymentData<F>>,
    PaymentStatus: Operation<F, api::PaymentsRequest, Data = PaymentData<F>>,
    &'a PaymentStatus: Operation<F, api::PaymentsRequest, Data = PaymentData<F>>,
{
    if confirm.unwrap_or(false) {
        Box::new(PaymentConfirm)
    } else {
        match status {
            storage_enums::IntentStatus::RequiresConfirmation
            | storage_enums::IntentStatus::RequiresCustomerAction
            | storage_enums::IntentStatus::RequiresPaymentMethod => Box::new(current),
            _ => Box::new(&PaymentStatus),
        }
    }
}


pub struct PaymentCancelPostCapture;