// File: crates/hyperswitch_connectors/src/connectors/netcetera/netcetera_types.rs
// Module: hyperswitch_connectors
// Public functions: 4
// Public structs: 17

use std::collections::HashMap;

use common_utils::{pii::Email, types::SemanticVersion};
use hyperswitch_domain_models::router_request_types::{
    authentication::MessageCategory, BrowserInformation,
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::ExposeInterface;
use serde::{Deserialize, Serialize};
use unidecode::unidecode;

use crate::utils::{AddressDetailsData as _, PhoneDetailsData as _};

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum SingleOrListElement<T> {
    Single(T),
    List(Vec<T>),
}

impl<T> SingleOrListElement<T> {
    fn get_version_checked(message_version: SemanticVersion, value: T) -> Self {
        if message_version.get_major() >= 2 && message_version.get_minor() >= 3 {
            Self::List(vec![value])
        } else {
            Self::Single(value)
        }
    }
}

impl<T> SingleOrListElement<T> {
    pub fn new_single(value: T) -> Self {
        Self::Single(value)
    }

    pub fn new_list(value: Vec<T>) -> Self {
        Self::List(value)
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum NetceteraDeviceChannel {
    #[serde(rename = "01")]
    AppBased,
    #[serde(rename = "02")]
    Browser,
    #[serde(rename = "03")]
    ThreeDsRequestorInitiated,
}

impl From<api_models::payments::DeviceChannel> for NetceteraDeviceChannel {
    fn from(value: api_models::payments::DeviceChannel) -> Self {
        match value {
            api_models::payments::DeviceChannel::App => Self::AppBased,
            api_models::payments::DeviceChannel::Browser => Self::Browser,
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum NetceteraMessageCategory {
    #[serde(rename = "01")]
    PaymentAuthentication,
    #[serde(rename = "02")]
    NonPaymentAuthentication,
}

impl From<MessageCategory> for NetceteraMessageCategory {
    fn from(value: MessageCategory) -> Self {
        match value {
            MessageCategory::NonPayment => Self::NonPaymentAuthentication,
            MessageCategory::Payment => Self::PaymentAuthentication,
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum ThreeDSMethodCompletionIndicator {
    /// Successfully completed
    Y,
    /// Did not successfully complete
    N,
    /// Unavailable - 3DS Method URL was not present in the PRes message data
    U,
}
impl From<api_models::payments::ThreeDsCompletionIndicator> for ThreeDSMethodCompletionIndicator {
    fn from(value: api_models::payments::ThreeDsCompletionIndicator) -> Self {
        match value {
            api_models::payments::ThreeDsCompletionIndicator::Success => Self::Y,
            api_models::payments::ThreeDsCompletionIndicator::Failure => Self::N,
            api_models::payments::ThreeDsCompletionIndicator::NotAvailable => Self::U,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDSRequestor {
    #[serde(rename = "threeDSRequestorAuthenticationInd")]
    pub three_ds_requestor_authentication_ind: ThreeDSRequestorAuthenticationIndicator,
    /// Format of this field was changed with EMV 3DS 2.3.1 version:
    /// In versions prior to 2.3.1, this field is a single object.
    /// Starting from EMVCo version 2.3.1, this field is now an array of objects. Accepted value length is 1-3 elements.
    ///
    /// This field is optional, but recommended to include.
    #[serde(rename = "threeDSRequestorAuthenticationInfo")]
    pub three_ds_requestor_authentication_info:
        Option<SingleOrListElement<ThreeDSRequestorAuthenticationInformation>>,
    #[serde(rename = "threeDSRequestorChallengeInd")]
    pub three_ds_requestor_challenge_ind:
        Option<SingleOrListElement<ThreeDSRequestorChallengeIndicator>>,
    #[serde(rename = "threeDSRequestorPriorAuthenticationInfo")]
    pub three_ds_requestor_prior_authentication_info:
        Option<SingleOrListElement<ThreeDSRequestorPriorTransactionAuthenticationInformation>>,
    #[serde(rename = "threeDSRequestorDecReqInd")]
    pub three_ds_requestor_dec_req_ind: Option<ThreeDSRequestorDecoupledRequestIndicator>,
    /// Indicates the maximum amount of time that the 3DS Requestor will wait for an ACS to provide the results
    /// of a Decoupled Authentication transaction (in minutes). Valid values are between 1 and 10080.
    ///
    /// The field is optional and if value is not present, the expected action is for the ACS to interpret it as
    /// 10080 minutes (7 days).
    /// Available for supporting EMV 3DS 2.2.0 and later versions.
    ///
    /// Starting from EMV 3DS 2.3.1:
    /// This field is required if threeDSRequestorDecReqInd = Y, F or B.
    #[serde(rename = "threeDSRequestorDecMaxTime")]
    pub three_ds_requestor_dec_max_time: Option<u32>,
    /// External IP address (i.e., the device public IP address) used by the 3DS Requestor App when it connects to the
    /// 3DS Requestor environment. The value length is maximum 45 characters. Accepted values are:
    ///
    /// - IPv4 address is represented in the dotted decimal f. Refer to RFC 791.
    /// - IPv6 address. Refer to RFC 4291.
    ///
    /// This field is required when deviceChannel = 01 (APP) and unless market or regional mandate restricts sending
    /// this information.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    pub app_ip: Option<std::net::IpAddr>,
    /// Indicate if the 3DS Requestor supports the SPC authentication.
    ///
    /// The accepted values are:
    ///
    /// - Y -> Supported
    ///
    /// This field is required if deviceChannel = 02 (BRW) and it is supported by the 3DS Requestor.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    #[serde(rename = "threeDSRequestorSpcSupport")]
    pub three_ds_requestor_spc_support: Option<String>,
    /// Reason that the SPC authentication was not completed.
    /// Accepted value length is 2 characters.
    ///
    /// The accepted values are:
    ///
    /// - 01 -> SPC did not run or did not successfully complete
    /// - 02 -> Cardholder cancels the SPC authentication
    ///
    /// This field is required if deviceChannel = 02 (BRW) and the 3DS Requestor attempts to invoke SPC API and there is an
    /// error.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    pub spc_incomp_ind: Option<String>,
}

/// Indicates the type of Authentication request.
///
/// This data element provides additional information to the ACS to determine the best approach for handling an authentication request.
///
/// This value is used for App-based and Browser flows.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ThreeDSRequestorAuthenticationIndicator {
    #[serde(rename = "01")]
    Payment,
    #[serde(rename = "02")]
    Recurring,
    #[serde(rename = "03")]
    Installment,
    #[serde(rename = "04")]
    AddCard,
    #[serde(rename = "05")]
    MaintainCard,
    #[serde(rename = "06")]
    CardholderVerification,
    #[serde(rename = "07")]
    BillingAgreement,
}

impl ThreeDSRequestor {
    pub fn new(
        app_ip: Option<std::net::IpAddr>,
        psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
        force_3ds_challenge: bool,
        message_version: SemanticVersion,
    ) -> Self {
        // if sca exemption is provided, we need to set the challenge indicator to NoChallengeRequestedTransactionalRiskAnalysis
        let three_ds_requestor_challenge_ind = if force_3ds_challenge {
            Some(SingleOrListElement::get_version_checked(
                message_version,
                ThreeDSRequestorChallengeIndicator::ChallengeRequestedMandate,
            ))
        } else if let Some(common_enums::ScaExemptionType::TransactionRiskAnalysis) =
            psd2_sca_exemption_type
        {
            Some(SingleOrListElement::get_version_checked(
                message_version,
                ThreeDSRequestorChallengeIndicator::NoChallengeRequestedTransactionalRiskAnalysis,
            ))
        } else {
            None
        };

        Self {
            three_ds_requestor_authentication_ind: ThreeDSRequestorAuthenticationIndicator::Payment,
            three_ds_requestor_authentication_info: None,
            three_ds_requestor_challenge_ind,
            three_ds_requestor_prior_authentication_info: None,
            three_ds_requestor_dec_req_ind: None,
            three_ds_requestor_dec_max_time: None,
            app_ip,
            three_ds_requestor_spc_support: None,
            spc_incomp_ind: None,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDSRequestorAuthenticationInformation {
    /// Mechanism used by the Cardholder to authenticate to the 3DS Requestor. Accepted values are:
    /// - 01 -> No 3DS Requestor authentication occurred (i.e. cardholder "logged in" as guest)
    /// - 02 -> Login to the cardholder account at the 3DS Requestor system using 3DS Requestor's own credentials
    /// - 03 -> Login to the cardholder account at the 3DS Requestor system using federated ID
    /// - 04 -> Login to the cardholder account at the 3DS Requestor system using issuer credentials
    /// - 05 -> Login to the cardholder account at the 3DS Requestor system using third-party authentication
    /// - 06 -> Login to the cardholder account at the 3DS Requestor system using FIDO Authenticator.
    ///
    /// The next values are accepted as well if 3DS Server initiates authentication with EMV 3DS 2.2.0 version or greater (required protocol version can be set in ThreeDSServerAuthenticationRequest#preferredProtocolVersion field):
    /// - 07 -> Login to the cardholder account at the 3DS Requestor system using FIDO Authenticator (FIDO assurance data signed).
    /// - 08 -> SRC Assurance Data.
    /// - Additionally, 80-99 can be used for PS-specific values, regardless of protocol version.
    #[serde(rename = "threeDSReqAuthMethod")]
    pub three_ds_req_auth_method: ThreeDSReqAuthMethod,
    /// Date and time converted into UTC of the cardholder authentication. Field is limited to 12 characters and accepted format is YYYYMMDDHHMM
    #[serde(rename = "threeDSReqAuthTimestamp")]
    pub three_ds_req_auth_timestamp: String,
    /// Data that documents and supports a specific authentication process. In the current version of the specification, this data element is not defined in detail, however the intention is that for each 3DS Requestor Authentication Method, this field carry data that the ACS can use to verify the authentication process.
    /// For example, if the 3DS Requestor Authentication Method is:
    ///
    ///  - 03 -> then this element can carry information about the provider of the federated ID and related information
    ///  - 06 -> then this element can carry the FIDO attestation data (incl. the signature)
    ///  - 07 -> then this element can carry FIDO Attestation data with the FIDO assurance data signed.
    ///  - 08 -> then this element can carry the SRC assurance data.
    #[serde(rename = "threeDSReqAuthData")]
    pub three_ds_req_auth_data: Option<String>,
}

/// Indicates whether a challenge is requested for this transaction. For example: For 01-PA, a 3DS Requestor may have
/// concerns about the transaction, and request a challenge. For 02-NPA, a challenge may be necessary when adding a new
/// card to a wallet.
///
/// This field is optional. The accepted values are:
///
///  - 01 -> No preference
///  - 02 -> No challenge requested
///  - 03 -> Challenge requested: 3DS Requestor Preference
///  - 04 -> Challenge requested: Mandate.
///    The next values are accepted as well if 3DS Server initiates authentication with EMV 3DS 2.2.0 version
///    or greater (required protocol version can be set in
///    ThreeDSServerAuthenticationRequest#preferredProtocolVersion field):
///
///  - 05 -> No challenge requested (transactional risk analysis is already performed)
///  - 06 -> No challenge requested (Data share only)
///  - 07 -> No challenge requested (strong consumer authentication is already performed)
///  - 08 -> No challenge requested (utilise whitelist exemption if no challenge required)
///  - 09 -> Challenge requested (whitelist prompt requested if challenge required).
///  - Additionally, 80-99 can be used for PS-specific values, regardless of protocol version.
///
/// If the element is not provided, the expected action is that the ACS would interpret as 01 -> No preference.
///
/// Format of this field was changed with EMV 3DS 2.3.1 version:
/// In versions prior to 2.3.1, this field is a String.
/// Starting from EMVCo version 2.3.1, this field is now an array of objects. Accepted value length is 1-2 elements.
/// When providing two preferences, the 3DS Requestor ensures that they are in preference order and are not
/// conflicting. For example, 02 = No challenge requested and 04 = Challenge requested (Mandate).
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ThreeDSRequestorChallengeIndicator {
    #[serde(rename = "01")]
    NoPreference,
    #[serde(rename = "02")]
    NoChallengeRequested,
    #[serde(rename = "03")]
    ChallengeRequested3DSRequestorPreference,
    #[serde(rename = "04")]
    ChallengeRequestedMandate,
    #[serde(rename = "05")]
    NoChallengeRequestedTransactionalRiskAnalysis,
    #[serde(rename = "06")]
    NoChallengeRequestedDataShareOnly,
    #[serde(rename = "07")]
    NoChallengeRequestedStrongConsumerAuthentication,
    #[serde(rename = "08")]
    NoChallengeRequestedWhitelistExemption,
    #[serde(rename = "09")]
    ChallengeRequestedWhitelistPrompt,
}

/// This field contains information about how the 3DS Requestor authenticated the cardholder as part of a previous 3DS transaction.
/// Format of this field was changed with EMV 3DS 2.3.1 version:
/// In versions prior to 2.3.1, this field is a single object.
/// Starting from EMVCo version 2.3.1, this field is now an array of objects. Accepted value length is 1-3 elements.
///
/// This field is optional, but recommended to include for versions prior to 2.3.1. From 2.3.1,
/// it is required for 3RI in the case of Decoupled Authentication Fallback or for SPC.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDSRequestorPriorTransactionAuthenticationInformation {
    /// This data element provides additional information to the ACS to determine the best
    /// approach for handling a request. The field is limited to 36 characters containing
    /// ACS Transaction ID for a prior authenticated transaction (for example, the first
    /// recurring transaction that was authenticated with the cardholder).
    pub three_ds_req_prior_ref: String,

    /// Mechanism used by the Cardholder to previously authenticate to the 3DS Requestor.
    /// Accepted values for this field are:
    ///    - 01 -> Frictionless authentication occurred by ACS
    ///    - 02 -> Cardholder challenge occurred by ACS
    ///    - 03 -> AVS verified
    ///    - 04 -> Other issuer methods
    ///    - 80-99 -> PS-specific value (dependent on the payment scheme type).
    pub three_ds_req_prior_auth_method: String,

    /// Date and time converted into UTC of the prior authentication. Accepted date
    /// format is YYYYMMDDHHMM.
    pub three_ds_req_prior_auth_timestamp: String,

    /// Data that documents and supports a specific authentication process. In the current
    /// version of the specification this data element is not defined in detail, however
    /// the intention is that for each 3DS Requestor Authentication Method, this field carry
    /// data that the ACS can use to verify the authentication process. In future versions
    /// of the application, these details are expected to be included. Field is limited to
    /// maximum 2048 characters.
    pub three_ds_req_prior_auth_data: String,
}

/// Enum indicating whether the 3DS Requestor requests the ACS to utilize Decoupled Authentication.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ThreeDSRequestorDecoupledRequestIndicator {
    /// Decoupled Authentication is supported and preferred if challenge is necessary.
    Y,
    /// Do not use Decoupled Authentication.
    N,
    /// Decoupled Authentication is supported and is to be used only as a fallback challenge method
    /// if a challenge is necessary (Transaction Status = D in RReq).
    F,
    /// Decoupled Authentication is supported and can be used as a primary or fallback challenge method
    /// if a challenge is necessary (Transaction Status = D in either ARes or RReq).
    B,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum SchemeId {
    Visa,
    Mastercard,
    #[serde(rename = "JCB")]
    Jcb,
    #[serde(rename = "American Express")]
    AmericanExpress,
    Diners,
    // For Cartes Bancaires and UnionPay, it is recommended to send the scheme ID
    #[serde(rename = "CB")]
    CartesBancaires,
    UnionPay,
}

impl TryFrom<common_enums::CardNetwork> for SchemeId {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(network: common_enums::CardNetwork) -> Result<Self, Self::Error> {
        match network {
            common_enums::CardNetwork::Visa => Ok(Self::Visa),
            common_enums::CardNetwork::Mastercard => Ok(Self::Mastercard),
            common_enums::CardNetwork::JCB => Ok(Self::Jcb),
            common_enums::CardNetwork::AmericanExpress => Ok(Self::AmericanExpress),
            common_enums::CardNetwork::DinersClub => Ok(Self::Diners),
            common_enums::CardNetwork::CartesBancaires => Ok(Self::CartesBancaires),
            common_enums::CardNetwork::UnionPay => Ok(Self::UnionPay),
            _ => Err(ConnectorError::RequestEncodingFailedWithReason(
                "Invalid card network".to_string(),
            ))?,
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CardholderAccount {
    /// Indicates the type of account.
    /// This is required if 3DS Requestor is asking Cardholder which Account Type they are using before making
    /// the purchase. This field is required in some markets. Otherwise, it is optional.
    pub acct_type: Option<AccountType>,
    /// Expiry date of the PAN or token supplied to the 3DS Requestor by the Cardholder.
    /// The field has 4 characters in a format YYMM.
    ///
    /// The requirements of the presence of this field are DS specific.
    pub card_expiry_date: Option<masking::Secret<String>>,
    /// This field contains additional information about the Cardholder’s account provided by the 3DS Requestor.
    ///
    /// The field is optional but recommended to include.
    ///
    /// Starting from EMV 3DS 2.3.1, added new field:
    /// - `ch_acc_req_id` -> The 3DS Requestor assigned account identifier of the transacting Cardholder.
    ///   This identifier is a unique representation of the account identifier for the 3DS Requestor and
    ///   is provided as a String.
    pub acct_info: Option<CardHolderAccountInformation>,
    /// Account number that will be used in the authorization request for payment transactions.
    /// May be represented by PAN or token.
    ///
    /// This field is required.
    pub acct_number: cards::CardNumber,
    /// ID for the scheme to which the Cardholder's acctNumber belongs to.
    /// It will be used to identify the Scheme from the 3DS Server configuration.
    ///
    /// This field is optional, but recommended to include.
    /// It should be present when it is not one of the schemes for which scheme resolving regular expressions
    /// are provided in the 3DS Server Configuration Properties. Additionally,
    /// if the schemeId is present in the request and there are card ranges found by multiple schemes, the schemeId will be
    /// used for proper resolving of the versioning data.
    pub scheme_id: Option<SchemeId>,
    /// Additional information about the account optionally provided by the 3DS Requestor.
    ///
    /// This field is limited to 64 characters and it is optional to use.
    #[serde(rename = "acctID")]
    pub acct_id: Option<String>,
    /// Indicates if the transaction was de-tokenized prior to being received by the ACS.
    ///
    /// The boolean value of true is the only valid response for this field when it is present.
    ///
    /// The field is required only if there is a de-tokenization of an Account Number.
    pub pay_token_ind: Option<bool>,
    /// Information about the de-tokenised Payment Token.
    /// Note: Data will be formatted into a JSON object prior to being placed into the EMV Payment Token field of the message.
    ///
    /// This field is optional.
    pub pay_token_info: Option<String>,
    /// Three or four-digit security code printed on the card.
    /// The value is numeric and limited to 3-4 characters.
    ///
    /// This field is required depending on the rules provided by the Directory Server.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    pub card_security_code: Option<masking::Secret<String>>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AccountType {
    #[serde(rename = "01")]
    NotApplicable,
    #[serde(rename = "02")]
    Credit,
    #[serde(rename = "03")]
    Debit,
    #[serde(rename = "80")]
    Jcb,
    /// 81-99 -> PS-specific value (dependent on the payment scheme type).
    #[serde(untagged)]
    PsSpecificValue(String),
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CardHolderAccountInformation {
    /// Length of time that the cardholder has had the account with the 3DS Requestor.
    ///
    /// Accepted values are:
    /// - `01` -> No account
    /// - `02` -> Created during this transaction
    /// - `03` -> Less than 30 days
    /// - `04` -> Between 30 and 60 days
    /// - `05` -> More than 60 days
    pub ch_acc_age_ind: Option<String>,

    /// Date converted into UTC that the cardholder opened the account with the 3DS Requestor.
    ///
    /// Date format = YYYYMMDD.
    pub ch_acc_date: Option<String>,

    /// Length of time since the cardholder’s account information with the 3DS Requestor was
    /// last changed.
    ///
    /// Includes Billing or Shipping address, new payment account, or new user(s) added.
    ///
    /// Accepted values are:
    /// - `01` -> Changed during this transaction
    /// - `02` -> Less than 30 days
    /// - `03` -> 30 - 60 days
    /// - `04` -> More than 60 days
    pub ch_acc_change_ind: Option<String>,

    /// Date converted into UTC that the cardholder’s account with the 3DS Requestor was last changed.
    ///
    /// Including Billing or Shipping address, new payment account, or new user(s) added.
    ///
    /// Date format = YYYYMMDD.
    pub ch_acc_change: Option<String>,

    /// Length of time since the cardholder’s account with the 3DS Requestor had a password change
    /// or account reset.
    ///
    /// The accepted values are:
    /// - `01` -> No change
    /// - `02` -> Changed during this transaction
    /// - `03` -> Less than 30 days
    /// - `04` -> 30 - 60 days
    /// - `05` -> More than 60 days
    pub ch_acc_pw_change_ind: Option<String>,

    /// Date converted into UTC that cardholder’s account with the 3DS Requestor had a password
    /// change or account reset.
    ///
    /// Date format must be YYYYMMDD.
    pub ch_acc_pw_change: Option<String>,

    /// Indicates when the shipping address used for this transaction was first used with the
    /// 3DS Requestor.
    ///
    /// Accepted values are:
    /// - `01` -> This transaction
    /// - `02` -> Less than 30 days
    /// - `03` -> 30 - 60 days
    /// - `04` -> More than 60 days
    pub ship_address_usage_ind: Option<String>,

    /// Date converted into UTC when the shipping address used for this transaction was first
    /// used with the 3DS Requestor.
    ///
    /// Date format must be YYYYMMDD.
    pub ship_address_usage: Option<String>,

    /// Number of transactions (successful and abandoned) for this cardholder account with the
    /// 3DS Requestor across all payment accounts in the previous 24 hours.
    pub txn_activity_day: Option<u32>,

    /// Number of transactions (successful and abandoned) for this cardholder account with the
    /// 3DS Requestor across all payment accounts in the previous year.
    pub txn_activity_year: Option<u32>,

    /// Number of Add Card attempts in the last 24 hours.
    pub provision_attempts_day: Option<u32>,

    /// Number of purchases with this cardholder account during the previous six months.
    pub nb_purchase_account: Option<u32>,

    /// Indicates whether the 3DS Requestor has experienced suspicious activity
    /// (including previous fraud) on the cardholder account.
    ///
    /// Accepted values are:
    /// - `01` -> No suspicious activity has been observed
    /// - `02` -> Suspicious activity has been observed
    pub suspicious_acc_activity: Option<String>,

    /// Indicates if the Cardholder Name on the account is identical to the shipping Name used
    /// for this transaction.
    ///
    /// Accepted values are:
    /// - `01` -> Account Name identical to shipping Name
    /// - `02` -> Account Name different than shipping Name
    pub ship_name_indicator: Option<String>,

    /// Indicates the length of time that the payment account was enrolled in the cardholder’s
    /// account with the 3DS Requester.
    ///
    /// Accepted values are:
    /// - `01` -> No account (guest check-out)
    /// - `02` -> During this transaction
    /// - `03` -> Less than 30 days
    /// - `04` -> 30 - 60 days
    /// - `05` -> More than 60 days
    pub payment_acc_ind: Option<String>,

    /// Date converted into UTC that the payment account was enrolled in the cardholder’s account with
    /// the 3DS Requestor.
    ///
    /// Date format must be YYYYMMDD.
    pub payment_acc_age: Option<String>,

    /// The 3DS Requestor assigned account identifier of the transacting Cardholder.
    ///
    /// This identifier is a unique representation of the account identifier for the 3DS Requestor and
    /// is provided as a String. Accepted value length is maximum 64 characters.
    ///
    /// Added starting from EMV 3DS 2.3.1.
    pub ch_acc_req_id: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[serde_with::skip_serializing_none]
pub struct Cardholder {
    /// Indicates whether the Cardholder Shipping Address and Cardholder Billing Address are the same.
    ///
    /// Accepted values:
    /// - `Y` -> Shipping Address matches Billing Address
    /// - `N` -> Shipping Address does not match Billing Address
    ///
    /// If the field is not set and the shipping and billing addresses are the same, the 3DS Server will set the value to
    /// `Y`. Otherwise, the value will not be changed.
    ///
    /// This field is optional.
    addr_match: Option<String>,

    /// The city of the Cardholder billing address associated with the card used for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    bill_addr_city: Option<String>,

    /// The country of the Cardholder billing address associated with the card used for this purchase.
    ///
    /// This field is limited to 3 characters. This value shall be the ISO 3166-1 numeric country code, except values
    /// from range 901 - 999 which are reserved by ISO.
    ///
    /// The field is required if Cardholder Billing Address State is present and unless market or regional mandate
    /// restricts sending this information.
    bill_addr_country: Option<String>,

    /// First line of the street address or equivalent local portion of the Cardholder billing address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    bill_addr_line1: Option<masking::Secret<String>>,

    /// Second line of the street address or equivalent local portion of the Cardholder billing address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    bill_addr_line2: Option<masking::Secret<String>>,

    /// Third line of the street address or equivalent local portion of the Cardholder billing address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    bill_addr_line3: Option<masking::Secret<String>>,

    /// ZIP or other postal code of the Cardholder billing address associated with the card used for this purchase.
    ///
    /// This field is limited to a maximum of 16 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    bill_addr_post_code: Option<masking::Secret<String>>,

    /// The state or province of the Cardholder billing address associated with the card used for this purchase.
    ///
    /// This field is limited to 3 characters. The value should be the country subdivision code defined in ISO 3166-2.
    ///
    /// This field is required unless State is not applicable for this country and unless market or regional mandate
    /// restricts sending this information.
    bill_addr_state: Option<masking::Secret<String>>,

    /// The email address associated with the account that is either entered by the Cardholder, or is on file with
    /// the 3DS Requestor.
    ///
    /// This field is limited to a maximum of 256 characters and shall meet requirements of Section 3.4 of
    /// IETF RFC 5322.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    email: Option<Email>,

    /// The home phone provided by the Cardholder.
    ///
    /// Refer to ITU-E.164 for additional information on format and length.
    ///
    /// This field is required if available, unless market or regional mandate restricts sending this information.
    home_phone: Option<PhoneNumber>,

    /// The mobile phone provided by the Cardholder.
    ///
    /// Refer to ITU-E.164 for additional information on format and length.
    ///
    /// This field is required if available, unless market or regional mandate restricts sending this information.
    mobile_phone: Option<PhoneNumber>,

    /// The work phone provided by the Cardholder.
    ///
    /// Refer to ITU-E.164 for additional information on format and length.
    ///
    /// This field is required if available, unless market or regional mandate restricts sending this information.
    work_phone: Option<PhoneNumber>,

    /// Name of the Cardholder.
    ///
    /// This field is limited to 2-45 characters.
    ///
    /// This field is required unless market or regional mandate restricts sending this information.
    ///
    /// Starting from EMV 3DS 2.3.1:
    /// This field is limited to 1-45 characters.
    cardholder_name: Option<masking::Secret<String>>,

    /// City portion of the shipping address requested by the Cardholder.
    ///
    /// This field is required unless shipping information is the same as billing information, or market or regional
    /// mandate restricts sending this information.
    ship_addr_city: Option<String>,

    /// Country of the shipping address requested by the Cardholder.
    ///
    /// This field is limited to 3 characters. This value shall be the ISO 3166-1 numeric country code, except values
    /// from range 901 - 999 which are reserved by ISO.
    ///
    /// This field is required if Cardholder Shipping Address State is present and if shipping information are not the same
    /// as billing information. This field can be omitted if market or regional mandate restricts sending this information.
    ship_addr_country: Option<String>,

    /// First line of the street address or equivalent local portion of the shipping address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless shipping information is the same as billing information, or market or regional
    /// mandate restricts sending this information.
    ship_addr_line1: Option<masking::Secret<String>>,

    /// Second line of the street address or equivalent local portion of the shipping address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless shipping information is the same as billing information, or market or regional
    /// mandate restricts sending this information.
    ship_addr_line2: Option<masking::Secret<String>>,

    /// Third line of the street address or equivalent local portion of the shipping address associated with
    /// the card use for this purchase.
    ///
    /// This field is limited to a maximum of 50 characters.
    ///
    /// This field is required unless shipping information is the same as billing information, or market or regional
    /// mandate restricts sending this information.
    ship_addr_line3: Option<masking::Secret<String>>,

    /// ZIP or other postal code of the shipping address associated with the card used for this purchase.
    ///
    /// This field is limited to a maximum of 16 characters.
    ///
    /// This field is required unless shipping information is the same as billing information, or market or regional
    /// mandate restricts sending this information.
    ship_addr_post_code: Option<masking::Secret<String>>,

    /// The state or province of the shipping address associated with the card used for this purchase.
    ///
    /// This field is limited to 3 characters. The value should be the country subdivision code defined in ISO 3166-2.
    ///
    /// This field is required unless shipping information is the same as billing information, or State is not applicable
    /// for this country, or market or regional mandate restricts sending this information.
    ship_addr_state: Option<masking::Secret<String>>,

    /// Tax ID is the Cardholder's tax identification.
    ///
    /// The value is limited to 45 characters.
    ///
    /// This field is required depending on the rules provided by the Directory Server.


// Struct: SmtpServerConfig
// File: crates/external_services/src/email/smtp.rs
// Module: external_services
// Implementations: 1

pub struct SmtpServerConfig


// Struct: RedirectData
// File: crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RedirectData


// Implementation: impl Hash for for PaymentMetricsBucketIdentifier
// File: crates/api_models/src/analytics/payments.rs
// Module: api_models
// Methods: 1 total (0 public)

impl Hash for for PaymentMetricsBucketIdentifier


// Struct: InespayErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct InespayErrorResponse


// Struct: FrmFulfillmentRequest
// File: crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs
// Module: hyperswitch_domain_models
// Implementations: 1
// Traits: ApiEventMetric

pub struct FrmFulfillmentRequest


// File: crates/analytics/src/payments/metrics/sessionized_metrics/debit_routing.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::PaymentMetricRow;
use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(crate) struct DebitRouting;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for DebitRouting
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Sum {
                field: "debit_routing_savings",
                alias: Some("total"),
            })
            .switch()?;
        query_builder.add_select_column("currency").switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        query_builder
            .add_group_by_clause("currency")
            .attach_printable("Error grouping by currency")
            .switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                PaymentDimensions::PaymentStatus,
                storage_enums::AttemptStatus::Charged,
            )
            .switch()?;

        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Struct: OpennodeWebhookDetails
// File: crates/hyperswitch_connectors/src/connectors/opennode/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OpennodeWebhookDetails


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

impl Responder


// Function: get_payment_config_routing_id
// File: crates/common_utils/src/id_type/merchant.rs
// Module: common_utils

// Documentation: get_payment_config_routing_id

pub fn get_payment_config_routing_id(&self) -> String


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

impl api::MandateSetup for for Juspaythreedsserver


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

impl ConnectorValidation for for Coinbase


        // TODO: This has to be generated as the reference id based on the connector configuration
        // Some connectros might not accept accept the global id. This has to be done when generating the reference id
        connector_request_reference_id,
        preprocessing_id: attempt.preprocessing_step_id.clone(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        // TODO: take this based on the env
        test_mode: Some(true),
        payment_method_balance: None,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload,
        connector_mandate_request_reference_id: None,
        authentication_id: None,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
    };

    Ok(router_data)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_cancel_router_data_v2<'a>(
    state: &'a SessionState,
    merchant_account: &domain::MerchantAccount,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    payment_data: &hyperswitch_domain_models::payments::PaymentCancelData<api::Void>,
    request: types::PaymentsCancelData,
    connector_request_reference_id: String,
    customer_id: Option<common_utils::id_type::CustomerId>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<
    RouterDataV2<
        api::Void,
        flow_common_types::PaymentFlowData,
        types::PaymentsCancelData,
        types::PaymentsResponseData,
    >,
> {
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    let payment_cancel_data = flow_common_types::PaymentFlowData {
        merchant_id: merchant_account.get_id().clone(),
        customer_id,
        connector_customer: None,
        payment_id: payment_data
            .payment_attempt
            .payment_id
            .get_string_repr()
            .to_owned(),
        attempt_id: payment_data
            .payment_attempt
            .get_id()
            .get_string_repr()
            .to_owned(),
        status: payment_data.payment_attempt.status,
        payment_method: payment_data.payment_attempt.payment_method_type,
        description: payment_data
            .payment_intent
            .description
            .as_ref()
            .map(|description| description.get_string_repr())
            .map(ToOwned::to_owned),
        address: hyperswitch_domain_models::payment_address::PaymentAddress::default(),
        auth_type: payment_data.payment_attempt.authentication_type,
        connector_meta_data: merchant_connector_account.get_metadata(),
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: payment_data.payment_attempt.preprocessing_step_id.clone(),
        payment_method_balance: None,
        connector_api_version: None,
        connector_request_reference_id,
        test_mode: Some(true),
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        connector_response: None,
        payment_method_status: None,
    };

    let router_data_v2 = RouterDataV2 {
        flow: PhantomData,
        tenant_id: state.tenant.tenant_id.clone(),
        resource_common_data: payment_cancel_data,
        connector_auth_type: auth_type,
        request,
        response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
    };

    Ok(router_data_v2)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_router_data_for_cancel<'a>(
    state: &'a SessionState,
    payment_data: hyperswitch_domain_models::payments::PaymentCancelData<
        hyperswitch_domain_models::router_flow_types::Void,
    >,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    _merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<types::PaymentsCancelRouterData> {
    fp_utils::when(merchant_connector_account.is_disabled(), || {
        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
    })?;

    // TODO: Take Globalid and convert to connector reference id
    let customer_id = customer
        .to_owned()
        .map(|customer| common_utils::id_type::CustomerId::try_from(customer.id.clone()))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Invalid global customer generated, not able to convert to reference id",
        )?;
    let payment_intent = payment_data.get_payment_intent();
    let attempt = payment_data.get_payment_attempt();
    let connector_request_reference_id = payment_data
        .payment_attempt
        .connector_request_reference_id
        .clone()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("connector_request_reference_id not found in payment_attempt")?;

    let request = types::PaymentsCancelData {
        amount: Some(attempt.amount_details.get_net_amount().get_amount_as_i64()),
        currency: Some(payment_intent.amount_details.currency),
        connector_transaction_id: attempt
            .get_connector_payment_id()
            .unwrap_or_default()
            .to_string(),
        cancellation_reason: attempt.cancellation_reason.clone(),
        connector_meta: attempt.connector_metadata.clone().expose_option(),
        browser_info: None,
        metadata: None,
        minor_amount: Some(attempt.amount_details.get_net_amount()),
        webhook_url: None,
        capture_method: Some(payment_intent.capture_method),
    };

    // Construct RouterDataV2 for cancel operation
    let router_data_v2 = construct_cancel_router_data_v2(
        state,
        merchant_context.get_merchant_account(),
        merchant_connector_account,
        &payment_data,
        request,
        connector_request_reference_id.clone(),
        customer_id.clone(),
        header_payload.clone(),
    )
    .await?;

    // Convert RouterDataV2 to old RouterData (v1) using the existing RouterDataConversion trait
    let router_data = flow_common_types::PaymentFlowData::to_old_router_data(router_data_v2)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Cannot construct router data for making the unified connector service call",
        )?;

    Ok(router_data)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_payment_router_data_for_sdk_session<'a>(
    state: &'a SessionState,
    payment_data: hyperswitch_domain_models::payments::PaymentIntentData<api::Session>,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    _merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<types::PaymentsSessionRouterData> {
    fp_utils::when(merchant_connector_account.is_disabled(), || {
        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
    })?;

    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    // TODO: Take Globalid and convert to connector reference id
    let customer_id = customer
        .to_owned()
        .map(|customer| common_utils::id_type::CustomerId::try_from(customer.id.clone()))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Invalid global customer generated, not able to convert to reference id",
        )?;
    let billing_address = payment_data
        .payment_intent
        .billing_address
        .as_ref()
        .map(|billing_address| billing_address.clone().into_inner());
    // fetch email from customer or billing address (fallback)
    let email = customer
        .as_ref()
        .and_then(|customer| customer.email.clone())
        .map(pii::Email::from)
        .or(billing_address
            .as_ref()
            .and_then(|address| address.email.clone()));
    // fetch customer name from customer or billing address (fallback)
    let customer_name = customer
        .as_ref()
        .and_then(|customer| customer.name.clone())
        .map(|name| name.into_inner())
        .or(billing_address.and_then(|address| {
            address
                .address
                .as_ref()
                .and_then(|address_details| address_details.get_optional_full_name())
        }));
    let order_details = payment_data
        .payment_intent
        .order_details
        .clone()
        .map(|order_details| {
            order_details
                .into_iter()
                .map(|order_detail| order_detail.expose())
                .collect()
        });
    let required_amount_type = StringMajorUnitForConnector;

    let apple_pay_amount = required_amount_type
        .convert(
            payment_data.payment_intent.amount_details.order_amount,
            payment_data.payment_intent.amount_details.currency,
        )
        .change_context(errors::ApiErrorResponse::PreconditionFailed {
            message: "Failed to convert amount to string major unit for applePay".to_string(),
        })?;

    let apple_pay_recurring_details = payment_data
        .payment_intent
        .feature_metadata
        .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details)
        .map(|apple_pay_recurring_details| {
            ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount))
        });

    let order_tax_amount = payment_data
        .payment_intent
        .amount_details
        .tax_details
        .clone()
        .and_then(|tax| tax.get_default_tax_amount());

    // TODO: few fields are repeated in both routerdata and request
    let request = types::PaymentsSessionData {
        amount: payment_data
            .payment_intent
            .amount_details
            .order_amount
            .get_amount_as_i64(),
        currency: payment_data.payment_intent.amount_details.currency,
        country: payment_data
            .payment_intent
            .billing_address
            .and_then(|billing_address| {
                billing_address
                    .get_inner()
                    .address
                    .as_ref()
                    .and_then(|address| address.country)
            }),
        // TODO: populate surcharge here
        surcharge_details: None,
        order_details,
        email,
        minor_amount: payment_data.payment_intent.amount_details.order_amount,
        apple_pay_recurring_details,
        customer_name,
        metadata: payment_data.payment_intent.metadata,
        order_tax_amount,
        shipping_cost: payment_data.payment_intent.amount_details.shipping_cost,
    };

    // TODO: evaluate the fields in router data, if they are required or not
    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
        // TODO: evaluate why we need customer id at the connector level. We already have connector customer id.
        customer_id,
        connector: connector_id.to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        // TODO: evaluate why we need payment id at the connector level. We already have connector reference id
        payment_id: payment_data.payment_intent.id.get_string_repr().to_owned(),
        // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id
        attempt_id: "".to_string(),
        status: enums::AttemptStatus::Started,
        payment_method: enums::PaymentMethod::Wallet,
        connector_auth_type: auth_type,
        description: payment_data
            .payment_intent
            .description
            .as_ref()
            .map(|description| description.get_string_repr())
            .map(ToOwned::to_owned),
        // TODO: Create unified address
        address: hyperswitch_domain_models::payment_address::PaymentAddress::default(),
        auth_type: payment_data
            .payment_intent
            .authentication_type
            .unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: None,
        request,
        response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_status: None,
        payment_method_token: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        // TODO: This has to be generated as the reference id based on the connector configuration
        // Some connectros might not accept accept the global id. This has to be done when generating the reference id
        connector_request_reference_id: "".to_string(),
        preprocessing_id: None,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        // TODO: take this based on the env
        test_mode: Some(true),
        payment_method_balance: None,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload,
        connector_mandate_request_reference_id: None,
        psd2_sca_exemption_type: None,
        authentication_id: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
    };

    Ok(router_data)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_payment_router_data_for_setup_mandate<'a>(
    state: &'a SessionState,
    payment_data: hyperswitch_domain_models::payments::PaymentConfirmData<api::SetupMandate>,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    _merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<types::SetupMandateRouterData> {
    fp_utils::when(merchant_connector_account.is_disabled(), || {
        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
    })?;

    let auth_type = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    // TODO: Take Globalid and convert to connector reference id
    let customer_id = customer
        .to_owned()
        .map(|customer| common_utils::id_type::CustomerId::try_from(customer.id.clone()))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Invalid global customer generated, not able to convert to reference id",
        )?;

    let connector_customer_id = customer.as_ref().and_then(|customer| {
        customer
            .get_connector_customer_id(merchant_connector_account)
            .map(String::from)
    });

    let payment_method = payment_data.payment_attempt.payment_method_type;

    let router_base_url = &state.base_url;
    let attempt = &payment_data.payment_attempt;

    let complete_authorize_url = Some(helpers::create_complete_authorize_url(
        router_base_url,
        attempt,
        connector_id,
        None,
    ));

    let webhook_url = match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(
            merchant_connector_account,
        ) => Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account.get_id().get_string_repr(),
        )),
        // TODO: Implement for connectors that require a webhook URL to be included in the request payload.
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            todo!("Add webhook URL to request for this connector")
        }
    };

    let router_return_url = payment_data
        .payment_intent
        .create_finish_redirection_url(
            router_base_url,
            merchant_context
                .get_merchant_account()
                .publishable_key
                .as_ref(),
        )
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to construct finish redirection url")?
        .to_string();

    let connector_request_reference_id = payment_data
        .payment_attempt
        .connector_request_reference_id
        .clone()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("connector_request_reference_id not found in payment_attempt")?;

    let email = customer
        .as_ref()
        .and_then(|customer| customer.email.clone())
        .map(pii::Email::from);

    let browser_info = payment_data
        .payment_attempt
        .browser_info
        .clone()
        .map(types::BrowserInformation::from);

    // TODO: few fields are repeated in both routerdata and request
    let request = types::SetupMandateRequestData {
        currency: payment_data.payment_intent.amount_details.currency,
        payment_method_data: payment_data
            .payment_method_data
            .get_required_value("payment_method_data")?,
        amount: Some(
            payment_data
                .payment_attempt
                .amount_details
                .get_net_amount()
                .get_amount_as_i64(),
        ),
        confirm: true,
        statement_descriptor_suffix: None,
        customer_acceptance: None,
        mandate_id: None,
        setup_future_usage: Some(payment_data.payment_intent.setup_future_usage),
        off_session: None,
        setup_mandate_details: None,
        router_return_url: Some(router_return_url.clone()),
        webhook_url,
        browser_info,
        email,
        customer_name: None,
        return_url: Some(router_return_url),
        payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype),
        request_incremental_authorization: matches!(
            payment_data
                .payment_intent
                .request_incremental_authorization,
            RequestIncrementalAuthorization::True
        ),
        metadata: payment_data.payment_intent.metadata,
        minor_amount: Some(payment_data.payment_attempt.amount_details.get_net_amount()),
        shipping_cost: payment_data.payment_intent.amount_details.shipping_cost,
        capture_method: Some(payment_data.payment_intent.capture_method),
        complete_authorize_url,
        connector_testing_data: None,
        customer_id: None,
        enable_partial_authorization: None,
        payment_channel: None,
        enrolled_for_3ds: true,
        related_transaction_id: None,
        is_stored_credential: None,
    };
    let connector_mandate_request_reference_id = payment_data
        .payment_attempt
        .connector_token_details
        .as_ref()
        .and_then(|detail| detail.get_connector_token_request_reference_id());

    // TODO: evaluate the fields in router data, if they are required or not
    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
        tenant_id: state.tenant.tenant_id.clone(),
        // TODO: evaluate why we need customer id at the connector level. We already have connector customer id.
        customer_id,
        connector: connector_id.to_owned(),
        // TODO: evaluate why we need payment id at the connector level. We already have connector reference id
        payment_id: payment_data
            .payment_attempt
            .payment_id
            .get_string_repr()
            .to_owned(),
        // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id
        attempt_id: payment_data
            .payment_attempt
            .get_id()
            .get_string_repr()
            .to_owned(),
        status: payment_data.payment_attempt.status,
        payment_method,
        connector_auth_type: auth_type,
        description: payment_data
            .payment_intent
            .description
            .as_ref()
            .map(|description| description.get_string_repr())
            .map(ToOwned::to_owned),
        // TODO: Create unified address
        address: payment_data.payment_address.clone(),
        auth_type: payment_data.payment_attempt.authentication_type,
        connector_meta_data: None,
        connector_wallets_details: None,
        request,
        response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_status: None,
        payment_method_token: None,
        connector_customer: connector_customer_id,
        recurring_mandate_payment_data: None,
        // TODO: This has to be generated as the reference id based on the connector configuration
        // Some connectros might not accept accept the global id. This has to be done when generating the reference id
        connector_request_reference_id,
        preprocessing_id: payment_data.payment_attempt.preprocessing_step_id,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        // TODO: take this based on the env
        test_mode: Some(true),
        payment_method_balance: None,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload,
        connector_mandate_request_reference_id,
        authentication_id: None,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
    };

    Ok(router_data)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_payment_router_data<'a, F, T>(
    state: &'a SessionState,
    payment_data: PaymentData<F>,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &helpers::MerchantConnectorAccountType,
    merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<types::RouterData<F, T, types::PaymentsResponseData>>
where
    T: TryFrom<PaymentAdditionalData<'a, F>>,
    types::RouterData<F, T, types::PaymentsResponseData>: Feature<F, T>,
    F: Clone,
    error_stack::Report<errors::ApiErrorResponse>:
        From<<T as TryFrom<PaymentAdditionalData<'a, F>>>::Error>,
{
    let (payment_method, router_data);

    fp_utils::when(merchant_connector_account.is_disabled(), || {
        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
    })?;

    let test_mode = merchant_connector_account.is_test_mode_on();

    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    payment_method = payment_data
        .payment_attempt
        .payment_method
        .or(payment_data.payment_attempt.payment_method)
        .get_required_value("payment_method_type")?;

    let resource_id = match payment_data
        .payment_attempt
        .get_connector_payment_id()
        .map(ToString::to_string)
    {
        Some(id) => types::ResponseId::ConnectorTransactionId(id),
        None => types::ResponseId::NoResponseId,
    };

    // [#44]: why should response be filled during request
    let response = Ok(types::PaymentsResponseData::TransactionResponse {
        resource_id,
        redirection_data: Box::new(None),
        mandate_reference: Box::new(None),
        connector_metadata: None,
        network_txn_id: None,
        connector_response_reference_id: None,
        incremental_authorization_allowed: None,
        charges: None,
    });

    let additional_data = PaymentAdditionalData {
        router_base_url: state.base_url.clone(),
        connector_name: connector_id.to_string(),
        payment_data: payment_data.clone(),
        state,
        customer_data: customer,
    };

    let customer_id = customer.to_owned().map(|customer| customer.customer_id);

    let supported_connector = &state
        .conf
        .multiple_api_version_supported_connectors
        .supported_connectors;
    let connector_enum = api_models::enums::Connector::from_str(connector_id)
        .change_context(errors::ConnectorError::InvalidConnectorName)
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "connector",
        })
        .attach_printable_lazy(|| format!("unable to parse connector name {connector_id:?}"))?;

    let connector_api_version = if supported_connector.contains(&connector_enum) {
        state
            .store
            .find_config_by_key(&format!("connector_api_version_{connector_id}"))
            .await
            .map(|value| value.config)
            .ok()
    } else {
        None
    };

    let apple_pay_flow = payments::decide_apple_pay_flow(
        state,
        payment_data.payment_attempt.payment_method_type,
        Some(merchant_connector_account),
    );

    let unified_address = if let Some(payment_method_info) =
        payment_data.payment_method_info.clone()
    {
        let payment_method_billing = payment_method_info
            .payment_method_billing_address
            .map(|decrypted_data| decrypted_data.into_inner().expose())
            .map(|decrypted_value| decrypted_value.parse_value("payment_method_billing_address"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("unable to parse payment_method_billing_address")?;
        payment_data
            .address
            .clone()
            .unify_with_payment_data_billing(payment_method_billing)
    } else {
        payment_data.address
    };
    let connector_mandate_request_reference_id = payment_data
        .payment_attempt
        .connector_mandate_detail
        .as_ref()
        .and_then(|detail| detail.get_connector_mandate_request_reference_id());
    let order_details = payment_data
        .payment_intent
        .order_details
        .as_ref()
        .map(|order_details| {
            order_details
                .iter()
                .map(|data| {
                    data.to_owned()
                        .parse_value("OrderDetailsWithAmount")
                        .change_context(errors::ApiErrorResponse::InvalidDataValue {
                            field_name: "OrderDetailsWithAmount",
                        })
                        .attach_printable("Unable to parse OrderDetailsWithAmount")
                })
                .collect::<Result<Vec<_>, _>>()
        })
        .transpose()?;
    let l2_l3_data = state.conf.l2_l3_data_config.enabled.then(|| {
        let shipping_address = unified_address.get_shipping();
        let billing_address = unified_address.get_payment_billing();
        let merchant_tax_registration_id = merchant_context
            .get_merchant_account()
            .get_merchant_tax_registration_id();

        types::L2L3Data {
            order_date: payment_data.payment_intent.order_date,
            tax_status: payment_data.payment_intent.tax_status,
            customer_tax_registration_id: customer.as_ref().and_then(|c| {
                c.tax_registration_id
                    .as_ref()
                    .map(|e| e.clone().into_inner())
            }),
            order_details: order_details.clone(),
            discount_amount: payment_data.payment_intent.discount_amount,
            shipping_cost: payment_data.payment_intent.shipping_cost,
            shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax,
            duty_amount: payment_data.payment_intent.duty_amount,
            order_tax_amount: payment_data
                .payment_attempt
                .net_amount
                .get_order_tax_amount(),
            merchant_order_reference_id: payment_data
                .payment_intent
                .merchant_order_reference_id
                .clone(),
            customer_id: payment_data.payment_intent.customer_id.clone(),
            shipping_origin_zip: shipping_address
                .and_then(|addr| addr.address.as_ref())
                .and_then(|details| details.origin_zip.clone()),
            shipping_state: shipping_address
                .as_ref()
                .and_then(|addr| addr.address.as_ref())
                .and_then(|details| details.state.clone()),
            shipping_country: shipping_address
                .as_ref()
                .and_then(|addr| addr.address.as_ref())
                .and_then(|details| details.country),
            shipping_destination_zip: shipping_address
                .as_ref()
                .and_then(|addr| addr.address.as_ref())
                .and_then(|details| details.zip.clone()),
            billing_address_city: billing_address
                .as_ref()
                .and_then(|addr| addr.address.as_ref())
                .and_then(|details| details.city.clone()),
            merchant_tax_registration_id,
        }
    });
    crate::logger::debug!("unified address details {:?}", unified_address);

    router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
        customer_id,
        tenant_id: state.tenant.tenant_id.clone(),
        connector: connector_id.to_owned(),
        payment_id: payment_data
            .payment_attempt
            .payment_id
            .get_string_repr()
            .to_owned(),
        attempt_id: payment_data.payment_attempt.attempt_id.clone(),
        status: payment_data.payment_attempt.status,
        payment_method,
        connector_auth_type: auth_type,
        description: payment_data.payment_intent.description.clone(),
        address: unified_address,
        auth_type: payment_data
            .payment_attempt
            .authentication_type
            .unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        request: T::try_from(additional_data)?,
        response,
        amount_captured: payment_data
            .payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_data.payment_intent.amount_captured,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_status: payment_data.payment_method_info.map(|info| info.status),
        payment_method_token: payment_data
            .pm_token
            .map(|token| types::PaymentMethodToken::Token(Secret::new(token))),
        connector_customer: payment_data.connector_customer_id,
        recurring_mandate_payment_data: payment_data.recurring_mandate_payment_data,
        connector_request_reference_id: core_utils::get_connector_request_reference_id(
            &state.conf,
            merchant_context.get_merchant_account().get_id(),
            &payment_data.payment_intent,
            &payment_data.payment_attempt,
            connector_id,
        )?,
        preprocessing_id: payment_data.payment_attempt.preprocessing_step_id,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        payment_method_balance: None,
        connector_api_version,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow,
        frm_metadata: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: merchant_recipient_data.map(|data| {
            api_models::admin::AdditionalMerchantData::foreign_from(
                types::AdditionalMerchantData::OpenBankingRecipientData(data),
            )
        }),
        header_payload,
        connector_mandate_request_reference_id,
        authentication_id: None,
        psd2_sca_exemption_type: payment_data.payment_intent.psd2_sca_exemption_type,
        raw_connector_response: None,
        is_payment_id_from_merchant: payment_data.payment_intent.is_payment_id_from_merchant,
        l2_l3_data,
        minor_amount_capturable: None,
    };

    Ok(router_data)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_payment_router_data_for_update_metadata<'a>(
    state: &'a SessionState,
    payment_data: PaymentData<api::UpdateMetadata>,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &helpers::MerchantConnectorAccountType,
    merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<
    types::RouterData<
        api::UpdateMetadata,
        types::PaymentsUpdateMetadataData,
        types::PaymentsResponseData,
    >,
> {
    let (payment_method, router_data);

    fp_utils::when(merchant_connector_account.is_disabled(), || {
        Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled)
    })?;

    let test_mode = merchant_connector_account.is_test_mode_on();

    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    payment_method = payment_data
        .payment_attempt
        .payment_method
        .or(payment_data.payment_attempt.payment_method)
        .get_required_value("payment_method_type")?;

    // [#44]: why should response be filled during request
    let response = Err(hyperswitch_domain_models::router_data::ErrorResponse {
        code: "IR_20".to_string(),
        message: "Update metadata is not implemented for this connector".to_string(),
        reason: None,
        status_code: http::StatusCode::BAD_REQUEST.as_u16(),
        attempt_status: None,
        connector_transaction_id: None,
        network_decline_code: None,
        network_advice_code: None,
        network_error_message: None,
        connector_metadata: None,
    });

    let additional_data = PaymentAdditionalData {
        router_base_url: state.base_url.clone(),
        connector_name: connector_id.to_string(),
        payment_data: payment_data.clone(),
        state,
        customer_data: customer,
    };

    let customer_id = customer.to_owned().map(|customer| customer.customer_id);

    let supported_connector = &state
        .conf
        .multiple_api_version_supported_connectors
        .supported_connectors;
    let connector_enum = api_models::enums::Connector::from_str(connector_id)
        .change_context(errors::ConnectorError::InvalidConnectorName)
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "connector",
        })
        .attach_printable_lazy(|| format!("unable to parse connector name {connector_id:?}"))?;

    let connector_api_version = if supported_connector.contains(&connector_enum) {
        state
            .store
            .find_config_by_key(&format!("connector_api_version_{connector_id}"))
            .await
            .map(|value| value.config)
            .ok()
    } else {
        None
    };

    let apple_pay_flow = payments::decide_apple_pay_flow(
        state,
        payment_data.payment_attempt.payment_method_type,
        Some(merchant_connector_account),
    );

    let unified_address = if let Some(payment_method_info) =
        payment_data.payment_method_info.clone()
    {
        let payment_method_billing = payment_method_info
            .payment_method_billing_address
            .map(|decrypted_data| decrypted_data.into_inner().expose())
            .map(|decrypted_value| decrypted_value.parse_value("payment_method_billing_address"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("unable to parse payment_method_billing_address")?;
        payment_data
            .address
            .clone()
            .unify_with_payment_data_billing(payment_method_billing)
    } else {
        payment_data.address
    };
    let connector_mandate_request_reference_id = payment_data
        .payment_attempt
        .connector_mandate_detail
        .as_ref()
        .and_then(|detail| detail.get_connector_mandate_request_reference_id());

    crate::logger::debug!("unified address details {:?}", unified_address);

    router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
        customer_id,
        tenant_id: state.tenant.tenant_id.clone(),
        connector: connector_id.to_owned(),
        payment_id: payment_data
            .payment_attempt
            .payment_id
            .get_string_repr()
            .to_owned(),
        attempt_id: payment_data.payment_attempt.attempt_id.clone(),
        status: payment_data.payment_attempt.status,
        payment_method,
        connector_auth_type: auth_type,
        description: payment_data.payment_intent.description.clone(),
        address: unified_address,
        auth_type: payment_data
            .payment_attempt
            .authentication_type
            .unwrap_or_default(),


// Implementation: impl ConnectorSpecifications for for ConnectorEnum
// File: crates/hyperswitch_interfaces/src/connector_integration_interface.rs
// Module: hyperswitch_interfaces
// Methods: 7 total (0 public)

impl ConnectorSpecifications for for ConnectorEnum


// Struct: ResponseData
// File: crates/hyperswitch_connectors/src/connectors/rapyd/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ResponseData


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

impl api::MandateSetup for for Opayo


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

pub fn call_create_connector_customer_if_required<F, Req, D>(
    state: &SessionState,
    customer: &Option<domain::Customer>,
    merchant_context: &domain::MerchantContext,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    payment_data: &mut D,
) -> RouterResult<Option<storage::CustomerUpdate>>
where
    F: Send + Clone + Sync,
    Req: Send + Sync,

    // To create connector flow specific interface data
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    D: ConstructFlowSpecificData<F, Req, router_types::PaymentsResponseData>,
    RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req> + Send,

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


// Struct: PayPalActionUrlResponse
// File: crates/api_models/src/connector_onboarding.rs
// Module: api_models
// Implementations: 0

pub struct PayPalActionUrlResponse


// Struct: Amount
// File: crates/hyperswitch_connectors/src/connectors/trustpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Amount


// Struct: NmiWebhookEventBody
// File: crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NmiWebhookEventBody


// Implementation: impl api::PaymentAuthorize for for Nmi
// File: crates/hyperswitch_connectors/src/connectors/nmi.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentAuthorize for for Nmi