// File: crates/router/src/core/payments/tokenization.rs
// Module: router
// Public functions: 17
// Public structs: 2

use std::collections::HashMap;

use ::payment_methods::controller::PaymentMethodsController;
#[cfg(feature = "v1")]
use api_models::payment_methods::PaymentMethodsData;
use api_models::{
    payment_methods::PaymentMethodDataWalletInfo, payments::ConnectorMandateReferenceId,
};
use common_enums::{ConnectorMandateStatus, PaymentMethod};
use common_types::callback_mapper::CallbackMapperData;
use common_utils::{
    crypto::Encryptable,
    ext_traits::{AsyncExt, Encode, ValueExt},
    id_type,
    metrics::utils::record_operation_time,
    pii,
};
use diesel_models::business_profile::ExternalVaultConnectorDetails;
use error_stack::{report, ResultExt};
#[cfg(feature = "v1")]
use hyperswitch_domain_models::{
    callback_mapper::CallbackMapper,
    mandates::{CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord},
    payment_method_data,
};
use masking::{ExposeInterface, Secret};
use router_env::{instrument, tracing};

use super::helpers;
#[cfg(feature = "v1")]
use crate::core::payment_methods::vault_payment_method_external_v1;
use crate::{
    consts,
    core::{
        errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt},
        mandate,
        payment_methods::{
            self,
            cards::{create_encrypted_data, PmCards},
            network_tokenization,
        },
        payments,
    },
    logger,
    routes::{metrics, SessionState},
    services,
    types::{
        self,
        api::{self, CardDetailFromLocker, CardDetailsPaymentMethod, PaymentMethodCreateExt},
        domain, payment_methods as pm_types,
        storage::enums as storage_enums,
    },
    utils::{generate_id, OptionExt},
};

#[cfg(feature = "v1")]
async fn save_in_locker(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_method_request: api::PaymentMethodCreate,
    card_detail: Option<api::CardDetail>,
    business_profile: &domain::Profile,
) -> RouterResult<(
    api_models::payment_methods::PaymentMethodResponse,
    Option<payment_methods::transformers::DataDuplicationCheck>,
)> {
    match &business_profile.external_vault_details {
        domain::ExternalVaultDetails::ExternalVaultEnabled(external_vault_details) => {
            logger::info!("External vault is enabled, using vault_payment_method_external_v1");

            Box::pin(save_in_locker_external(
                state,
                merchant_context,
                payment_method_request,
                card_detail,
                external_vault_details,
            ))
            .await
        }
        domain::ExternalVaultDetails::Skip => {
            // Use internal vault (locker)
            save_in_locker_internal(state, merchant_context, payment_method_request, card_detail)
                .await
        }
    }
}

pub struct SavePaymentMethodData<Req> {
    request: Req,
    response: Result<types::PaymentsResponseData, types::ErrorResponse>,
    payment_method_token: Option<types::PaymentMethodToken>,
    payment_method: PaymentMethod,
    attempt_status: common_enums::AttemptStatus,
}

