// File: crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 76

use base64::Engine;
use common_enums::{enums, FutureUsage};
use common_types::payments::ApplePayPredecryptData;
use common_utils::{consts, ext_traits::OptionExt, pii, types::StringMajorUnit};
use hyperswitch_domain_models::{
    payment_method_data::{
        ApplePayWalletData, GooglePayWalletData, PaymentMethodData, SamsungPayWalletData,
        WalletData,
    },
    router_data::{
        AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData,
        ErrorResponse, PaymentMethodToken, RouterData,
    },
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{
        PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData,
        ResponseId,
    },
    router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        RefundsRouterData, SetupMandateRouterData,
    },
};
use hyperswitch_interfaces::{api, errors};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
    constants,
    types::{RefundsResponseRouterData, ResponseRouterData},
    unimplemented_payment_method,
    utils::{
        self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData,
        PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RecurringMandateData,
        RouterData as OtherRouterData,
    },
};
pub struct BankOfAmericaAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) merchant_account: Secret<String>,
    pub(super) api_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for BankOfAmericaAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::SignatureKey {
            api_key,
            key1,
            api_secret,
        } = auth_type
        {
            Ok(Self {
                api_key: api_key.to_owned(),
                merchant_account: key1.to_owned(),
                api_secret: api_secret.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType)?
        }
    }
}

pub struct BankOfAmericaRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}

impl<T> TryFrom<(StringMajorUnit, T)> for BankOfAmericaRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from((amount, item): (StringMajorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaPaymentsRequest {
    processing_information: ProcessingInformation,
    payment_information: PaymentInformation,
    order_information: OrderInformationWithBill,
    client_reference_information: ClientReferenceInformation,
    #[serde(skip_serializing_if = "Option::is_none")]
    consumer_authentication_information: Option<BankOfAmericaConsumerAuthInformation>,
    #[serde(skip_serializing_if = "Option::is_none")]
    merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessingInformation {
    action_list: Option<Vec<BankOfAmericaActionsList>>,
    action_token_types: Option<Vec<BankOfAmericaActionsTokenType>>,
    authorization_options: Option<BankOfAmericaAuthorizationOptions>,
    commerce_indicator: String,
    capture: Option<bool>,
    capture_options: Option<CaptureOptions>,
    payment_solution: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BankOfAmericaActionsList {
    TokenCreate,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BankOfAmericaActionsTokenType {
    PaymentInstrument,
    Customer,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaAuthorizationOptions {
    initiator: Option<BankOfAmericaPaymentInitiator>,
    merchant_intitiated_transaction: Option<MerchantInitiatedTransaction>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaPaymentInitiator {
    #[serde(rename = "type")]
    initiator_type: Option<BankOfAmericaPaymentInitiatorTypes>,
    credential_stored_on_file: Option<bool>,
    stored_credential_used: Option<bool>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum BankOfAmericaPaymentInitiatorTypes {
    Customer,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantInitiatedTransaction {
    reason: Option<String>,
    //Required for recurring mandates payment
    original_authorized_amount: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDefinedInformation {
    key: u8,
    value: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthInformation {
    ucaf_collection_indicator: Option<String>,
    cavv: Option<String>,
    ucaf_authentication_data: Option<Secret<String>>,
    xid: Option<String>,
    directory_server_transaction_id: Option<Secret<String>>,
    specification_version: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CaptureOptions {
    capture_sequence_number: u32,
    total_capture_count: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BankOfAmericaPaymentInstrument {
    id: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardPaymentInformation {
    card: Card,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayPaymentInformation {
    fluid_data: FluidData,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenizedCard {
    transaction_type: TransactionType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenPaymentInformation {
    fluid_data: FluidData,
    tokenized_card: ApplePayTokenizedCard,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayPaymentInformation {
    tokenized_card: TokenizedCard,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum PaymentInformation {
    Cards(Box<CardPaymentInformation>),
    GooglePay(Box<GooglePayPaymentInformation>),
    ApplePay(Box<ApplePayPaymentInformation>),
    ApplePayToken(Box<ApplePayTokenPaymentInformation>),
    MandatePayment(Box<MandatePaymentInformation>),
    SamsungPay(Box<SamsungPayPaymentInformation>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MandatePaymentInformation {
    payment_instrument: BankOfAmericaPaymentInstrument,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Card {
    number: cards::CardNumber,
    expiration_month: Secret<String>,
    expiration_year: Secret<String>,
    security_code: Secret<String>,
    #[serde(rename = "type")]
    card_type: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenizedCard {
    number: cards::CardNumber,
    expiration_month: Secret<String>,
    expiration_year: Secret<String>,
    cryptogram: Secret<String>,
    transaction_type: TransactionType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FluidData {
    value: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    descriptor: Option<String>,
}

pub const FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY: &str = "FID=COMMON.SAMSUNG.INAPP.PAYMENT";

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderInformationWithBill {
    amount_details: Amount,
    bill_to: Option<BillTo>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    total_amount: StringMajorUnit,
    currency: api_models::enums::Currency,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BillTo {
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
    address1: Option<Secret<String>>,
    locality: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    administrative_area: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    postal_code: Option<Secret<String>>,
    country: Option<enums::CountryAlpha2>,
    email: pii::Email,
}

impl TryFrom<&SetupMandateRouterData> for BankOfAmericaPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &SetupMandateRouterData) -> Result<Self, Self::Error> {
        match item.request.payment_method_data.clone() {
            PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)),
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                WalletData::ApplePay(apple_pay_data) => Self::try_from((item, apple_pay_data)),
                WalletData::GooglePay(google_pay_data) => Self::try_from((item, google_pay_data)),
                WalletData::AliPayQr(_)
                | WalletData::AliPayRedirect(_)
                | WalletData::AliPayHkRedirect(_)
                | WalletData::AmazonPayRedirect(_)
                | WalletData::Paysera(_)
                | WalletData::Skrill(_)
                | WalletData::BluecodeRedirect {}
                | WalletData::MomoRedirect(_)
                | WalletData::KakaoPayRedirect(_)
                | WalletData::GoPayRedirect(_)
                | WalletData::GcashRedirect(_)
                | WalletData::ApplePayRedirect(_)
                | WalletData::ApplePayThirdPartySdk(_)
                | WalletData::DanaRedirect {}
                | WalletData::GooglePayRedirect(_)
                | WalletData::GooglePayThirdPartySdk(_)
                | WalletData::MbWayRedirect(_)
                | WalletData::MobilePayRedirect(_)
                | WalletData::PaypalRedirect(_)
                | WalletData::PaypalSdk(_)
                | WalletData::Paze(_)
                | WalletData::SamsungPay(_)
                | WalletData::AmazonPay(_)
                | WalletData::TwintRedirect {}
                | WalletData::VippsRedirect {}
                | WalletData::TouchNGoRedirect(_)
                | WalletData::WeChatPayRedirect(_)
                | WalletData::WeChatPayQr(_)
                | WalletData::CashappQr(_)
                | WalletData::SwishQr(_)
                | WalletData::Mifinity(_)
                | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
                ))?,
            },
            PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
                ))?
            }
        }
    }
}

impl<F, T>
    TryFrom<ResponseRouterData<F, BankOfAmericaSetupMandatesResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BankOfAmericaSetupMandatesResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            BankOfAmericaSetupMandatesResponse::ClientReferenceInformation(info_response) => {
                let mandate_reference =
                    info_response
                        .token_information
                        .clone()
                        .map(|token_info| MandateReference {
                            connector_mandate_id: token_info
                                .payment_instrument
                                .map(|payment_instrument| payment_instrument.id.expose()),
                            payment_method_id: None,
                            mandate_metadata: None,
                            connector_mandate_request_reference_id: None,
                        });
                let mut mandate_status =
                    map_boa_attempt_status((info_response.status.clone(), false));
                if matches!(mandate_status, enums::AttemptStatus::Authorized) {
                    //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well.
                    mandate_status = enums::AttemptStatus::Charged
                }
                let error_response =
                    get_error_response_if_failure((&info_response, mandate_status, item.http_code));

                let connector_response = match item.data.payment_method {
                    common_enums::PaymentMethod::Card => info_response
                        .processor_information
                        .as_ref()
                        .and_then(|processor_information| {
                            info_response
                                .consumer_authentication_information
                                .as_ref()
                                .map(|consumer_auth_information| {
                                    convert_to_additional_payment_method_connector_response(
                                        processor_information,
                                        consumer_auth_information,
                                    )
                                })
                        })
                        .map(ConnectorResponseData::with_additional_payment_method_data),
                    common_enums::PaymentMethod::CardRedirect
                    | common_enums::PaymentMethod::PayLater
                    | common_enums::PaymentMethod::Wallet
                    | common_enums::PaymentMethod::BankRedirect
                    | common_enums::PaymentMethod::BankTransfer
                    | common_enums::PaymentMethod::Crypto
                    | common_enums::PaymentMethod::BankDebit
                    | common_enums::PaymentMethod::Reward
                    | common_enums::PaymentMethod::RealTimePayment
                    | common_enums::PaymentMethod::MobilePayment
                    | common_enums::PaymentMethod::Upi
                    | common_enums::PaymentMethod::Voucher
                    | common_enums::PaymentMethod::OpenBanking
                    | common_enums::PaymentMethod::GiftCard => None,
                };

                Ok(Self {
                    status: mandate_status,
                    response: match error_response {
                        Some(error) => Err(error),
                        None => Ok(PaymentsResponseData::TransactionResponse {
                            resource_id: ResponseId::ConnectorTransactionId(
                                info_response.id.clone(),
                            ),
                            redirection_data: Box::new(None),
                            mandate_reference: Box::new(mandate_reference),
                            connector_metadata: None,
                            network_txn_id: None,
                            connector_response_reference_id: Some(
                                info_response
                                    .client_reference_information
                                    .code
                                    .clone()
                                    .unwrap_or(info_response.id),
                            ),
                            incremental_authorization_allowed: None,
                            charges: None,
                        }),
                    },
                    connector_response,
                    ..item.data
                })
            }
            BankOfAmericaSetupMandatesResponse::ErrorInformation(error_response) => {
                let response = Err(convert_to_error_response_from_error_info(
                    &error_response,
                    item.http_code,
                ));
                Ok(Self {
                    response,
                    status: enums::AttemptStatus::Failure,
                    ..item.data
                })
            }
        }
    }
}

// for bankofamerica each item in Billing is mandatory
// fn build_bill_to(
//     address_details: &payments::Address,
//     email: pii::Email,
// ) -> Result<BillTo, error_stack::Report<errors::ConnectorError>> {
//     let address = address_details
//         .address
//         .as_ref()
//         .ok_or_else(utils::missing_field_err("billing.address"))?;

//     let country = address.get_country()?.to_owned();
//     let first_name = address.get_first_name()?;

//     let (administrative_area, postal_code) =
//         if country == api_enums::CountryAlpha2::US || country == api_enums::CountryAlpha2::CA {
//             let mut state = address.to_state_code()?.peek().clone();
//             state.truncate(20);
//             (
//                 Some(Secret::from(state)),
//                 Some(address.get_zip()?.to_owned()),
//             )
//         } else {
//             let zip = address.zip.clone();
//             let mut_state = address.state.clone().map(|state| state.expose());
//             match mut_state {
//                 Some(mut state) => {
//                     state.truncate(20);
//                     (Some(Secret::from(state)), zip)
//                 }
//                 None => (None, zip),
//             }
//         };
//     Ok(BillTo {
//         first_name: first_name.clone(),
//         last_name: address.get_last_name().unwrap_or(first_name).clone(),
//         address1: address.get_line1()?.to_owned(),
//         locality: Secret::new(address.get_city()?.to_owned()),
//         administrative_area,
//         postal_code,
//         country,
//         email,
//     })
// }

fn build_bill_to(
    address_details: Option<&hyperswitch_domain_models::address::Address>,
    email: pii::Email,
) -> Result<BillTo, error_stack::Report<errors::ConnectorError>> {
    let default_address = BillTo {
        first_name: None,
        last_name: None,
        address1: None,
        locality: None,
        administrative_area: None,
        postal_code: None,
        country: None,
        email: email.clone(),
    };

    Ok(address_details
        .and_then(|addr| {
            addr.address.as_ref().map(|addr| {
                let administrative_area = addr.to_state_code_as_optional().unwrap_or_else(|_| {
                    addr.state
                        .clone()
                        .map(|state| Secret::new(format!("{:.20}", state.expose())))
                });

                BillTo {
                    first_name: addr.first_name.clone(),
                    last_name: addr.last_name.clone(),
                    address1: addr.line1.clone(),
                    locality: addr.city.clone(),
                    administrative_area,
                    postal_code: addr.zip.clone(),
                    country: addr.country,
                    email,
                }
            })
        })
        .unwrap_or(default_address))
}

fn get_boa_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> {
    match card_network {
        common_enums::CardNetwork::Visa => Some("001"),
        common_enums::CardNetwork::Mastercard => Some("002"),
        common_enums::CardNetwork::AmericanExpress => Some("003"),
        common_enums::CardNetwork::JCB => Some("007"),
        common_enums::CardNetwork::DinersClub => Some("005"),
        common_enums::CardNetwork::Discover => Some("004"),
        common_enums::CardNetwork::CartesBancaires => Some("006"),
        common_enums::CardNetwork::UnionPay => Some("062"),
        //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024"
        common_enums::CardNetwork::Maestro => Some("042"),
        common_enums::CardNetwork::Interac
        | common_enums::CardNetwork::RuPay
        | common_enums::CardNetwork::Star
        | common_enums::CardNetwork::Accel
        | common_enums::CardNetwork::Pulse
        | common_enums::CardNetwork::Nyce => None,
    }
}

#[derive(Debug, Serialize)]
pub enum PaymentSolution {
    ApplePay,
    GooglePay,
    SamsungPay,
}

impl From<PaymentSolution> for String {
    fn from(solution: PaymentSolution) -> Self {
        let payment_solution = match solution {
            PaymentSolution::ApplePay => "001",
            PaymentSolution::GooglePay => "012",
            PaymentSolution::SamsungPay => "008",
        };
        payment_solution.to_string()
    }
}

#[derive(Debug, Serialize)]
pub enum TransactionType {
    #[serde(rename = "1")]
    ApplePay,
    #[serde(rename = "1")]
    SamsungPay,
}

impl
    From<(
        &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
        Option<BillTo>,
    )> for OrderInformationWithBill
{
    fn from(
        (item, bill_to): (
            &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
            Option<BillTo>,
        ),
    ) -> Self {
        Self {
            amount_details: Amount {
                total_amount: item.amount.to_owned(),
                currency: item.router_data.request.currency,
            },
            bill_to,
        }
    }
}

impl
    TryFrom<(
        &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
        Option<PaymentSolution>,
        Option<String>,
    )> for ProcessingInformation
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        (item, solution, network): (
            &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
            Option<PaymentSolution>,
            Option<String>,
        ),
    ) -> Result<Self, Self::Error> {
        let (action_list, action_token_types, authorization_options) =
            if item.router_data.request.setup_future_usage == Some(FutureUsage::OffSession)
                && (item.router_data.request.customer_acceptance.is_some()
                    || item
                        .router_data
                        .request
                        .setup_mandate_details
                        .clone()
                        .is_some_and(|mandate_details| {
                            mandate_details.customer_acceptance.is_some()
                        }))
            {
                get_boa_mandate_action_details()
            } else if item.router_data.request.connector_mandate_id().is_some() {
                let original_amount = item
                    .router_data
                    .get_recurring_mandate_payment_data()?
                    .get_original_payment_amount()?;
                let original_currency = item
                    .router_data
                    .get_recurring_mandate_payment_data()?
                    .get_original_payment_currency()?;
                (
                    None,
                    None,
                    Some(BankOfAmericaAuthorizationOptions {
                        initiator: None,
                        merchant_intitiated_transaction: Some(MerchantInitiatedTransaction {
                            reason: None,
                            original_authorized_amount: Some(utils::get_amount_as_string(
                                &api::CurrencyUnit::Base,
                                original_amount,
                                original_currency,
                            )?),
                        }),
                    }),
                )
            } else {
                (None, None, None)
            };

        let commerce_indicator = get_commerce_indicator(network);

        Ok(Self {
            capture: Some(matches!(
                item.router_data.request.capture_method,
                Some(enums::CaptureMethod::Automatic) | None
            )),
            payment_solution: solution.map(String::from),
            action_list,
            action_token_types,
            authorization_options,
            capture_options: None,
            commerce_indicator,
        })
    }
}

impl From<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> for ClientReferenceInformation {
    fn from(item: &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>) -> Self {
        Self {
            code: Some(item.router_data.connector_request_reference_id.clone()),
        }
    }
}

impl From<&SetupMandateRouterData> for ClientReferenceInformation {
    fn from(item: &SetupMandateRouterData) -> Self {
        Self {
            code: Some(item.connector_request_reference_id.clone()),
        }
    }
}

fn convert_metadata_to_merchant_defined_info(metadata: Value) -> Vec<MerchantDefinedInformation> {
    let hashmap: std::collections::BTreeMap<String, Value> =
        serde_json::from_str(&metadata.to_string()).unwrap_or(std::collections::BTreeMap::new());
    let mut vector = Vec::new();
    let mut iter = 1;
    for (key, value) in hashmap {
        vector.push(MerchantDefinedInformation {
            key: iter,
            value: format!("{key}={value}"),
        });
        iter += 1;
    }
    vector
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientReferenceInformation {
    code: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientProcessorInformation {
    avs: Option<Avs>,
    card_verification: Option<CardVerification>,
    processor: Option<ProcessorResponse>,
    network_transaction_id: Option<Secret<String>>,
    approval_code: Option<String>,
    merchant_advice: Option<MerchantAdvice>,
    response_code: Option<String>,
    ach_verification: Option<AchVerification>,
    system_trace_audit_number: Option<String>,
    event_status: Option<String>,
    retrieval_reference_number: Option<String>,
    consumer_authentication_response: Option<ConsumerAuthenticationResponse>,
    response_details: Option<String>,
    transaction_id: Option<Secret<String>>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantAdvice {
    code: Option<String>,
    code_raw: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConsumerAuthenticationResponse {
    code: Option<String>,
    code_raw: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AchVerification {
    result_code_raw: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessorResponse {
    name: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardVerification {
    result_code: Option<String>,
    result_code_raw: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientRiskInformation {
    rules: Option<Vec<ClientRiskInformationRules>>,
    profile: Option<Profile>,
    score: Option<Score>,
    info_codes: Option<InfoCodes>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InfoCodes {
    address: Option<Vec<String>>,
    identity_change: Option<Vec<String>>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Score {
    factor_codes: Option<Vec<String>>,
    result: Option<RiskResult>,
    model_used: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RiskResult {
    StringVariant(String),
    IntVariant(u64),
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
    early_decision: Option<String>,
    name: Option<String>,
    decision: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClientRiskInformationRules {
    name: Option<Secret<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Avs {
    code: Option<String>,
    code_raw: Option<String>,
}

impl
    TryFrom<(
        &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
        hyperswitch_domain_models::payment_method_data::Card,
    )> for BankOfAmericaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, ccard): (
            &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
            hyperswitch_domain_models::payment_method_data::Card,
        ),
    ) -> Result<Self, Self::Error> {
        if item.router_data.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Card 3DS".to_string(),
                connector: "BankOfAmerica",
            })?
        };

        let email = item.router_data.request.get_email()?;
        let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?;
        let order_information = OrderInformationWithBill::from((item, Some(bill_to)));
        let payment_information = PaymentInformation::try_from(&ccard)?;
        let processing_information = ProcessingInformation::try_from((item, None, None))?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let merchant_defined_information = item
            .router_data
            .request
            .metadata
            .clone()
            .map(convert_metadata_to_merchant_defined_info);

        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            merchant_defined_information,
            consumer_authentication_information: None,
        })
    }
}

impl
    TryFrom<(
        &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
        Box<ApplePayPredecryptData>,
        ApplePayWalletData,
    )> for BankOfAmericaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, apple_pay_data, apple_pay_wallet_data): (
            &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
            Box<ApplePayPredecryptData>,
            ApplePayWalletData,
        ),
    ) -> Result<Self, Self::Error> {
        let email = item.router_data.request.get_email()?;
        let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?;
        let order_information = OrderInformationWithBill::from((item, Some(bill_to)));
        let processing_information = ProcessingInformation::try_from((
            item,
            Some(PaymentSolution::ApplePay),
            Some(apple_pay_wallet_data.payment_method.network.clone()),
        ))?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let payment_information = PaymentInformation::try_from(&apple_pay_data)?;
        let merchant_defined_information = item
            .router_data
            .request
            .metadata
            .clone()
            .map(convert_metadata_to_merchant_defined_info);
        let ucaf_collection_indicator = match apple_pay_wallet_data
            .payment_method
            .network
            .to_lowercase()
            .as_str()
        {
            "mastercard" => Some("2".to_string()),
            _ => None,
        };
        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            merchant_defined_information,
            consumer_authentication_information: Some(BankOfAmericaConsumerAuthInformation {
                ucaf_collection_indicator,
                cavv: None,
                ucaf_authentication_data: None,
                xid: None,
                directory_server_transaction_id: None,
                specification_version: None,
            }),
        })
    }
}

impl
    TryFrom<(
        &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
        GooglePayWalletData,
    )> for BankOfAmericaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, google_pay_data): (
            &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
            GooglePayWalletData,
        ),
    ) -> Result<Self, Self::Error> {
        let email = item.router_data.request.get_email()?;
        let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?;
        let order_information = OrderInformationWithBill::from((item, Some(bill_to)));
        let payment_information = PaymentInformation::try_from(&google_pay_data)?;
        let processing_information =
            ProcessingInformation::try_from((item, Some(PaymentSolution::GooglePay), None))?;
        let client_reference_information = ClientReferenceInformation::from(item);
        let merchant_defined_information = item
            .router_data
            .request
            .metadata
            .clone()
            .map(convert_metadata_to_merchant_defined_info);

        Ok(Self {
            processing_information,
            payment_information,
            order_information,
            client_reference_information,
            merchant_defined_information,
            consumer_authentication_information: None,
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SamsungPayTokenizedCard {
    transaction_type: TransactionType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SamsungPayPaymentInformation {
    fluid_data: FluidData,
    tokenized_card: SamsungPayTokenizedCard,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SamsungPayFluidDataValue {
    public_key_hash: Secret<String>,
    version: String,
    data: Secret<String>,
}

impl TryFrom<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>>
    for BankOfAmericaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.connector_mandate_id() {
            Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)),
            None => {
                match item.router_data.request.payment_method_data.clone() {
                    PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
                    PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                        WalletData::ApplePay(apple_pay_data) => {
                            match item.router_data.payment_method_token.clone() {
                                Some(payment_method_token) => match payment_method_token {
                                    PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
                                        Self::try_from((item, decrypt_data, apple_pay_data))
                                    }
                                    PaymentMethodToken::Token(_) => {
                                        Err(unimplemented_payment_method!(
                                            "Apple Pay",
                                            "Manual",
                                            "Bank Of America"
                                        ))?
                                    }
                                    PaymentMethodToken::PazeDecrypt(_) => Err(
                                        unimplemented_payment_method!("Paze", "Bank Of America"),
                                    )?,
                                    PaymentMethodToken::GooglePayDecrypt(_) => {
                                        Err(unimplemented_payment_method!(
                                            "Google Pay",
                                            "Bank Of America"
                                        ))?
                                    }
                                },
                                None => {
                                    let email = item.router_data.request.get_email()?;
                                    let bill_to = build_bill_to(
                                        item.router_data.get_optional_billing(),
                                        email,
                                    )?;
                                    let order_information: OrderInformationWithBill =
                                        OrderInformationWithBill::from((item, Some(bill_to)));
                                    let processing_information =
                                        ProcessingInformation::try_from((
                                            item,
                                            Some(PaymentSolution::ApplePay),
                                            Some(apple_pay_data.payment_method.network.clone()),
                                        ))?;
                                    let client_reference_information =
                                        ClientReferenceInformation::from(item);
                                    let payment_information =
                                        PaymentInformation::try_from(&apple_pay_data)?;
                                    let merchant_defined_information = item
                                        .router_data
                                        .request
                                        .metadata
                                        .clone()
                                        .map(convert_metadata_to_merchant_defined_info);
                                    let ucaf_collection_indicator = match apple_pay_data
                                        .payment_method
                                        .network
                                        .to_lowercase()
                                        .as_str()
                                    {
                                        "mastercard" => Some("2".to_string()),
                                        _ => None,
                                    };
                                    Ok(Self {
                                        processing_information,
                                        payment_information,
                                        order_information,
                                        merchant_defined_information,
                                        client_reference_information,
                                        consumer_authentication_information: Some(
                                            BankOfAmericaConsumerAuthInformation {
                                                ucaf_collection_indicator,
                                                cavv: None,
                                                ucaf_authentication_data: None,
                                                xid: None,
                                                directory_server_transaction_id: None,
                                                specification_version: None,
                                            },
                                        ),
                                    })
                                }
                            }
                        }
                        WalletData::GooglePay(google_pay_data) => {
                            Self::try_from((item, google_pay_data))
                        }
                        WalletData::SamsungPay(samsung_pay_data) => {
                            Self::try_from((item, samsung_pay_data))
                        }

                        WalletData::AliPayQr(_)
                        | WalletData::AliPayRedirect(_)
                        | WalletData::AliPayHkRedirect(_)
                        | WalletData::AmazonPayRedirect(_)
                        | WalletData::Paysera(_)
                        | WalletData::Skrill(_)
                        | WalletData::BluecodeRedirect {}
                        | WalletData::MomoRedirect(_)
                        | WalletData::KakaoPayRedirect(_)
                        | WalletData::GoPayRedirect(_)
                        | WalletData::GcashRedirect(_)
                        | WalletData::ApplePayRedirect(_)
                        | WalletData::ApplePayThirdPartySdk(_)
                        | WalletData::DanaRedirect {}
                        | WalletData::GooglePayRedirect(_)
                        | WalletData::GooglePayThirdPartySdk(_)
                        | WalletData::MbWayRedirect(_)
                        | WalletData::MobilePayRedirect(_)
                        | WalletData::PaypalRedirect(_)
                        | WalletData::PaypalSdk(_)
                        | WalletData::Paze(_)
                        | WalletData::AmazonPay(_)
                        | WalletData::TwintRedirect {}
                        | WalletData::VippsRedirect {}
                        | WalletData::TouchNGoRedirect(_)
                        | WalletData::WeChatPayRedirect(_)
                        | WalletData::WeChatPayQr(_)
                        | WalletData::CashappQr(_)
                        | WalletData::SwishQr(_)
                        | WalletData::Mifinity(_)
                        | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                            utils::get_unimplemented_payment_method_error_message(


// Struct: OwnerCompany
// File: crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OwnerCompany


// Implementation: impl ConnectorSpecifications for for Opayo
// File: crates/hyperswitch_connectors/src/connectors/opayo.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorSpecifications for for Opayo


// Struct: ClientRiskInformation
// File: crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ClientRiskInformation


// File: crates/router/src/core/proxy/utils.rs
// Module: router
// Public functions: 7
// Public structs: 2

use api_models::{payment_methods::PaymentMethodId, proxy as proxy_api_models};
use common_utils::{
    ext_traits::{Encode, OptionExt},
    id_type,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::payment_methods;
use masking::Mask;
use serde_json::Value;
use x509_parser::nom::{
    bytes::complete::{tag, take_while1},
    character::complete::{char, multispace0},
    sequence::{delimited, preceded, terminated},
    IResult,
};

use crate::{
    core::{
        errors::{self, RouterResult},
        payment_methods::vault,
    },
    routes::SessionState,
    types::{domain, payment_methods as pm_types},
};

pub struct ProxyRequestWrapper(pub proxy_api_models::ProxyRequest);
pub enum ProxyRecord {
    PaymentMethodRecord(Box<domain::PaymentMethod>),
    TokenizationRecord(Box<domain::Tokenization>),
}

impl ProxyRequestWrapper {
    pub async fn get_proxy_record(
        &self,
        state: &SessionState,
        key_store: &domain::MerchantKeyStore,
        storage_scheme: common_enums::enums::MerchantStorageScheme,
    ) -> RouterResult<ProxyRecord> {
        let token = &self.0.token;

        match self.0.token_type {
            proxy_api_models::TokenType::PaymentMethodId => {
                let pm_id = PaymentMethodId {
                    payment_method_id: token.clone(),
                };
                let pm_id =
                    id_type::GlobalPaymentMethodId::generate_from_string(pm_id.payment_method_id)
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Unable to generate GlobalPaymentMethodId")?;

                let payment_method_record = state
                    .store
                    .find_payment_method(&((state).into()), key_store, &pm_id, storage_scheme)
                    .await
                    .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)?;
                Ok(ProxyRecord::PaymentMethodRecord(Box::new(
                    payment_method_record,
                )))
            }
            proxy_api_models::TokenType::TokenizationId => {
                let token_id = id_type::GlobalTokenId::from_string(token.clone().as_str())
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Error while coneverting from string to GlobalTokenId type",
                    )?;
                let db = state.store.as_ref();
                let key_manager_state = &(state).into();

                let tokenization_record = db
                    .get_entity_id_vault_id_by_token_id(&token_id, key_store, key_manager_state)
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Error while fetching tokenization record from vault")?;

                Ok(ProxyRecord::TokenizationRecord(Box::new(
                    tokenization_record,
                )))
            }
        }
    }

    pub fn get_headers(&self) -> Vec<(String, masking::Maskable<String>)> {
        self.0
            .headers
            .as_map()
            .iter()
            .map(|(key, value)| (key.clone(), value.clone().into_masked()))
            .collect()
    }

    pub fn get_destination_url(&self) -> &str {
        self.0.destination_url.as_str()
    }

    pub fn get_method(&self) -> common_utils::request::Method {
        self.0.method
    }
}

impl ProxyRecord {
    fn get_vault_id(&self) -> RouterResult<payment_methods::VaultId> {
        match self {
            Self::PaymentMethodRecord(payment_method) => payment_method
                .locker_id
                .clone()
                .get_required_value("vault_id")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Locker id not present in Payment Method Entry"),
            Self::TokenizationRecord(tokenization_record) => Ok(
                payment_methods::VaultId::generate(tokenization_record.locker_id.clone()),
            ),
        }
    }

    fn get_customer_id(&self) -> id_type::GlobalCustomerId {
        match self {
            Self::PaymentMethodRecord(payment_method) => payment_method.customer_id.clone(),
            Self::TokenizationRecord(tokenization_record) => {
                tokenization_record.customer_id.clone()
            }
        }
    }

    pub async fn get_vault_data(
        &self,
        state: &SessionState,
        merchant_context: domain::MerchantContext,
    ) -> RouterResult<Value> {
        match self {
            Self::PaymentMethodRecord(_) => {
                let vault_resp = vault::retrieve_payment_method_from_vault_internal(
                    state,
                    &merchant_context,
                    &self.get_vault_id()?,
                    &self.get_customer_id(),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error while fetching data from vault")?;

                Ok(vault_resp
                    .data
                    .encode_to_value()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to serialize vault data")?)
            }
            Self::TokenizationRecord(_) => {
                let vault_request = pm_types::VaultRetrieveRequest {
                    entity_id: self.get_customer_id(),
                    vault_id: self.get_vault_id()?,
                };

                let vault_data = vault::retrieve_value_from_vault(state, vault_request)
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to retrieve vault data")?;

                Ok(vault_data.get("data").cloned().unwrap_or(Value::Null))
            }
        }
    }
}

#[derive(Debug)]
pub struct TokenReference {
    pub field: String,
}

pub fn parse_token(input: &str) -> IResult<&str, TokenReference> {
    let (input, field) = delimited(
        tag("{{"),
        preceded(
            multispace0,
            preceded(
                char('$'),
                terminated(
                    take_while1(|c: char| c.is_alphanumeric() || c == '_'),
                    multispace0,
                ),
            ),
        ),
        tag("}}"),
    )(input)?;

    Ok((
        input,
        TokenReference {
            field: field.to_string(),
        },
    ))
}

pub fn contains_token(s: &str) -> bool {
    s.contains("{{") && s.contains("$") && s.contains("}}")
}


// Function: find_by_merchant_id_refund_id
// File: crates/diesel_models/src/query/refund.rs
// Module: diesel_models

pub fn find_by_merchant_id_refund_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        refund_id: &str,
    ) -> StorageResult<Self>


// Module Structure
// File: crates/storage_impl/src/lib.rs
// Module: storage_impl

// Public submodules:
pub mod callback_mapper;
pub mod cards_info;
pub mod config;
pub mod connection;
pub mod customers;
pub mod database;
pub mod errors;
pub mod kv_router_store;
pub mod lookup;
pub mod mandate;
pub mod metrics;
pub mod mock_db;
pub mod payment_method;
pub mod payments;
pub mod payouts;
// ... and 4 more

// Public exports:
pub use mock_db::MockDb;
pub use crate::database::store::Store;
pub use crate::{database::store::DatabaseStore, errors::StorageError};


// Function: upload_and_get_provider_provider_file_id_profile_id
// File: crates/router/src/core/files/helpers.rs
// Module: router

pub fn upload_and_get_provider_provider_file_id_profile_id(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    create_file_request: &api::CreateFileRequest,
    file_key: String,
) -> CustomResult<
    (
        String,
        api_models::enums::FileUploadProvider,
        Option<common_utils::id_type::ProfileId>,
        Option<common_utils::id_type::MerchantConnectorAccountId>,
    ),
    errors::ApiErrorResponse,
>


// Function: log_payment_attempt
// File: crates/router/src/services/kafka.rs
// Module: router

pub fn log_payment_attempt(
        &self,
        attempt: &PaymentAttempt,
        old_attempt: Option<PaymentAttempt>,
        tenant_id: TenantID,
    ) -> MQResult<()>


// Function: set_eligible_connector_for_nti_in_payment_data
// File: crates/router/src/core/payments.rs
// Module: router

pub fn set_eligible_connector_for_nti_in_payment_data<F, D>(
    state: &SessionState,
    business_profile: &domain::Profile,
    key_store: &domain::MerchantKeyStore,
    payment_data: &mut D,
    connector_choice: api::ConnectorChoice,
) -> RouterResult<api::ConnectorData>
where
    F: Send + Clone,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,


// Function: find_by_org_id
// File: crates/diesel_models/src/query/organization.rs
// Module: diesel_models

pub fn find_by_org_id(
        conn: &PgPooledConn,
        org_id: id_type::OrganizationId,
    ) -> StorageResult<Self>


// Implementation: impl DbConnectionParams for for Database
// File: crates/drainer/src/settings.rs
// Module: drainer
// Methods: 5 total (0 public)

impl DbConnectionParams for for Database


// Implementation: impl webhooks::IncomingWebhook for for Redsys
// File: crates/hyperswitch_connectors/src/connectors/redsys.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl webhooks::IncomingWebhook for for Redsys


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

impl IncomingWebhook for for Placetopay


// Function: get_retries
// File: crates/router/src/core/payouts/retry.rs
// Module: router

pub fn get_retries(
    state: &app::SessionState,
    retries: Option<i32>,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_type: PayoutRetryType,
) -> Option<i32>


// Function: get_string_repr
// File: crates/common_utils/src/id_type/global_id/refunds.rs
// Module: common_utils

// Documentation: Get string representation of the id

pub fn get_string_repr(&self) -> &str


// Function: add_tenant
// File: crates/router/src/events.rs
// Module: router

pub fn add_tenant(&mut self, tenant_config: &dyn TenantConfig)


// Implementation: impl super::disputes::filters::DisputeFilterAnalytics for for SqlxClient
// File: crates/analytics/src/sqlx.rs
// Module: analytics
// Methods: 0 total (0 public)

impl super::disputes::filters::DisputeFilterAnalytics for for SqlxClient


// File: crates/router/src/services/card_testing_guard.rs
// Module: router
// Public functions: 3

use std::sync::Arc;

use error_stack::ResultExt;
use redis_interface::RedisConnectionPool;

use crate::{
    core::errors::{ApiErrorResponse, RouterResult},
    routes::app::SessionStateInfo,
};

fn get_redis_connection<A: SessionStateInfo>(state: &A) -> RouterResult<Arc<RedisConnectionPool>> {
    state
        .store()
        .get_redis_conn()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")
}

pub async fn set_blocked_count_in_cache<A>(
    state: &A,
    cache_key: &str,
    value: i32,
    expiry: i64,
) -> RouterResult<()>
where
    A: SessionStateInfo + Sync,
{
    let redis_conn = get_redis_connection(state)?;

    redis_conn
        .set_key_with_expiry(&cache_key.into(), value, expiry)
        .await
        .change_context(ApiErrorResponse::InternalServerError)
}

pub async fn get_blocked_count_from_cache<A>(
    state: &A,
    cache_key: &str,
) -> RouterResult<Option<i32>>
where
    A: SessionStateInfo + Sync,
{
    let redis_conn = get_redis_connection(state)?;

    let value: Option<i32> = redis_conn
        .get_key(&cache_key.into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)?;

    Ok(value)
}

pub async fn increment_blocked_count_in_cache<A>(
    state: &A,
    cache_key: &str,
    expiry: i64,
) -> RouterResult<()>
where
    A: SessionStateInfo + Sync,
{
    let redis_conn = get_redis_connection(state)?;

    let value: Option<i32> = redis_conn
        .get_key(&cache_key.into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)?;

    let mut incremented_blocked_count: i32 = 1;

    if let Some(actual_value) = value {
        incremented_blocked_count = actual_value + 1;
    }

    redis_conn
        .set_key_with_expiry(&cache_key.into(), incremented_blocked_count, expiry)
        .await
        .change_context(ApiErrorResponse::InternalServerError)
}


// Struct: ClientRiskInformation
// File: crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ClientRiskInformation


// Implementation: impl DefendDispute for for Adyen
// File: crates/hyperswitch_connectors/src/connectors/adyen.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl DefendDispute for for Adyen


// Function: connector_delete
// File: crates/openapi/src/routes/merchant_connector_account.rs
// Module: openapi

pub fn connector_delete()


// Function: customers_retrieve
// File: crates/openapi/src/routes/customers.rs
// Module: openapi

pub fn customers_retrieve()


// Implementation: impl Responder
// File: crates/router/src/routes/payments.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Trait: PaymentMethodsStorageInterface
// File: crates/payment_methods/src/state.rs
// Module: payment_methods

pub trait PaymentMethodsStorageInterface: Send
    + Sync
    + dyn_clone::DynClone
    + pm_domain::PaymentMethodInterface<Error = errors::StorageError>
    + cards_info::CardsInfoInterface<Error = errors::StorageError>
    + customer::CustomerInterface<Error = errors::StorageError>
    + 'static


// Struct: PaymentMethodsState
// File: crates/payment_methods/src/state.rs
// Module: payment_methods
// Implementations: 1

pub struct PaymentMethodsState


// Struct: NoonCheckoutData
// File: crates/hyperswitch_connectors/src/connectors/noon/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NoonCheckoutData


// Function: from_relay_request
// File: crates/router/src/core/relay.rs
// Module: router

pub fn from_relay_request(relay_request: relay_api_models::RelayRequest) -> RouterResult<Self>


// Function: get_inner
// File: crates/router/src/types/domain/user.rs
// Module: router

pub fn get_inner(&self) -> &pii::Email


// Implementation: impl PaymentMethodVaultingData
// File: crates/hyperswitch_domain_models/src/vault.rs
// Module: hyperswitch_domain_models
// Methods: 2 total (2 public)

impl PaymentMethodVaultingData


// Function: into_inner
// File: crates/router/src/types/domain/user.rs
// Module: router

pub fn into_inner(self) -> pii::Email


// Struct: EbanxErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/ebanx/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct EbanxErrorResponse


// Struct: ProphetpayVoidResponse
// File: crates/hyperswitch_connectors/src/connectors/prophetpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ProphetpayVoidResponse


// File: crates/router/src/core/payments/types.rs
// Module: router
// Public functions: 25
// Public structs: 2

use std::{collections::HashMap, num::TryFromIntError};

use api_models::payment_methods::SurchargeDetailsResponse;
use common_utils::{
    errors::CustomResult,
    ext_traits::{Encode, OptionExt},
    types::{self as common_types, ConnectorTransactionIdTrait},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt;
pub use hyperswitch_domain_models::router_request_types::{
    AuthenticationData, SplitRefundsRequest, StripeSplitRefund, SurchargeDetails,
};
use redis_interface::errors::RedisError;
use router_env::{instrument, logger, tracing};

use crate::{
    consts as router_consts,
    core::errors::{self, RouterResult},
    routes::SessionState,
    types::{
        domain::Profile,
        storage::{self, enums as storage_enums},
        transformers::ForeignTryFrom,
    },
};

#[derive(Clone, Debug)]
pub struct MultipleCaptureData {
    // key -> capture_id, value -> Capture
    all_captures: HashMap<String, storage::Capture>,
    latest_capture: storage::Capture,
    pub expand_captures: Option<bool>,
    _private: Private, // to restrict direct construction of MultipleCaptureData
}
#[derive(Clone, Debug)]
struct Private {}

impl MultipleCaptureData {
    pub fn new_for_sync(
        captures: Vec<storage::Capture>,
        expand_captures: Option<bool>,
    ) -> RouterResult<Self> {
        let latest_capture = captures
            .last()
            .ok_or(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Cannot create MultipleCaptureData with empty captures list")?
            .clone();
        let multiple_capture_data = Self {
            all_captures: captures
                .into_iter()
                .map(|capture| (capture.capture_id.clone(), capture))
                .collect(),
            latest_capture,
            _private: Private {},
            expand_captures,
        };
        Ok(multiple_capture_data)
    }

    pub fn new_for_create(
        mut previous_captures: Vec<storage::Capture>,
        new_capture: storage::Capture,
    ) -> Self {
        previous_captures.push(new_capture.clone());
        Self {
            all_captures: previous_captures
                .into_iter()
                .map(|capture| (capture.capture_id.clone(), capture))
                .collect(),
            latest_capture: new_capture,
            _private: Private {},
            expand_captures: None,
        }
    }

    pub fn update_capture(&mut self, updated_capture: storage::Capture) {
        let capture_id = &updated_capture.capture_id;
        if self.all_captures.contains_key(capture_id) {
            self.all_captures
                .entry(capture_id.into())
                .and_modify(|capture| *capture = updated_capture.clone());
        }
    }
    pub fn get_total_blocked_amount(&self) -> common_types::MinorUnit {
        self.all_captures
            .iter()
            .fold(common_types::MinorUnit::new(0), |accumulator, capture| {
                accumulator
                    + match capture.1.status {
                        storage_enums::CaptureStatus::Charged
                        | storage_enums::CaptureStatus::Pending => capture.1.amount,
                        storage_enums::CaptureStatus::Started
                        | storage_enums::CaptureStatus::Failed => common_types::MinorUnit::new(0),
                    }
            })
    }
    pub fn get_total_charged_amount(&self) -> common_types::MinorUnit {
        self.all_captures
            .iter()
            .fold(common_types::MinorUnit::new(0), |accumulator, capture| {
                accumulator
                    + match capture.1.status {
                        storage_enums::CaptureStatus::Charged => capture.1.amount,
                        storage_enums::CaptureStatus::Pending
                        | storage_enums::CaptureStatus::Started
                        | storage_enums::CaptureStatus::Failed => common_types::MinorUnit::new(0),
                    }
            })
    }
    pub fn get_captures_count(&self) -> RouterResult<i16> {
        i16::try_from(self.all_captures.len())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while converting from usize to i16")
    }
    pub fn get_status_count(&self) -> HashMap<storage_enums::CaptureStatus, i16> {
        let mut hash_map: HashMap<storage_enums::CaptureStatus, i16> = HashMap::new();
        hash_map.insert(storage_enums::CaptureStatus::Charged, 0);
        hash_map.insert(storage_enums::CaptureStatus::Pending, 0);
        hash_map.insert(storage_enums::CaptureStatus::Started, 0);
        hash_map.insert(storage_enums::CaptureStatus::Failed, 0);
        self.all_captures
            .iter()
            .fold(hash_map, |mut accumulator, capture| {
                let current_capture_status = capture.1.status;
                accumulator
                    .entry(current_capture_status)
                    .and_modify(|count| *count += 1);
                accumulator
            })
    }
    pub fn get_attempt_status(
        &self,
        authorized_amount: common_types::MinorUnit,
    ) -> storage_enums::AttemptStatus {
        let total_captured_amount = self.get_total_charged_amount();
        if authorized_amount == total_captured_amount {
            return storage_enums::AttemptStatus::Charged;
        }
        let status_count_map = self.get_status_count();
        if status_count_map.get(&storage_enums::CaptureStatus::Charged) > Some(&0) {
            storage_enums::AttemptStatus::PartialChargedAndChargeable
        } else {
            storage_enums::AttemptStatus::CaptureInitiated
        }
    }
    pub fn get_pending_captures(&self) -> Vec<&storage::Capture> {
        self.all_captures
            .iter()
            .filter(|capture| capture.1.status == storage_enums::CaptureStatus::Pending)
            .map(|key_value| key_value.1)
            .collect()
    }
    pub fn get_all_captures(&self) -> Vec<&storage::Capture> {
        self.all_captures
            .iter()
            .map(|key_value| key_value.1)
            .collect()
    }
    pub fn get_capture_by_capture_id(&self, capture_id: String) -> Option<&storage::Capture> {
        self.all_captures.get(&capture_id)
    }
    pub fn get_capture_by_connector_capture_id(
        &self,
        connector_capture_id: &String,
    ) -> Option<&storage::Capture> {
        self.all_captures
            .iter()
            .find(|(_, capture)| {
                capture.get_optional_connector_transaction_id() == Some(connector_capture_id)
            })
            .map(|(_, capture)| capture)
    }
    pub fn get_latest_capture(&self) -> &storage::Capture {
        &self.latest_capture
    }
    pub fn get_pending_connector_capture_ids(&self) -> Vec<String> {
        let pending_connector_capture_ids = self
            .get_pending_captures()
            .into_iter()
            .filter_map(|capture| capture.get_optional_connector_transaction_id().cloned())
            .collect();
        pending_connector_capture_ids
    }
    pub fn get_pending_captures_without_connector_capture_id(&self) -> Vec<&storage::Capture> {
        self.get_pending_captures()
            .into_iter()
            .filter(|capture| capture.get_optional_connector_transaction_id().is_none())
            .collect()
    }
}

#[cfg(feature = "v2")]
impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsResponse {
    type Error = TryFromIntError;
    fn foreign_try_from(
        (surcharge_details, payment_attempt): (&SurchargeDetails, &PaymentAttempt),
    ) -> Result<Self, Self::Error> {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsResponse {
    type Error = TryFromIntError;
    fn foreign_try_from(
        (surcharge_details, payment_attempt): (&SurchargeDetails, &PaymentAttempt),
    ) -> Result<Self, Self::Error> {
        let currency = payment_attempt.currency.unwrap_or_default();
        let display_surcharge_amount = currency
            .to_currency_base_unit_asf64(surcharge_details.surcharge_amount.get_amount_as_i64())?;
        let display_tax_on_surcharge_amount = currency.to_currency_base_unit_asf64(
            surcharge_details
                .tax_on_surcharge_amount
                .get_amount_as_i64(),
        )?;
        let display_total_surcharge_amount = currency.to_currency_base_unit_asf64(
            (surcharge_details.surcharge_amount + surcharge_details.tax_on_surcharge_amount)
                .get_amount_as_i64(),
        )?;
        Ok(Self {
            surcharge: surcharge_details.surcharge.clone().into(),
            tax_on_surcharge: surcharge_details.tax_on_surcharge.clone().map(Into::into),
            display_surcharge_amount,
            display_tax_on_surcharge_amount,
            display_total_surcharge_amount,
        })
    }
}

#[derive(Eq, Hash, PartialEq, Clone, Debug, strum::Display)]
pub enum SurchargeKey {
    Token(String),
    PaymentMethodData(
        common_enums::PaymentMethod,
        common_enums::PaymentMethodType,
        Option<common_enums::CardNetwork>,
    ),
}

#[derive(Clone, Debug)]
pub struct SurchargeMetadata {
    surcharge_results: HashMap<SurchargeKey, SurchargeDetails>,
    pub payment_attempt_id: String,
}

impl SurchargeMetadata {
    pub fn new(payment_attempt_id: String) -> Self {
        Self {
            surcharge_results: HashMap::new(),
            payment_attempt_id,
        }
    }
    pub fn is_empty_result(&self) -> bool {
        self.surcharge_results.is_empty()
    }
    pub fn get_surcharge_results_size(&self) -> usize {
        self.surcharge_results.len()
    }
    pub fn insert_surcharge_details(
        &mut self,
        surcharge_key: SurchargeKey,
        surcharge_details: SurchargeDetails,
    ) {
        self.surcharge_results
            .insert(surcharge_key, surcharge_details);
    }
    pub fn get_surcharge_details(&self, surcharge_key: SurchargeKey) -> Option<&SurchargeDetails> {
        self.surcharge_results.get(&surcharge_key)
    }
    pub fn get_surcharge_metadata_redis_key(payment_attempt_id: &str) -> String {
        format!("surcharge_metadata_{payment_attempt_id}")
    }
    pub fn get_individual_surcharge_key_value_pairs(&self) -> Vec<(String, SurchargeDetails)> {
        self.surcharge_results
            .iter()
            .map(|(surcharge_key, surcharge_details)| {
                let key = Self::get_surcharge_details_redis_hashset_key(surcharge_key);
                (key, surcharge_details.to_owned())
            })
            .collect()
    }
    pub fn get_surcharge_details_redis_hashset_key(surcharge_key: &SurchargeKey) -> String {
        match surcharge_key {
            SurchargeKey::Token(token) => {
                format!("token_{token}")
            }
            SurchargeKey::PaymentMethodData(payment_method, payment_method_type, card_network) => {
                if let Some(card_network) = card_network {
                    format!("{payment_method}_{payment_method_type}_{card_network}")
                } else {
                    format!("{payment_method}_{payment_method_type}")
                }
            }
        }
    }
    #[instrument(skip_all)]
    pub async fn persist_individual_surcharge_details_in_redis(
        &self,
        state: &SessionState,
        business_profile: &Profile,
    ) -> RouterResult<()> {
        if !self.is_empty_result() {
            let redis_conn = state
                .store
                .get_redis_conn()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to get redis connection")?;
            let redis_key = Self::get_surcharge_metadata_redis_key(&self.payment_attempt_id);

            let mut value_list = Vec::with_capacity(self.get_surcharge_results_size());
            for (key, value) in self.get_individual_surcharge_key_value_pairs().into_iter() {
                value_list.push((
                    key,
                    value
                        .encode_to_string_of_json()
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed to encode to string of json")?,
                ));
            }
            let intent_fulfillment_time = business_profile
                .get_order_fulfillment_time()
                .unwrap_or(router_consts::DEFAULT_FULFILLMENT_TIME);
            redis_conn
                .set_hash_fields(
                    &redis_key.as_str().into(),
                    value_list,
                    Some(intent_fulfillment_time),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to write to redis")?;
            logger::debug!("Surcharge results stored in redis with key = {}", redis_key);
        }
        Ok(())
    }

    #[instrument(skip_all)]
    pub async fn get_individual_surcharge_detail_from_redis(
        state: &SessionState,
        surcharge_key: SurchargeKey,
        payment_attempt_id: &str,
    ) -> CustomResult<SurchargeDetails, RedisError> {
        let redis_conn = state
            .store
            .get_redis_conn()
            .attach_printable("Failed to get redis connection")?;
        let redis_key = Self::get_surcharge_metadata_redis_key(payment_attempt_id);
        let value_key = Self::get_surcharge_details_redis_hashset_key(&surcharge_key);
        let result = redis_conn
            .get_hash_field_and_deserialize(
                &redis_key.as_str().into(),
                &value_key,
                "SurchargeDetails",
            )
            .await;
        logger::debug!(
            "Surcharge result fetched from redis with key = {} and {}",
            redis_key,
            value_key
        );
        result
    }
}

impl
    ForeignTryFrom<
        &hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore,
    > for AuthenticationData
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(
        authentication_store: &hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore,
    ) -> Result<Self, Self::Error> {
        let authentication = &authentication_store.authentication;
        if authentication.authentication_status == common_enums::AuthenticationStatus::Success {
            let threeds_server_transaction_id =
                authentication.threeds_server_transaction_id.clone();
            let message_version = authentication.message_version.clone();
            let cavv = authentication_store
                .cavv
                .clone()
                .get_required_value("cavv")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("cavv must not be null when authentication_status is success")?;
            Ok(Self {
                eci: authentication.eci.clone(),
                created_at: authentication.created_at,
                cavv,
                threeds_server_transaction_id,
                message_version,
                ds_trans_id: authentication.ds_trans_id.clone(),
                authentication_type: authentication.authentication_type,
                challenge_code: authentication.challenge_code.clone(),
                challenge_cancel: authentication.challenge_cancel.clone(),
                challenge_code_reason: authentication.challenge_code_reason.clone(),
                message_extension: authentication.message_extension.clone(),
                acs_trans_id: authentication.acs_trans_id.clone(),
            })
        } else {
            Err(errors::ApiErrorResponse::PaymentAuthenticationFailed { data: None }.into())
        }
    }
}


// Implementation: impl Verify
// File: crates/router/src/routes/app.rs
// Module: router
// Methods: 1 total (1 public)

impl Verify


// Function: populate_bin_details_for_masked_card
// File: crates/payment_methods/src/core/migration/payment_methods.rs
// Module: payment_methods

pub fn populate_bin_details_for_masked_card(
    card_details: &api_models::payment_methods::MigrateCardDetail,
    db: &dyn state::PaymentMethodsStorageInterface,
    payment_method_type: Option<&enums::PaymentMethodType>,
) -> CustomResult<pm_api::CardDetailFromLocker, errors::ApiErrorResponse>


// File: crates/router/src/core/payments/customers.rs
// Module: router
// Public functions: 5

use common_utils::pii;
use hyperswitch_interfaces::api::ConnectorSpecifications;
use masking::ExposeOptionInterface;
use router_env::{instrument, tracing};

use crate::{
    core::{
        errors::{ConnectorErrorExt, RouterResult},
        payments,
    },
    logger,
    routes::{metrics, SessionState},
    services,
    types::{self, api, domain, storage},
};

#[instrument(skip_all)]
pub async fn create_connector_customer<F: Clone, T: Clone>(
    state: &SessionState,
    connector: &api::ConnectorData,
    router_data: &types::RouterData<F, T, types::PaymentsResponseData>,
    customer_request_data: types::ConnectorCustomerData,
) -> RouterResult<Option<String>> {
    let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
        api::CreateConnectorCustomer,
        types::ConnectorCustomerData,
        types::PaymentsResponseData,
    > = connector.connector.get_connector_integration();

    let customer_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
        Err(types::ErrorResponse::default());

    let customer_router_data = payments::helpers::router_data_type_conversion::<
        _,
        api::CreateConnectorCustomer,
        _,
        _,
        _,
        _,
    >(
        router_data.clone(),
        customer_request_data,
        customer_response_data,
    );

    let resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &customer_router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payment_failed_response()?;

    metrics::CONNECTOR_CUSTOMER_CREATE.add(
        1,
        router_env::metric_attributes!(("connector", connector.connector_name.to_string())),
    );

    let connector_customer_id = match resp.response {
        Ok(response) => match response {
            types::PaymentsResponseData::ConnectorCustomerResponse(customer_data) => {
                Some(customer_data.connector_customer_id)
            }
            _ => None,
        },
        Err(err) => {
            logger::error!(create_connector_customer_error=?err);
            None
        }
    };

    Ok(connector_customer_id)
}

#[cfg(feature = "v1")]
pub fn should_call_connector_create_customer<'a>(
    connector: &api::ConnectorData,
    customer: &'a Option<domain::Customer>,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    connector_label: &str,
) -> (bool, Option<&'a str>) {
    // Check if create customer is required for the connector
    let connector_needs_customer = connector
        .connector
        .should_call_connector_customer(payment_attempt);
    let connector_customer_details = customer
        .as_ref()
        .and_then(|customer| customer.get_connector_customer_id(connector_label));
    if connector_needs_customer {
        let should_call_connector = connector_customer_details.is_none();
        (should_call_connector, connector_customer_details)
    } else {
        // Populates connector_customer_id if it is present after data migration
        // For connector which does not have create connector customer flow
        (false, connector_customer_details)
    }
}

#[cfg(feature = "v2")]
pub fn should_call_connector_create_customer<'a>(
    connector: &api::ConnectorData,
    customer: &'a Option<domain::Customer>,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
) -> (bool, Option<&'a str>) {
    // Check if create customer is required for the connector
    match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(_) => {
            let connector_needs_customer = connector
                .connector
                .should_call_connector_customer(payment_attempt);

            if connector_needs_customer {
                let connector_customer_details = customer
                    .as_ref()
                    .and_then(|cust| cust.get_connector_customer_id(merchant_connector_account));
                let should_call_connector = connector_customer_details.is_none();
                (should_call_connector, connector_customer_details)
            } else {
                (false, None)
            }
        }

        // TODO: Construct connector_customer for MerchantConnectorDetails if required by connector.
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            todo!("Handle connector_customer construction for MerchantConnectorDetails");
        }
    }
}

#[cfg(feature = "v1")]
#[instrument]
pub async fn update_connector_customer_in_customers(
    connector_label: &str,
    customer: Option<&domain::Customer>,
    connector_customer_id: Option<String>,
) -> Option<storage::CustomerUpdate> {
    let mut connector_customer_map = customer
        .and_then(|customer| customer.connector_customer.clone().expose_option())
        .and_then(|connector_customer| connector_customer.as_object().cloned())
        .unwrap_or_default();

    let updated_connector_customer_map = connector_customer_id.map(|connector_customer_id| {
        let connector_customer_value = serde_json::Value::String(connector_customer_id);
        connector_customer_map.insert(connector_label.to_string(), connector_customer_value);
        connector_customer_map
    });

    updated_connector_customer_map
        .map(serde_json::Value::Object)
        .map(
            |connector_customer_value| storage::CustomerUpdate::ConnectorCustomer {
                connector_customer: Some(pii::SecretSerdeValue::new(connector_customer_value)),
            },
        )
}

#[cfg(feature = "v2")]
#[instrument]
pub async fn update_connector_customer_in_customers(
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    customer: Option<&domain::Customer>,
    connector_customer_id: Option<String>,
) -> Option<storage::CustomerUpdate> {
    match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(account) => {
            connector_customer_id.map(|new_conn_cust_id| {
                let connector_account_id = account.get_id().clone();
                let mut connector_customer_map = customer
                    .and_then(|customer| customer.connector_customer.clone())
                    .unwrap_or_default();
                connector_customer_map.insert(connector_account_id, new_conn_cust_id);
                storage::CustomerUpdate::ConnectorCustomer {
                    connector_customer: Some(connector_customer_map),
                }
            })
        }
        // TODO: Construct connector_customer for MerchantConnectorDetails if required by connector.
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            todo!("Handle connector_customer construction for MerchantConnectorDetails");
        }
    }
}


// File: crates/router/src/types/domain/address.rs
// Module: router
// Public structs: 3

use async_trait::async_trait;
use common_utils::{
    crypto::{self, Encryptable},
    date_time,
    encryption::Encryption,
    errors::{CustomResult, ValidationError},
    id_type, pii, type_name,
    types::keymanager::{Identifier, KeyManagerState, ToEncryptable},
};
use diesel_models::{address::AddressUpdateInternal, enums};
use error_stack::ResultExt;
use masking::{PeekInterface, Secret, SwitchStrategy};
use rustc_hash::FxHashMap;
use time::{OffsetDateTime, PrimitiveDateTime};

use super::{behaviour, types};

#[derive(Clone, Debug, serde::Serialize, router_derive::ToEncryption)]
pub struct Address {
    pub address_id: String,
    pub city: Option<String>,
    pub country: Option<enums::CountryAlpha2>,
    #[encrypt]
    pub line1: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub line2: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub line3: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub state: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub zip: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub first_name: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub last_name: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub phone_number: Option<Encryptable<Secret<String>>>,
    pub country_code: Option<String>,
    #[serde(skip_serializing)]
    #[serde(with = "custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    #[serde(skip_serializing)]
    #[serde(with = "custom_serde::iso8601")]
    pub modified_at: PrimitiveDateTime,
    pub merchant_id: id_type::MerchantId,
    pub updated_by: String,
    #[encrypt]
    pub email: Option<Encryptable<Secret<String, pii::EmailStrategy>>>,
    #[encrypt]
    pub origin_zip: Option<Encryptable<Secret<String>>>,
}

/// Based on the flow, appropriate address has to be used
/// In case of Payments, The `PaymentAddress`[PaymentAddress] has to be used
/// which contains only the `Address`[Address] object and `payment_id` and optional `customer_id`
#[derive(Debug, Clone)]
pub struct PaymentAddress {
    pub address: Address,
    pub payment_id: id_type::PaymentId,
    // This is present in `PaymentAddress` because even `payouts` uses `PaymentAddress`
    pub customer_id: Option<id_type::CustomerId>,
}

#[derive(Debug, Clone)]
pub struct CustomerAddress {
    pub address: Address,
    pub customer_id: id_type::CustomerId,
}

#[async_trait]
impl behaviour::Conversion for CustomerAddress {
    type DstType = diesel_models::address::Address;
    type NewDstType = diesel_models::address::AddressNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let converted_address = Address::convert(self.address).await?;
        Ok(diesel_models::address::Address {
            customer_id: Some(self.customer_id),
            payment_id: None,
            ..converted_address
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        other: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError> {
        let customer_id =
            other
                .customer_id
                .clone()
                .ok_or(ValidationError::MissingRequiredField {
                    field_name: "customer_id".to_string(),
                })?;

        let address = Address::convert_back(state, other, key, key_manager_identifier).await?;

        Ok(Self {
            address,
            customer_id,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let address_new = Address::construct_new(self.address).await?;

        Ok(Self::NewDstType {
            customer_id: Some(self.customer_id),
            payment_id: None,
            ..address_new
        })
    }
}
#[async_trait]
impl behaviour::Conversion for PaymentAddress {
    type DstType = diesel_models::address::Address;
    type NewDstType = diesel_models::address::AddressNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let converted_address = Address::convert(self.address).await?;
        Ok(diesel_models::address::Address {
            customer_id: self.customer_id,
            payment_id: Some(self.payment_id),
            ..converted_address
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        other: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError> {
        let payment_id = other
            .payment_id
            .clone()
            .ok_or(ValidationError::MissingRequiredField {
                field_name: "payment_id".to_string(),
            })?;

        let customer_id = other.customer_id.clone();

        let address = Address::convert_back(state, other, key, key_manager_identifier).await?;

        Ok(Self {
            address,
            payment_id,
            customer_id,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let address_new = Address::construct_new(self.address).await?;

        Ok(Self::NewDstType {
            customer_id: self.customer_id,
            payment_id: Some(self.payment_id),
            ..address_new
        })
    }
}

#[async_trait]
impl behaviour::Conversion for Address {
    type DstType = diesel_models::address::Address;
    type NewDstType = diesel_models::address::AddressNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::address::Address {
            address_id: self.address_id,
            city: self.city,
            country: self.country,
            line1: self.line1.map(Encryption::from),
            line2: self.line2.map(Encryption::from),
            line3: self.line3.map(Encryption::from),
            state: self.state.map(Encryption::from),
            zip: self.zip.map(Encryption::from),
            first_name: self.first_name.map(Encryption::from),
            last_name: self.last_name.map(Encryption::from),
            phone_number: self.phone_number.map(Encryption::from),
            country_code: self.country_code,
            created_at: self.created_at,
            modified_at: self.modified_at,
            merchant_id: self.merchant_id,
            updated_by: self.updated_by,
            email: self.email.map(Encryption::from),
            payment_id: None,
            customer_id: None,
            origin_zip: self.origin_zip.map(Encryption::from),
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        other: Self::DstType,
        key: &Secret<Vec<u8>>,
        _key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError> {
        let identifier = Identifier::Merchant(other.merchant_id.clone());
        let decrypted: FxHashMap<String, Encryptable<Secret<String>>> = types::crypto_operation(
            state,
            type_name!(Self::DstType),
            types::CryptoOperation::BatchDecrypt(EncryptedAddress::to_encryptable(
                EncryptedAddress {
                    line1: other.line1,
                    line2: other.line2,
                    line3: other.line3,
                    state: other.state,
                    zip: other.zip,
                    first_name: other.first_name,
                    last_name: other.last_name,
                    phone_number: other.phone_number,
                    email: other.email,
                    origin_zip: other.origin_zip,
                },
            )),
            identifier.clone(),
            key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting".to_string(),
        })?;
        let encryptable_address = EncryptedAddress::from_encryptable(decrypted).change_context(
            ValidationError::InvalidValue {
                message: "Failed while decrypting".to_string(),
            },
        )?;
        Ok(Self {
            address_id: other.address_id,
            city: other.city,
            country: other.country,
            line1: encryptable_address.line1,
            line2: encryptable_address.line2,
            line3: encryptable_address.line3,
            state: encryptable_address.state,
            zip: encryptable_address.zip,
            first_name: encryptable_address.first_name,
            last_name: encryptable_address.last_name,
            phone_number: encryptable_address.phone_number,
            country_code: other.country_code,
            created_at: other.created_at,
            modified_at: other.modified_at,
            updated_by: other.updated_by,
            merchant_id: other.merchant_id,
            email: encryptable_address.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            origin_zip: encryptable_address.origin_zip,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(Self::NewDstType {
            address_id: self.address_id,
            city: self.city,
            country: self.country,
            line1: self.line1.map(Encryption::from),
            line2: self.line2.map(Encryption::from),
            line3: self.line3.map(Encryption::from),
            state: self.state.map(Encryption::from),
            zip: self.zip.map(Encryption::from),
            first_name: self.first_name.map(Encryption::from),
            last_name: self.last_name.map(Encryption::from),
            phone_number: self.phone_number.map(Encryption::from),
            country_code: self.country_code,
            merchant_id: self.merchant_id,
            created_at: now,
            modified_at: now,
            updated_by: self.updated_by,
            email: self.email.map(Encryption::from),
            customer_id: None,
            payment_id: None,
            origin_zip: self.origin_zip.map(Encryption::from),
        })
    }
}

#[derive(Debug, Clone)]
pub enum AddressUpdate {
    Update {
        city: Option<String>,
        country: Option<enums::CountryAlpha2>,
        line1: crypto::OptionalEncryptableSecretString,
        line2: crypto::OptionalEncryptableSecretString,
        line3: crypto::OptionalEncryptableSecretString,
        state: crypto::OptionalEncryptableSecretString,
        zip: crypto::OptionalEncryptableSecretString,
        first_name: crypto::OptionalEncryptableSecretString,
        last_name: crypto::OptionalEncryptableSecretString,
        phone_number: crypto::OptionalEncryptableSecretString,
        country_code: Option<String>,
        updated_by: String,
        email: crypto::OptionalEncryptableEmail,
        origin_zip: crypto::OptionalEncryptableSecretString,
    },
}

impl From<AddressUpdate> for AddressUpdateInternal {
    fn from(address_update: AddressUpdate) -> Self {
        match address_update {
            AddressUpdate::Update {
                city,
                country,
                line1,
                line2,
                line3,
                state,
                zip,
                first_name,
                last_name,
                phone_number,
                country_code,
                updated_by,
                email,
                origin_zip,
            } => Self {
                city,
                country,
                line1: line1.map(Encryption::from),
                line2: line2.map(Encryption::from),
                line3: line3.map(Encryption::from),
                state: state.map(Encryption::from),
                zip: zip.map(Encryption::from),
                first_name: first_name.map(Encryption::from),
                last_name: last_name.map(Encryption::from),
                phone_number: phone_number.map(Encryption::from),
                country_code,
                modified_at: date_time::convert_to_pdt(OffsetDateTime::now_utc()),
                updated_by,
                email: email.map(Encryption::from),
                origin_zip: origin_zip.map(Encryption::from),
            },
        }
    }
}


// Implementation: impl Adyen
// File: crates/hyperswitch_connectors/src/connectors/adyen.rs
// Module: hyperswitch_connectors
// Methods: 1 total (0 public)

impl Adyen