impl<F, Req: Clone> From<&types::RouterData<F, Req, types::PaymentsResponseData>>
    for SavePaymentMethodData<Req>
{
    fn from(router_data: &types::RouterData<F, Req, types::PaymentsResponseData>) -> Self {
        Self {
            request: router_data.request.clone(),
            response: router_data.response.clone(),
            payment_method_token: router_data.payment_method_token.clone(),
            payment_method: router_data.payment_method,
            attempt_status: router_data.status,
        }
    }
}
pub struct SavePaymentMethodDataResponse {
    pub payment_method_id: Option<String>,
    pub payment_method_status: Option<common_enums::PaymentMethodStatus>,
    pub connector_mandate_reference_id: Option<ConnectorMandateReferenceId>,
}
#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn save_payment_method<FData>(
    state: &SessionState,
    connector_name: String,
    save_payment_method_data: SavePaymentMethodData<FData>,
    customer_id: Option<id_type::CustomerId>,
    merchant_context: &domain::MerchantContext,
    payment_method_type: Option<storage_enums::PaymentMethodType>,
    billing_name: Option<Secret<String>>,
    payment_method_billing_address: Option<&hyperswitch_domain_models::address::Address>,
    business_profile: &domain::Profile,
    mut original_connector_mandate_reference_id: Option<ConnectorMandateReferenceId>,
    merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
    vault_operation: Option<hyperswitch_domain_models::payments::VaultOperation>,
    payment_method_info: Option<domain::PaymentMethod>,
) -> RouterResult<SavePaymentMethodDataResponse>
where
    FData: mandate::MandateBehaviour + Clone,
{
    let mut pm_status = None;
    let cards = PmCards {
        state,
        merchant_context,
    };
    match save_payment_method_data.response {
        Ok(responses) => {
            let db = &*state.store;
            let token_store = state
                .conf
                .tokenization
                .0
                .get(&connector_name.to_string())
                .map(|token_filter| token_filter.long_lived_token)
                .unwrap_or(false);

            let network_transaction_id = match &responses {
                types::PaymentsResponseData::TransactionResponse { network_txn_id, .. } => {
                    network_txn_id.clone()
                }
                _ => None,
            };

            let network_transaction_id =
                if save_payment_method_data.request.get_setup_future_usage()
                    == Some(storage_enums::FutureUsage::OffSession)
                {
                    if network_transaction_id.is_some() {
                        network_transaction_id
                    } else {
                        logger::info!("Skip storing network transaction id");
                        None
                    }
                } else {
                    None
                };

            let connector_token = if token_store {
                let tokens = save_payment_method_data
                    .payment_method_token
                    .to_owned()
                    .get_required_value("payment_token")?;
                let token = match tokens {
                    types::PaymentMethodToken::Token(connector_token) => connector_token.expose(),
                    types::PaymentMethodToken::ApplePayDecrypt(_) => {
                        Err(errors::ApiErrorResponse::NotSupported {
                            message: "Apple Pay Decrypt token is not supported".to_string(),
                        })?
                    }
                    types::PaymentMethodToken::PazeDecrypt(_) => {
                        Err(errors::ApiErrorResponse::NotSupported {
                            message: "Paze Decrypt token is not supported".to_string(),
                        })?
                    }
                    types::PaymentMethodToken::GooglePayDecrypt(_) => {
                        Err(errors::ApiErrorResponse::NotSupported {
                            message: "Google Pay Decrypt token is not supported".to_string(),
                        })?
                    }
                };
                Some((connector_name, token))
            } else {
                None
            };

            let mandate_data_customer_acceptance = save_payment_method_data
                .request
                .get_setup_mandate_details()
                .and_then(|mandate_data| mandate_data.customer_acceptance.clone());

            let customer_acceptance = save_payment_method_data
                .request
                .get_customer_acceptance()
                .or(mandate_data_customer_acceptance.clone())
                .map(|ca| ca.encode_to_value())
                .transpose()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to serialize customer acceptance to value")?;

            let (connector_mandate_id, mandate_metadata, connector_mandate_request_reference_id) =
                match responses {
                    types::PaymentsResponseData::TransactionResponse {
                        mandate_reference, ..
                    } => {
                        if let Some(ref mandate_ref) = *mandate_reference {
                            (
                                mandate_ref.connector_mandate_id.clone(),
                                mandate_ref.mandate_metadata.clone(),
                                mandate_ref.connector_mandate_request_reference_id.clone(),
                            )
                        } else {
                            (None, None, None)
                        }
                    }
                    _ => (None, None, None),
                };

            let pm_id = if customer_acceptance.is_some() {
                let payment_method_data =
                    save_payment_method_data.request.get_payment_method_data();
                let payment_method_create_request =
                    payment_methods::get_payment_method_create_request(
                        Some(&payment_method_data),
                        Some(save_payment_method_data.payment_method),
                        payment_method_type,
                        &customer_id.clone(),
                        billing_name,
                        payment_method_billing_address,
                    )
                    .await?;
                let payment_methods_data =
                    &save_payment_method_data.request.get_payment_method_data();

                let co_badged_card_data = payment_methods_data.get_co_badged_card_data();

                let customer_id = customer_id.to_owned().get_required_value("customer_id")?;
                let merchant_id = merchant_context.get_merchant_account().get_id();
                let is_network_tokenization_enabled =
                    business_profile.is_network_tokenization_enabled;
                let (
                    (mut resp, duplication_check, network_token_requestor_ref_id),
                    network_token_resp,
                ) = if !state.conf.locker.locker_enabled {
                    let (res, dc) = skip_saving_card_in_locker(
                        merchant_context,
                        payment_method_create_request.to_owned(),
                    )
                    .await?;
                    ((res, dc, None), None)
                } else {
                    let payment_method_status = common_enums::PaymentMethodStatus::from(
                        save_payment_method_data.attempt_status,
                    );
                    pm_status = Some(payment_method_status);
                    save_card_and_network_token_in_locker(
                        state,
                        customer_id.clone(),
                        payment_method_status,
                        payment_method_data.clone(),
                        vault_operation,
                        payment_method_info,
                        merchant_context,
                        payment_method_create_request.clone(),
                        is_network_tokenization_enabled,
                        business_profile,
                    )
                    .await?
                };
                let network_token_locker_id = match network_token_resp {
                    Some(ref token_resp) => {
                        if network_token_requestor_ref_id.is_some() {
                            Some(token_resp.payment_method_id.clone())
                        } else {
                            None
                        }
                    }
                    None => None,
                };

                let optional_pm_details = match (resp.card.as_ref(), payment_method_data) {
                    (Some(card), _) => Some(PaymentMethodsData::Card(
                        CardDetailsPaymentMethod::from((card.clone(), co_badged_card_data)),
                    )),
                    (
                        _,
                        domain::PaymentMethodData::Wallet(domain::WalletData::ApplePay(applepay)),
                    ) => Some(PaymentMethodsData::WalletDetails(
                        PaymentMethodDataWalletInfo::from(applepay),
                    )),
                    (
                        _,
                        domain::PaymentMethodData::Wallet(domain::WalletData::GooglePay(googlepay)),
                    ) => Some(PaymentMethodsData::WalletDetails(
                        PaymentMethodDataWalletInfo::from(googlepay),
                    )),
                    _ => None,
                };

                let key_manager_state = state.into();
                let pm_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> =
                    optional_pm_details
                        .async_map(|pm| {
                            create_encrypted_data(
                                &key_manager_state,
                                merchant_context.get_merchant_key_store(),
                                pm,
                            )
                        })
                        .await
                        .transpose()
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Unable to encrypt payment method data")?;

                let pm_network_token_data_encrypted: Option<
                    Encryptable<Secret<serde_json::Value>>,
                > = match network_token_resp {
                    Some(token_resp) => {
                        let pm_token_details = token_resp.card.as_ref().map(|card| {
                            PaymentMethodsData::Card(CardDetailsPaymentMethod::from((
                                card.clone(),
                                None,
                            )))
                        });

                        pm_token_details
                            .async_map(|pm_card| {
                                create_encrypted_data(
                                    &key_manager_state,
                                    merchant_context.get_merchant_key_store(),
                                    pm_card,
                                )
                            })
                            .await
                            .transpose()
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Unable to encrypt payment method data")?
                    }
                    None => None,
                };

                let encrypted_payment_method_billing_address: Option<
                    Encryptable<Secret<serde_json::Value>>,
                > = payment_method_billing_address
                    .async_map(|address| {
                        create_encrypted_data(
                            &key_manager_state,
                            merchant_context.get_merchant_key_store(),
                            address.clone(),
                        )
                    })
                    .await
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Unable to encrypt payment method billing address")?;

                let mut payment_method_id = resp.payment_method_id.clone();
                let mut locker_id = None;
                let (external_vault_details, vault_type) = match &business_profile.external_vault_details{
                    hyperswitch_domain_models::business_profile::ExternalVaultDetails::ExternalVaultEnabled(external_vault_connector_details) => {
                        (Some(external_vault_connector_details), Some(common_enums::VaultType::External))
                    },
                    hyperswitch_domain_models::business_profile::ExternalVaultDetails::Skip => (None, Some(common_enums::VaultType::Internal)),
                };
                let external_vault_mca_id = external_vault_details
                    .map(|connector_details| connector_details.vault_connector_id.clone());

                let vault_source_details = domain::PaymentMethodVaultSourceDetails::try_from((
                    vault_type,
                    external_vault_mca_id,
                ))
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to create vault source details")?;

                match duplication_check {
                    Some(duplication_check) => match duplication_check {
                        payment_methods::transformers::DataDuplicationCheck::Duplicated => {
                            let payment_method = {
                                let existing_pm_by_pmid = db
                                    .find_payment_method(
                                        &(state.into()),
                                        merchant_context.get_merchant_key_store(),
                                        &payment_method_id,
                                        merchant_context.get_merchant_account().storage_scheme,
                                    )
                                    .await;

                                if let Err(err) = existing_pm_by_pmid {
                                    if err.current_context().is_db_not_found() {
                                        locker_id = Some(payment_method_id.clone());
                                        let existing_pm_by_locker_id = db
                                            .find_payment_method_by_locker_id(
                                                &(state.into()),
                                                merchant_context.get_merchant_key_store(),
                                                &payment_method_id,
                                                merchant_context
                                                    .get_merchant_account()
                                                    .storage_scheme,
                                            )
                                            .await;

                                        match &existing_pm_by_locker_id {
                                            Ok(pm) => {
                                                payment_method_id.clone_from(&pm.payment_method_id);
                                            }
                                            Err(_) => {
                                                payment_method_id =
                                                    generate_id(consts::ID_LENGTH, "pm")
                                            }
                                        };
                                        existing_pm_by_locker_id
                                    } else {
                                        Err(err)
                                    }
                                } else {
                                    existing_pm_by_pmid
                                }
                            };

                            resp.payment_method_id = payment_method_id;

                            match payment_method {
                                Ok(pm) => {
                                    let pm_metadata = create_payment_method_metadata(
                                        pm.metadata.as_ref(),
                                        connector_token,
                                    )?;
                                    payment_methods::cards::update_payment_method_metadata_and_last_used(
                                        state,
                                        merchant_context.get_merchant_key_store(),
                                        db,
                                        pm.clone(),
                                        pm_metadata,
                                        merchant_context.get_merchant_account().storage_scheme,
                                    )
                                    .await
                                    .change_context(errors::ApiErrorResponse::InternalServerError)
                                    .attach_printable("Failed to add payment method in db")?;
                                }
                                Err(err) => {
                                    if err.current_context().is_db_not_found() {
                                        let pm_metadata =
                                            create_payment_method_metadata(None, connector_token)?;
                                        cards
                                            .create_payment_method(
                                                &payment_method_create_request,
                                                &customer_id,
                                                &resp.payment_method_id,
                                                locker_id,
                                                merchant_id,
                                                pm_metadata,
                                                customer_acceptance,
                                                pm_data_encrypted,
                                                None,
                                                pm_status,
                                                network_transaction_id,
                                                encrypted_payment_method_billing_address,
                                                resp.card.and_then(|card| {
                                                    card.card_network.map(|card_network| {
                                                        card_network.to_string()
                                                    })
                                                }),
                                                network_token_requestor_ref_id,
                                                network_token_locker_id,
                                                pm_network_token_data_encrypted,
                                                Some(vault_source_details),
                                            )
                                            .await
                                    } else {
                                        Err(err)
                                            .change_context(
                                                errors::ApiErrorResponse::InternalServerError,
                                            )
                                            .attach_printable("Error while finding payment method")
                                    }?;
                                }
                            };
                        }
                        payment_methods::transformers::DataDuplicationCheck::MetaDataChanged => {
                            if let Some(card) = payment_method_create_request.card.clone() {
                                let payment_method = {
                                    let existing_pm_by_pmid = db
                                        .find_payment_method(
                                            &(state.into()),
                                            merchant_context.get_merchant_key_store(),
                                            &payment_method_id,
                                            merchant_context.get_merchant_account().storage_scheme,
                                        )
                                        .await;

                                    if let Err(err) = existing_pm_by_pmid {
                                        if err.current_context().is_db_not_found() {
                                            locker_id = Some(payment_method_id.clone());
                                            let existing_pm_by_locker_id = db
                                                .find_payment_method_by_locker_id(
                                                    &(state.into()),
                                                    merchant_context.get_merchant_key_store(),
                                                    &payment_method_id,
                                                    merchant_context
                                                        .get_merchant_account()
                                                        .storage_scheme,
                                                )
                                                .await;

                                            match &existing_pm_by_locker_id {
                                                Ok(pm) => {
                                                    payment_method_id
                                                        .clone_from(&pm.payment_method_id);
                                                }
                                                Err(_) => {
                                                    payment_method_id =
                                                        generate_id(consts::ID_LENGTH, "pm")
                                                }
                                            };
                                            existing_pm_by_locker_id
                                        } else {
                                            Err(err)
                                        }
                                    } else {
                                        existing_pm_by_pmid
                                    }
                                };

                                resp.payment_method_id = payment_method_id;

                                let existing_pm = match payment_method {
                                    Ok(pm) => {
                                        let mandate_details =    pm
                                        .connector_mandate_details
                                        .clone()
                                        .map(|val| {
                                            val.parse_value::<PaymentsMandateReference>(
                                                "PaymentsMandateReference",
                                            )
                                        })
                                        .transpose()
                                        .change_context(errors::ApiErrorResponse::InternalServerError)
                                        .attach_printable("Failed to deserialize to Payment Mandate Reference ")?;
                                        if let Some((mandate_details, merchant_connector_id)) =
                                            mandate_details.zip(merchant_connector_id)
                                        {
                                            let connector_mandate_details =
                                                update_connector_mandate_details_status(
                                                    merchant_connector_id,
                                                    mandate_details,
                                                    ConnectorMandateStatus::Inactive,
                                                )?;
                                            payment_methods::cards::update_payment_method_connector_mandate_details(
                                            state,
                                            merchant_context.get_merchant_key_store(),
                                            db,
                                            pm.clone(),
                                            connector_mandate_details,
                                            merchant_context.get_merchant_account().storage_scheme,
                                        )
                                        .await
                                        .change_context(errors::ApiErrorResponse::InternalServerError)
                                        .attach_printable("Failed to add payment method in db")?;
                                        }
                                        Ok(pm)
                                    }
                                    Err(err) => {
                                        if err.current_context().is_db_not_found() {
                                            cards
                                                .create_payment_method(
                                                    &payment_method_create_request,
                                                    &customer_id,
                                                    &resp.payment_method_id,
                                                    locker_id,
                                                    merchant_id,
                                                    resp.metadata.clone().map(|val| val.expose()),
                                                    customer_acceptance,
                                                    pm_data_encrypted,
                                                    None,
                                                    pm_status,
                                                    network_transaction_id,
                                                    encrypted_payment_method_billing_address,
                                                    resp.card.and_then(|card| {
                                                        card.card_network.map(|card_network| {
                                                            card_network.to_string()
                                                        })
                                                    }),
                                                    network_token_requestor_ref_id,
                                                    network_token_locker_id,
                                                    pm_network_token_data_encrypted,
                                                    Some(vault_source_details),
                                                )
                                                .await
                                        } else {
                                            Err(err)
                                                .change_context(
                                                    errors::ApiErrorResponse::InternalServerError,
                                                )
                                                .attach_printable(
                                                    "Error while finding payment method",
                                                )
                                        }
                                    }
                                }?;

                                cards
                                    .delete_card_from_locker(
                                        &customer_id,
                                        merchant_id,
                                        existing_pm
                                            .locker_id
                                            .as_ref()
                                            .unwrap_or(&existing_pm.payment_method_id),
                                    )
                                    .await?;

                                let add_card_resp = cards
                                    .add_card_hs(
                                        payment_method_create_request,
                                        &card,
                                        &customer_id,
                                        api::enums::LockerChoice::HyperswitchCardVault,
                                        Some(
                                            existing_pm
                                                .locker_id
                                                .as_ref()
                                                .unwrap_or(&existing_pm.payment_method_id),
                                        ),
                                    )
                                    .await;

                                if let Err(err) = add_card_resp {
                                    logger::error!(vault_err=?err);
                                    db.delete_payment_method_by_merchant_id_payment_method_id(
                                        &(state.into()),
                                        merchant_context.get_merchant_key_store(),
                                        merchant_id,
                                        &resp.payment_method_id,
                                    )
                                    .await
                                    .to_not_found_response(
                                        errors::ApiErrorResponse::PaymentMethodNotFound,
                                    )?;

                                    Err(report!(errors::ApiErrorResponse::InternalServerError)
                                        .attach_printable(
                                            "Failed while updating card metadata changes",
                                        ))?
                                };

                                let existing_pm_data = cards
                                    .get_card_details_without_locker_fallback(&existing_pm)
                                    .await?;

                                // scheme should be updated in case of co-badged cards
                                let card_scheme = card
                                    .card_network
                                    .clone()
                                    .map(|card_network| card_network.to_string())
                                    .or(existing_pm_data.scheme.clone());

                                let updated_card = Some(CardDetailFromLocker {
                                    scheme: card_scheme.clone(),
                                    last4_digits: Some(card.card_number.get_last4()),
                                    issuer_country: card
                                        .card_issuing_country
                                        .or(existing_pm_data.issuer_country),
                                    card_isin: Some(card.card_number.get_card_isin()),
                                    card_number: Some(card.card_number),
                                    expiry_month: Some(card.card_exp_month),
                                    expiry_year: Some(card.card_exp_year),
                                    card_token: None,
                                    card_fingerprint: None,
                                    card_holder_name: card
                                        .card_holder_name
                                        .or(existing_pm_data.card_holder_name),
                                    nick_name: card.nick_name.or(existing_pm_data.nick_name),
                                    card_network: card
                                        .card_network
                                        .or(existing_pm_data.card_network),
                                    card_issuer: card.card_issuer.or(existing_pm_data.card_issuer),
                                    card_type: card.card_type.or(existing_pm_data.card_type),
                                    saved_to_locker: true,
                                });

                                let updated_pmd = updated_card.as_ref().map(|card| {
                                    PaymentMethodsData::Card(CardDetailsPaymentMethod::from((
                                        card.clone(),
                                        co_badged_card_data,
                                    )))
                                });
                                let pm_data_encrypted: Option<
                                    Encryptable<Secret<serde_json::Value>>,
                                > = updated_pmd
                                    .async_map(|pmd| {
                                        create_encrypted_data(
                                            &key_manager_state,
                                            merchant_context.get_merchant_key_store(),
                                            pmd,
                                        )
                                    })
                                    .await
                                    .transpose()
                                    .change_context(errors::ApiErrorResponse::InternalServerError)
                                    .attach_printable("Unable to encrypt payment method data")?;

                                payment_methods::cards::update_payment_method_and_last_used(
                                    state,
                                    merchant_context.get_merchant_key_store(),
                                    db,
                                    existing_pm,
                                    pm_data_encrypted.map(Into::into),
                                    merchant_context.get_merchant_account().storage_scheme,
                                    card_scheme,
                                )
                                .await
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable("Failed to add payment method in db")?;
                            }
                        }
                    },
                    None => {
                        let customer_saved_pm_option = if payment_method_type
                            .map(|payment_method_type_value| {
                                payment_method_type_value
                                    .should_check_for_customer_saved_payment_method_type()
                            })
                            .unwrap_or(false)
                        {
                            match state
                                .store
                                .find_payment_method_by_customer_id_merchant_id_list(
                                    &(state.into()),
                                    merchant_context.get_merchant_key_store(),
                                    &customer_id,
                                    merchant_id,
                                    None,
                                )
                                .await
                            {
                                Ok(customer_payment_methods) => Ok(customer_payment_methods
                                    .iter()
                                    .find(|payment_method| {
                                        payment_method.get_payment_method_subtype()
                                            == payment_method_type
                                    })
                                    .cloned()),
                                Err(error) => {
                                    if error.current_context().is_db_not_found() {
                                        Ok(None)
                                    } else {
                                        Err(error)
                                            .change_context(
                                                errors::ApiErrorResponse::InternalServerError,
                                            )
                                            .attach_printable(
                                                "failed to find payment methods for a customer",
                                            )
                                    }
                                }
                            }
                        } else {
                            Ok(None)
                        }?;

                        if let Some(customer_saved_pm) = customer_saved_pm_option {
                            payment_methods::cards::update_last_used_at(
                                &customer_saved_pm,
                                state,
                                merchant_context.get_merchant_account().storage_scheme,
                                merchant_context.get_merchant_key_store(),
                            )
                            .await
                            .map_err(|e| {
                                logger::error!("Failed to update last used at: {:?}", e);
                            })
                            .ok();
                            resp.payment_method_id = customer_saved_pm.payment_method_id;
                        } else {
                            let pm_metadata =
                                create_payment_method_metadata(None, connector_token)?;

                            locker_id = resp.payment_method.and_then(|pm| {
                                if pm == PaymentMethod::Card {
                                    Some(resp.payment_method_id)
                                } else {
                                    None
                                }
                            });

                            resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
                            cards
                                .create_payment_method(
                                    &payment_method_create_request,
                                    &customer_id,
                                    &resp.payment_method_id,
                                    locker_id,
                                    merchant_id,
                                    pm_metadata,
                                    customer_acceptance,
                                    pm_data_encrypted,
                                    None,
                                    pm_status,
                                    network_transaction_id,
                                    encrypted_payment_method_billing_address,
                                    resp.card.and_then(|card| {
                                        card.card_network
                                            .map(|card_network| card_network.to_string())
                                    }),
                                    network_token_requestor_ref_id.clone(),
                                    network_token_locker_id,
                                    pm_network_token_data_encrypted,
                                    Some(vault_source_details),
                                )
                                .await?;

                            match network_token_requestor_ref_id {
                                Some(network_token_requestor_ref_id) => {
                                    //Insert the network token reference ID along with merchant id, customer id in CallbackMapper table for its respective webooks
                                    let callback_mapper_data =
                                        CallbackMapperData::NetworkTokenWebhook {
                                            merchant_id: merchant_context
                                                .get_merchant_account()
                                                .get_id()
                                                .clone(),
                                            customer_id,
                                            payment_method_id: resp.payment_method_id.clone(),
                                        };
                                    let callback_mapper = CallbackMapper::new(
                                        network_token_requestor_ref_id,
                                        common_enums::CallbackMapperIdType::NetworkTokenRequestorReferenceID,
                                        callback_mapper_data,
                                        common_utils::date_time::now(),
                                        common_utils::date_time::now(),
                                    );

                                    db.insert_call_back_mapper(callback_mapper)
                                        .await
                                        .change_context(
                                            errors::ApiErrorResponse::InternalServerError,
                                        )
                                        .attach_printable(
                                            "Failed to insert in Callback Mapper table",
                                        )?;
                                }
                                None => {
                                    logger::info!("Network token requestor reference ID is not available, skipping callback mapper insertion");
                                }
                            };
                        };
                    }
                }

                Some(resp.payment_method_id)
            } else {
                None
            };
            // check if there needs to be a config if yes then remove it to a different place
            let connector_mandate_reference_id = if connector_mandate_id.is_some() {
                if let Some(ref mut record) = original_connector_mandate_reference_id {
                    record.update(
                        connector_mandate_id,
                        None,
                        None,
                        mandate_metadata,
                        connector_mandate_request_reference_id,
                    );
                    Some(record.clone())
                } else {
                    Some(ConnectorMandateReferenceId::new(
                        connector_mandate_id,
                        None,
                        None,
                        mandate_metadata,
                        connector_mandate_request_reference_id,
                    ))
                }
            } else {
                None
            };

            Ok(SavePaymentMethodDataResponse {
                payment_method_id: pm_id,
                payment_method_status: pm_status,
                connector_mandate_reference_id,
            })
        }
        Err(_) => Ok(SavePaymentMethodDataResponse {
            payment_method_id: None,
            payment_method_status: None,
            connector_mandate_reference_id: None,
        }),
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn save_payment_method<FData>(
    _state: &SessionState,
    _connector_name: String,
    _save_payment_method_data: SavePaymentMethodData<FData>,
    _customer_id: Option<id_type::CustomerId>,
    _merchant_context: &domain::MerchantContext,
    _payment_method_type: Option<storage_enums::PaymentMethodType>,
    _billing_name: Option<Secret<String>>,
    _payment_method_billing_address: Option<&api::Address>,
    _business_profile: &domain::Profile,
    _connector_mandate_request_reference_id: Option<String>,
) -> RouterResult<SavePaymentMethodDataResponse>
where
    FData: mandate::MandateBehaviour + Clone,
{
    todo!()
}

#[cfg(feature = "v1")]
pub async fn pre_payment_tokenization(
    state: &SessionState,
    customer_id: id_type::CustomerId,
    card: &payment_method_data::Card,
) -> RouterResult<(Option<pm_types::TokenResponse>, Option<String>)> {
    let network_tokenization_supported_card_networks = &state
        .conf
        .network_tokenization_supported_card_networks
        .card_networks;

    if card
        .card_network
        .as_ref()
        .filter(|cn| network_tokenization_supported_card_networks.contains(cn))
        .is_some()
    {
        let optional_card_cvc = Some(card.card_cvc.clone());
        let card_detail = payment_method_data::CardDetail::from(card);
        match network_tokenization::make_card_network_tokenization_request(
            state,
            &card_detail,
            optional_card_cvc,
            &customer_id,
        )
        .await
        {
            Ok((_token_response, network_token_requestor_ref_id)) => {
                let network_tokenization_service = &state.conf.network_tokenization_service;
                match (
                    network_token_requestor_ref_id.clone(),
                    network_tokenization_service,
                ) {
                    (Some(token_ref), Some(network_tokenization_service)) => {
                        let network_token = record_operation_time(
                            async {
                                network_tokenization::get_network_token(
                                    state,
                                    customer_id,
                                    token_ref,
                                    network_tokenization_service.get_inner(),
                                )
                                .await
                            },
                            &metrics::FETCH_NETWORK_TOKEN_TIME,
                            &[],
                        )
                        .await;
                        match network_token {
                            Ok(token_response) => {
                                Ok((Some(token_response), network_token_requestor_ref_id.clone()))
                            }
                            _ => {
                                logger::error!(
                                    "Error while fetching token from tokenization service"
                                );
                                Ok((None, network_token_requestor_ref_id.clone()))
                            }
                        }
                    }
                    (Some(token_ref), _) => Ok((None, Some(token_ref))),
                    _ => Ok((None, None)),
                }
            }
            Err(err) => {
                logger::error!("Failed to tokenize card: {:?}", err);
                Ok((None, None)) //None will be returned in case of error when calling network tokenization service
            }
        }
    } else {
        Ok((None, None)) //None will be returned in case of unsupported card network.
    }
}

#[cfg(feature = "v1")]
async fn skip_saving_card_in_locker(
    merchant_context: &domain::MerchantContext,
    payment_method_request: api::PaymentMethodCreate,
) -> RouterResult<(
    api_models::payment_methods::PaymentMethodResponse,
    Option<payment_methods::transformers::DataDuplicationCheck>,
)> {
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = payment_method_request
        .clone()
        .customer_id
        .clone()
        .get_required_value("customer_id")?;
    let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm");

    let last4_digits = payment_method_request
        .card
        .clone()
        .map(|c| c.card_number.get_last4());

    let card_isin = payment_method_request
        .card
        .clone()
        .map(|c| c.card_number.get_card_isin());

    match payment_method_request.card.clone() {
        Some(card) => {
            let card_detail = CardDetailFromLocker {
                scheme: None,
                issuer_country: card.card_issuing_country.clone(),
                last4_digits: last4_digits.clone(),
                card_number: None,
                expiry_month: Some(card.card_exp_month.clone()),
                expiry_year: Some(card.card_exp_year),
                card_token: None,
                card_holder_name: card.card_holder_name.clone(),
                card_fingerprint: None,
                nick_name: None,
                card_isin: card_isin.clone(),
                card_issuer: card.card_issuer.clone(),
                card_network: card.card_network.clone(),
                card_type: card.card_type.clone(),
                saved_to_locker: false,
            };
            let pm_resp = api::PaymentMethodResponse {
                merchant_id: merchant_id.to_owned(),
                customer_id: Some(customer_id),
                payment_method_id,
                payment_method: payment_method_request.payment_method,
                payment_method_type: payment_method_request.payment_method_type,
                card: Some(card_detail),
                recurring_enabled: Some(false),
                installment_payment_enabled: Some(false),
                payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
                metadata: None,
                created: Some(common_utils::date_time::now()),
                #[cfg(feature = "payouts")]
                bank_transfer: None,
                last_used_at: Some(common_utils::date_time::now()),
                client_secret: None,
            };

            Ok((pm_resp, None))
        }
        None => {
            let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm");
            let payment_method_response = api::PaymentMethodResponse {
                merchant_id: merchant_id.to_owned(),
                customer_id: Some(customer_id),
                payment_method_id: pm_id,
                payment_method: payment_method_request.payment_method,
                payment_method_type: payment_method_request.payment_method_type,
                card: None,
                metadata: None,
                created: Some(common_utils::date_time::now()),
                recurring_enabled: Some(false),
                installment_payment_enabled: Some(false),
                payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
                #[cfg(feature = "payouts")]
                bank_transfer: None,
                last_used_at: Some(common_utils::date_time::now()),
                client_secret: None,
            };
            Ok((payment_method_response, None))
        }
    }
}

#[cfg(feature = "v2")]
async fn skip_saving_card_in_locker(
    merchant_context: &domain::MerchantContext,
    payment_method_request: api::PaymentMethodCreate,
) -> RouterResult<(
    api_models::payment_methods::PaymentMethodResponse,
    Option<payment_methods::transformers::DataDuplicationCheck>,
)> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn save_in_locker_internal(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_method_request: api::PaymentMethodCreate,
    card_detail: Option<api::CardDetail>,
) -> RouterResult<(
    api_models::payment_methods::PaymentMethodResponse,
    Option<payment_methods::transformers::DataDuplicationCheck>,
)> {
    payment_method_request.validate()?;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = payment_method_request
        .customer_id
        .clone()
        .get_required_value("customer_id")?;
    match (payment_method_request.card.clone(), card_detail) {
        (_, Some(card)) | (Some(card), _) => Box::pin(
            PmCards {
                state,
                merchant_context,
            }
            .add_card_to_locker(payment_method_request, &card, &customer_id, None),
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Add Card Failed"),
        _ => {
            let pm_id = common_utils::generate_id(consts::ID_LENGTH, "pm");
            let payment_method_response = api::PaymentMethodResponse {
                merchant_id: merchant_id.clone(),
                customer_id: Some(customer_id),
                payment_method_id: pm_id,
                payment_method: payment_method_request.payment_method,
                payment_method_type: payment_method_request.payment_method_type,
                #[cfg(feature = "payouts")]
                bank_transfer: None,
                card: None,
                metadata: None,
                created: Some(common_utils::date_time::now()),
                recurring_enabled: Some(false),
                installment_payment_enabled: Some(false),
                payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219]
                last_used_at: Some(common_utils::date_time::now()),
                client_secret: None,
            };
            Ok((payment_method_response, None))
        }
    }
}

#[cfg(feature = "v1")]
pub async fn save_in_locker_external(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_method_request: api::PaymentMethodCreate,
    card_detail: Option<api::CardDetail>,
    external_vault_connector_details: &ExternalVaultConnectorDetails,
) -> RouterResult<(
    api_models::payment_methods::PaymentMethodResponse,
    Option<payment_methods::transformers::DataDuplicationCheck>,
)> {
    let customer_id = payment_method_request
        .customer_id
        .clone()
        .get_required_value("customer_id")?;
    // For external vault, we need to convert the card data to PaymentMethodVaultingData
    if let Some(card) = card_detail {
        let payment_method_vaulting_data =


// Struct: AddCardRequest
// File: crates/router/src/core/payment_methods/transformers.rs
// Module: router
// Implementations: 0

pub struct AddCardRequest


// Implementation: impl api::MandateSetup for for Worldpay
// File: crates/hyperswitch_connectors/src/connectors/worldpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::MandateSetup for for Worldpay


// Struct: OnboardSubAccountRequest
// File: crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OnboardSubAccountRequest


                    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
        .email
        .clone()
        .async_lift(|inner| async {
            domain::types::crypto_operation(
                &key_manager_state,
                common_utils::type_name!(Authentication),
                domain::types::CryptoOperation::EncryptOptional(inner.map(|inner| inner.expose())),
                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_optionaloperation())
        })
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt email")?;

    let browser_info = req
        .browser_information
        .as_ref()
        .map(common_utils::ext_traits::Encode::encode_to_value)
        .transpose()
        .change_context(ApiErrorResponse::InvalidDataValue {
            field_name: "browser_information",
        })?;

    let updated_authentication = utils::external_authentication_update_trackers(
        &state,
        pre_auth_response,
        authentication.clone(),
        None,
        merchant_context.get_merchant_key_store(),
        encrypted_data
            .billing_address
            .map(common_utils::encryption::Encryption::from),
        encrypted_data
            .shipping_address
            .map(common_utils::encryption::Encryption::from),
        email_encrypted
            .clone()
            .map(common_utils::encryption::Encryption::from),
        browser_info,
    )
    .await?;

    let response = AuthenticationEligibilityResponse::foreign_try_from((
        updated_authentication,
        req.get_next_action_api(
            state.base_url,
            authentication_id.get_string_repr().to_string(),
        )
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to get next action api")?,
        profile_id,
        req.get_billing_address(),
        req.get_shipping_address(),
        req.get_browser_information(),
        email_encrypted,
    ))?;

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

#[cfg(feature = "v1")]
pub async fn authentication_authenticate_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationAuthenticateRequest,
    auth_flow: AuthFlow,
) -> RouterResponse<AuthenticationAuthenticateResponse> {
    let authentication_id = req.authentication_id.clone();
    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
        .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 = authentication.profile_id.clone();

    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 email_encrypted = authentication
        .email
        .clone()
        .async_lift(|inner| async {
            domain::types::crypto_operation(
                &key_manager_state,
                common_utils::type_name!(Authentication),
                domain::types::CryptoOperation::DecryptOptional(inner),
                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_optionaloperation())
        })
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to decrypt email from authentication table")?;

    let browser_info = authentication
        .browser_info
        .clone()
        .map(|browser_info| browser_info.parse_value::<BrowserInformation>("BrowserInformation"))
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to parse browser information from authentication table")?;

    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 authentication_details = business_profile
        .authentication_connector_details
        .clone()
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("authentication_connector_details not configured by the merchant")?;

    let connector_name_string = authentication_connector.to_string();
    let mca_id_option = three_ds_connector_account.get_mca_id();
    let merchant_connector_account_id_or_connector_name = mca_id_option
        .as_ref()
        .map(|mca_id| mca_id.get_string_repr())
        .unwrap_or(&connector_name_string);

    let webhook_url = helpers::create_webhook_url(
        &state.base_url,
        merchant_id,
        merchant_connector_account_id_or_connector_name,
    );

    let auth_response = <ExternalAuthentication as UnifiedAuthenticationService>::authentication(
        &state,
        &business_profile,
        &common_enums::PaymentMethod::Card,
        browser_info,
        authentication.amount,
        authentication.currency,
        MessageCategory::Payment,
        req.device_channel,
        authentication.clone(),
        None,
        req.sdk_information,
        req.threeds_method_comp_ind,
        email_encrypted.map(common_utils::pii::Email::from),
        webhook_url,
        &three_ds_connector_account,
        &authentication_connector.to_string(),
        None,
    )
    .await?;

    let authentication = utils::external_authentication_update_trackers(
        &state,
        auth_response,
        authentication.clone(),
        None,
        merchant_context.get_merchant_key_store(),
        None,
        None,
        None,
        None,
    )
    .await?;

    let (authentication_value, eci) = match auth_flow {
        AuthFlow::Client => (None, None),
        AuthFlow::Merchant => {
            if let Some(common_enums::TransactionStatus::Success) = authentication.trans_status {
                let tokenised_data = crate::core::payment_methods::vault::get_tokenized_data(
                    &state,
                    authentication_id.get_string_repr(),
                    false,
                    merchant_context.get_merchant_key_store().key.get_inner(),
                )
                .await
                .inspect_err(|err| router_env::logger::error!(tokenized_data_result=?err))
                .attach_printable("cavv not present after authentication status is success")?;
                (
                    Some(masking::Secret::new(tokenised_data.value1)),
                    authentication.eci.clone(),
                )
            } else {
                (None, None)
            }
        }
    };

    let response = AuthenticationAuthenticateResponse::foreign_try_from((
        &authentication,
        authentication_value,
        eci,
        authentication_details,
    ))?;

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

impl
    ForeignTryFrom<(
        &Authentication,
        Option<masking::Secret<String>>,
        Option<String>,
        diesel_models::business_profile::AuthenticationConnectorDetails,
    )> for AuthenticationAuthenticateResponse
{
    type Error = error_stack::Report<ApiErrorResponse>;

    fn foreign_try_from(
        (authentication, authentication_value, eci, authentication_details): (
            &Authentication,
            Option<masking::Secret<String>>,
            Option<String>,
            diesel_models::business_profile::AuthenticationConnectorDetails,
        ),
    ) -> Result<Self, Self::Error> {
        let authentication_connector = authentication
            .authentication_connector
            .as_ref()
            .map(|connector| common_enums::AuthenticationConnectors::from_str(connector))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Incorrect authentication connector stored in table")?;
        let acs_url = authentication
            .acs_url
            .clone()
            .map(|acs_url| url::Url::parse(&acs_url))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to parse the url with param")?;
        let acquirer_details = AcquirerDetails {
            acquirer_bin: authentication.acquirer_bin.clone(),
            acquirer_merchant_id: authentication.acquirer_merchant_id.clone(),
            merchant_country_code: authentication.acquirer_country_code.clone(),
        };
        Ok(Self {
            transaction_status: authentication.trans_status.clone(),
            acs_url,
            challenge_request: authentication.challenge_request.clone(),
            acs_reference_number: authentication.acs_reference_number.clone(),
            acs_trans_id: authentication.acs_trans_id.clone(),
            three_ds_server_transaction_id: authentication.threeds_server_transaction_id.clone(),
            acs_signed_content: authentication.acs_signed_content.clone(),
            three_ds_requestor_url: authentication_details.three_ds_requestor_url.clone(),
            three_ds_requestor_app_url: authentication_details.three_ds_requestor_app_url.clone(),
            error_code: None,
            error_message: authentication.error_message.clone(),
            authentication_value,
            status: authentication.authentication_status,
            authentication_connector,
            eci,
            authentication_id: authentication.authentication_id.clone(),
            acquirer_details: Some(acquirer_details),
        })
    }
}

#[cfg(feature = "v1")]
pub async fn authentication_sync_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    auth_flow: AuthFlow,
    req: AuthenticationSyncRequest,
) -> RouterResponse<AuthenticationSyncResponse> {
    let authentication_id = req.authentication_id;
    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
        .map(|client_secret| {
            utils::authenticate_authentication_client_secret_and_check_expiry(
                client_secret.peek(),
                &authentication,
            )
        })
        .transpose()?;

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

    let profile_id = authentication.profile_id.clone();

    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 updated_authentication = match authentication.trans_status.clone() {
        Some(trans_status) if trans_status.clone().is_pending() => {
            let post_auth_response = ExternalAuthentication::post_authentication(
                &state,
                &business_profile,
                None,
                &three_ds_connector_account,
                &authentication_connector.to_string(),
                &authentication_id,
                common_enums::PaymentMethod::Card,
                merchant_id,
                Some(&authentication),
            )
            .await?;

            utils::external_authentication_update_trackers(
                &state,
                post_auth_response,
                authentication.clone(),
                None,
                merchant_context.get_merchant_key_store(),
                None,
                None,
                None,
                None,
            )
            .await?
        }

        _ => authentication,
    };

    let (authentication_value, eci) = match auth_flow {
        AuthFlow::Client => (None, None),
        AuthFlow::Merchant => {
            if let Some(common_enums::TransactionStatus::Success) =
                updated_authentication.trans_status
            {
                let tokenised_data = crate::core::payment_methods::vault::get_tokenized_data(
                    &state,
                    authentication_id.get_string_repr(),
                    false,
                    merchant_context.get_merchant_key_store().key.get_inner(),
                )
                .await
                .inspect_err(|err| router_env::logger::error!(tokenized_data_result=?err))
                .attach_printable("cavv not present after authentication status is success")?;
                (
                    Some(masking::Secret::new(tokenised_data.value1)),
                    updated_authentication.eci.clone(),
                )
            } else {
                (None, None)
            }
        }
    };

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

    let encrypted_data = domain::types::crypto_operation(
        &key_manager_state,
        common_utils::type_name!(hyperswitch_domain_models::authentication::Authentication),
        domain::types::CryptoOperation::BatchDecrypt(
            hyperswitch_domain_models::authentication::EncryptedAuthentication::to_encryptable(
                hyperswitch_domain_models::authentication::EncryptedAuthentication {
                    billing_address: updated_authentication.billing_address,
                    shipping_address: updated_authentication.shipping_address,
                },
            ),
        ),
        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_decrypted = updated_authentication
        .email
        .clone()
        .async_lift(|inner| async {
            domain::types::crypto_operation(
                &key_manager_state,
                common_utils::type_name!(Authentication),
                domain::types::CryptoOperation::DecryptOptional(inner),
                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_optionaloperation())
        })
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt email")?;

    let browser_info = updated_authentication
        .browser_info
        .clone()
        .map(|browser_info| {
            browser_info.parse_value::<payments::BrowserInformation>("BrowserInformation")
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)?;

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

    let authentication_connector = updated_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 billing = encrypted_data
        .billing_address
        .map(|billing| {
            billing
                .into_inner()
                .expose()
                .parse_value::<payments::Address>("Address")
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to parse billing address")?;

    let shipping = encrypted_data
        .shipping_address
        .map(|shipping| {
            shipping
                .into_inner()
                .expose()
                .parse_value::<payments::Address>("Address")
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to parse shipping address")?;

    let response = AuthenticationSyncResponse {
        authentication_id: authentication_id.clone(),
        merchant_id: merchant_id.clone(),
        status: updated_authentication.authentication_status,
        client_secret: updated_authentication
            .authentication_client_secret
            .map(masking::Secret::new),
        amount,
        currency,
        authentication_connector,
        force_3ds_challenge: updated_authentication.force_3ds_challenge,
        return_url: updated_authentication.return_url.clone(),
        created_at: updated_authentication.created_at,
        profile_id: updated_authentication.profile_id.clone(),
        psd2_sca_exemption_type: updated_authentication.psd2_sca_exemption_type,
        acquirer_details,
        error_message: updated_authentication.error_message.clone(),
        error_code: updated_authentication.error_code.clone(),
        authentication_value,
        threeds_server_transaction_id: updated_authentication.threeds_server_transaction_id.clone(),
        maximum_supported_3ds_version: updated_authentication.maximum_supported_version.clone(),
        connector_authentication_id: updated_authentication.connector_authentication_id.clone(),
        three_ds_method_data: updated_authentication.three_ds_method_data.clone(),
        three_ds_method_url: updated_authentication.three_ds_method_url.clone(),
        message_version: updated_authentication.message_version.clone(),
        connector_metadata: updated_authentication.connector_metadata.clone(),
        directory_server_id: updated_authentication.directory_server_id.clone(),
        billing,
        shipping,
        browser_information: browser_info,
        email: email_decrypted,
        transaction_status: updated_authentication.trans_status.clone(),
        acs_url: updated_authentication.acs_url.clone(),
        challenge_request: updated_authentication.challenge_request.clone(),
        acs_reference_number: updated_authentication.acs_reference_number.clone(),
        acs_trans_id: updated_authentication.acs_trans_id.clone(),
        acs_signed_content: updated_authentication.acs_signed_content,
        three_ds_requestor_url: business_profile
            .authentication_connector_details
            .clone()
            .map(|details| details.three_ds_requestor_url),
        three_ds_requestor_app_url: business_profile
            .authentication_connector_details
            .and_then(|details| details.three_ds_requestor_app_url),
        profile_acquirer_id: updated_authentication.profile_acquirer_id.clone(),
        eci,
    };
    Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
        response,
    ))
}

#[cfg(feature = "v1")]
pub async fn authentication_post_sync_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationSyncPostUpdateRequest,
) -> RouterResponse<()> {
    let authentication_id = req.authentication_id;
    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(),
        })?;

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

    let key_manager_state = (&state).into();
    let business_profile = db
        .find_business_profile_by_profile_id(
            &key_manager_state,
            merchant_context.get_merchant_key_store(),
            &authentication.profile_id,
        )
        .await
        .to_not_found_response(ApiErrorResponse::ProfileNotFound {
            id: authentication.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 post_auth_response =
        <ExternalAuthentication as UnifiedAuthenticationService>::post_authentication(
            &state,
            &business_profile,
            None,
            &three_ds_connector_account,
            &authentication_connector.to_string(),
            &authentication_id,
            common_enums::PaymentMethod::Card,
            merchant_id,
            Some(&authentication),
        )
        .await?;

    let updated_authentication = utils::external_authentication_update_trackers(
        &state,
        post_auth_response,
        authentication.clone(),
        None,
        merchant_context.get_merchant_key_store(),
        None,
        None,
        None,
        None,
    )
    .await?;

    let authentication_details = business_profile
        .authentication_connector_details
        .clone()
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("authentication_connector_details not configured by the merchant")?;

    let authentication_response = AuthenticationAuthenticateResponse::foreign_try_from((
        &updated_authentication,
        None,
        None,
        authentication_details,
    ))?;

    let redirect_response = helpers::get_handle_response_url_for_modular_authentication(
        authentication_id,
        &business_profile,
        &authentication_response,
        authentication_connector.to_string(),
        authentication.return_url,
        updated_authentication
            .authentication_client_secret
            .clone()
            .map(masking::Secret::new)
            .as_ref(),
        updated_authentication.amount,
    )?;

    Ok(hyperswitch_domain_models::api::ApplicationResponse::JsonForRedirection(redirect_response))
}

fn ensure_not_terminal_status(
    status: Option<common_enums::TransactionStatus>,
) -> Result<(), error_stack::Report<ApiErrorResponse>> {
    status
        .filter(|s| s.clone().is_terminal_state())
        .map(|s| {
            Err(error_stack::Report::new(
                ApiErrorResponse::UnprocessableEntity {
                    message: format!(
                        "authentication status for the given authentication_id is already in {s}"
                    ),
                },
            ))
        })
        .unwrap_or(Ok(()))
}


// File: crates/router/src/routes/fraud_check.rs
// Module: router
// Public functions: 1

use actix_web::{web, HttpRequest, HttpResponse};
use router_env::Flow;

use crate::{
    core::{api_locking, fraud_check as frm_core},
    services::{self, api},
    types::domain,
    AppState,
};

#[cfg(feature = "v1")]
pub async fn frm_fulfillment(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<frm_core::types::FrmFulfillmentRequest>,
) -> HttpResponse {
    let flow = Flow::FrmFulfillment;
    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &req,
        json_payload.into_inner(),
        |state, auth: services::authentication::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            frm_core::frm_fulfillment_core(state, merchant_context, req)
        },
        &services::authentication::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// Implementation: impl api::PaymentSession for for Volt
// File: crates/hyperswitch_connectors/src/connectors/volt.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSession for for Volt


// Implementation: impl Loonio
// File: crates/hyperswitch_connectors/src/connectors/loonio.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl Loonio


// Implementation: impl MerchantConnectorAccountInterface for for KafkaStore
// File: crates/router/src/db/kafka_store.rs
// Module: router
// Methods: 13 total (0 public)

impl MerchantConnectorAccountInterface for for KafkaStore


// Implementation: impl api::Refund for for Archipel
// File: crates/hyperswitch_connectors/src/connectors/archipel.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Refund for for Archipel


// Function: find_by_connector_transaction_id_payment_id_merchant_id
// File: crates/diesel_models/src/query/payment_attempt.rs
// Module: diesel_models

pub fn find_by_connector_transaction_id_payment_id_merchant_id(
        conn: &PgPooledConn,
        connector_transaction_id: &common_utils::types::ConnectorTransactionId,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self>


// Struct: ActivateRoutingConfigRequest
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Implementations: 0

pub struct ActivateRoutingConfigRequest


// Struct: ProcessedAmountAccumulator
// File: crates/analytics/src/payment_intents/accumulator.rs
// Module: analytics
// Implementations: 1
// Traits: PaymentIntentMetricAccumulator

pub struct ProcessedAmountAccumulator


// Function: get_external_vault_token
// File: crates/router/src/core/payment_methods.rs
// Module: router

pub fn get_external_vault_token(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: enums::MerchantStorageScheme,
    payment_token: String,
    vault_token: domain::VaultToken,
    payment_method_type: &storage_enums::PaymentMethod,
) -> CustomResult<domain::ExternalVaultPaymentMethodData, errors::ApiErrorResponse>


// Struct: DwollaAuthType
// File: crates/hyperswitch_connectors/src/connectors/dwolla/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DwollaAuthType


// Struct: NexixpayPaymentsCancelRequest
// File: crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NexixpayPaymentsCancelRequest


// Implementation: impl api::RefundExecute for for Worldpayxml
// File: crates/hyperswitch_connectors/src/connectors/worldpayxml.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundExecute for for Worldpayxml


// Implementation: impl RoutingApproach
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Methods: 1 total (1 public)

impl RoutingApproach


// Implementation: impl std::fmt::Display for for PartitionKey
// File: crates/storage_impl/src/redis/kv_store.rs
// Module: storage_impl
// Methods: 1 total (0 public)

impl std::fmt::Display for for PartitionKey


// Struct: ClientProcessorInformation
// File: crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ClientProcessorInformation


// Struct: Affirm
// File: crates/hyperswitch_connectors/src/connectors/affirm.rs
// Module: hyperswitch_connectors
// Implementations: 18
// Traits: api::Payment, api::PaymentSession, api::ConnectorAccessToken, api::MandateSetup, api::PaymentAuthorize, api::PaymentsCompleteAuthorize, api::PaymentSync, api::PaymentCapture, api::PaymentVoid, api::Refund, api::RefundExecute, api::RefundSync, api::PaymentToken, ConnectorCommon, ConnectorValidation, webhooks::IncomingWebhook, ConnectorSpecifications

pub struct Affirm

api::Payment api::PaymentSession api::ConnectorAccessToken api::MandateSetup api::PaymentAuthorize api::PaymentsCompleteAuthorize api::PaymentSync api::PaymentCapture api::PaymentVoid api::Refund api::RefundExecute api::RefundSync api::PaymentToken ConnectorCommon ConnectorValidation webhooks::IncomingWebhook ConnectorSpecifications


// Implementation: impl ConnectorValidation for for Klarna
// File: crates/hyperswitch_connectors/src/connectors/klarna.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorValidation for for Klarna


// Struct: SRApiClient
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Implementations: 1
// Traits: DecisionEngineApiHandler

pub struct SRApiClient


// Implementation: impl NovalnetSyncResponseTransactionData
// File: crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl NovalnetSyncResponseTransactionData


// Struct: DigitalvirgoErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DigitalvirgoErrorResponse


// Trait: EmailClient
// File: crates/external_services/src/email.rs
// Module: external_services

pub trait EmailClient: Sync + Send + dyn_clone::DynClone


// Implementation: impl api::RefundSync for for Dlocal
// File: crates/hyperswitch_connectors/src/connectors/dlocal.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundSync for for Dlocal


// Implementation: impl ConnectorEvent
// File: crates/hyperswitch_interfaces/src/events/connector_api_logs.rs
// Module: hyperswitch_interfaces
// Methods: 8 total (4 public)

impl ConnectorEvent


// Function: get_cache_key_from_role_id
// File: crates/router/src/services/authorization.rs
// Module: router

pub fn get_cache_key_from_role_id(role_id: &str) -> String


// Function: encrypt
// File: crates/external_services/src/no_encryption/core.rs
// Module: external_services

// Documentation: Encryption functionality

pub fn encrypt(&self, data: impl AsRef<[u8]>) -> Vec<u8>


// Implementation: impl PaymentRedirectFlow for for PaymentRedirectCompleteAuthorize
// File: crates/router/src/core/payments.rs
// Module: router
// Methods: 3 total (0 public)

impl PaymentRedirectFlow for for PaymentRedirectCompleteAuthorize


// Struct: FeatureMetadata
// File: crates/diesel_models/src/types.rs
// Module: diesel_models
// Implementations: 1

pub struct FeatureMetadata


// Function: build_auth_array
// File: crates/analytics/src/opensearch.rs
// Module: analytics

pub fn build_auth_array(&self) -> Vec<Value>


// Function: get_create_file_request
// File: crates/router/src/routes/files/transformers.rs
// Module: router

pub fn get_create_file_request(
    mut payload: Multipart,
) -> CustomResult<CreateFileRequest, errors::ApiErrorResponse>


// Implementation: impl Checkout
// File: crates/hyperswitch_connectors/src/connectors/checkout.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl Checkout


// Function: get_token
// File: crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs
// Module: hyperswitch_connectors

pub fn get_token(transaction_data: Option<&Self>) -> Option<String>


// Function: merchant_account_create
// File: crates/openapi/src/routes/merchant_account.rs
// Module: openapi

pub fn merchant_account_create()


// Implementation: impl api::Payment for for Placetopay
// File: crates/hyperswitch_connectors/src/connectors/placetopay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Payment for for Placetopay


// Implementation: impl api::PaymentSync for for Payeezy
// File: crates/hyperswitch_connectors/src/connectors/payeezy.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSync for for Payeezy