File: crates/hyperswitch_connectors/src/connectors/netcetera/netcetera_types.rs
 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.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    tax_id: Option<String>,
}


File: crates/analytics/src/payment_intents/metrics/payment_processed_amount.rs

use std::collections::HashSet;

use api_models::analytics::{
    payment_intents::{
        PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier,
    },
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

#[derive(Default)]
pub(super) struct PaymentProcessedAmount;

#[async_trait::async_trait]
impl<T> super::PaymentIntentMetric<T> for PaymentProcessedAmount
where
    T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics,
    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: &[PaymentIntentDimensions],
        auth: &AuthInfo,
        filters: &PaymentIntentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>>
    {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentIntent);

        let mut dimensions = dimensions.to_vec();

        dimensions.push(PaymentIntentDimensions::PaymentIntentStatus);

        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("currency").switch()?;

        query_builder
            .add_select_column(Aggregate::Sum {
                field: "amount",
                alias: Some("total"),
            })
            .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(
                PaymentIntentDimensions::PaymentIntentStatus,
                storage_enums::IntentStatus::Succeeded,
            )
            .switch()?;

        query_builder
            .execute_query::<PaymentIntentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentIntentMetricsBucketIdentifier::new(
                        None,
                        i.currency.as_ref().map(|i| i.0),
                        i.profile_id.clone(),
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        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<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


pub async fn connector_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::MerchantConnectorCreate>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsCreate;
    let payload = json_payload.into_inner();
    let merchant_id = path.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth_data.merchant_account, auth_data.key_store),
            ));
            create_connector(state, req, merchant_context, auth_data.profile_id)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::ProfileConnectorWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentAuthorize for Worldline {}

impl api::PaymentAuthorize for for Worldline


pub async fn get_aggregates_for_refunds(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
    time_range: common_utils::types::TimeRange,
) -> RouterResponse<api_models::refunds::RefundAggregateResponse> {
    let db = state.store.as_ref();
    let refund_status_with_count = db
        .get_refund_status_with_count(
            merchant_context.get_merchant_account().get_id(),
            profile_id_list,
            &time_range,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to find status count")?;
    let mut status_map: HashMap<enums::RefundStatus, i64> =
        refund_status_with_count.into_iter().collect();
    for status in enums::RefundStatus::iter() {
        status_map.entry(status).or_default();
    }

    Ok(services::ApplicationResponse::Json(
        api_models::refunds::RefundAggregateResponse {
            status_with_count: status_map,
        },
    ))
}


pub struct FiservPaymentMethod {
    provider: FiservWallet,
    #[serde(rename = "type")]
    wallet_type: FiservWalletType,
}


pub struct StripePaymentMethodData {
    #[serde(rename = "type")]
    pub stype: StripePaymentMethodType,
    pub billing_details: Option<StripeBillingDetails>,
    #[serde(flatten)]
    pub payment_method_details: Option<StripePaymentMethodDetails>, // enum
    pub metadata: Option<SecretSerdeValue>,
}


macro_rules! lower_metadata {
    ($key:ident, $value:ident) => {
        match $value {
            ast::ValueType::MetadataVariant(md) => {
                Ok(vec![dir::DirValue::$key(types::MetadataValue {
                    key: md.key,
                    value: md.value,
                })])
            }
            _ => Err(AnalysisErrorType::InvalidType {
                key: dir::DirKeyKind::$key.to_string(),
                expected: DataType::MetadataValue,
                got: $value.get_type(),
            }),
        }
    };
}


impl PaymentId {
    /// Get the hash key to be stored in redis
    pub fn get_hash_key_for_kv_store(&self) -> String {
        format!("pi_{}", self.0 .0 .0)
    }

    // This function should be removed once we have a better way to handle mandatory payment id in other flows
    /// Get payment id in the format of irrelevant_payment_id_in_{flow}
    pub fn get_irrelevant_id(flow: &str) -> Self {
        let alphanumeric_id =
            AlphaNumericId::new_unchecked(format!("irrelevant_payment_id_in_{flow}"));
        let id = LengthId::new_unchecked(alphanumeric_id);
        Self(id)
    }

    /// Get the attempt id for the payment id based on the attempt count
    pub fn get_attempt_id(&self, attempt_count: i16) -> String {
        format!("{}_{attempt_count}", self.get_string_repr())
    }

    /// Generate a client id for the payment id
    pub fn generate_client_secret(&self) -> String {
        generate_id_with_default_len(&format!("{}_secret", self.get_string_repr()))
    }

    /// Generate a key for pm_auth
    pub fn get_pm_auth_key(&self) -> String {
        format!("pm_auth_{}", self.get_string_repr())
    }

    /// Get external authentication request poll id
    pub fn get_external_authentication_request_poll_id(&self) -> String {
        format!("external_authentication_{}", self.get_string_repr())
    }

    /// Generate a test payment id with prefix test_
    pub fn generate_test_payment_id_for_sample_data() -> Self {
        let id = generate_id_with_default_len("test");
        let alphanumeric_id = AlphaNumericId::new_unchecked(id);
        let id = LengthId::new_unchecked(alphanumeric_id);
        Self(id)
    }

    /// Wrap a string inside PaymentId
    pub fn wrap(payment_id_string: String) -> CustomResult<Self, ValidationError> {
        Self::try_from(std::borrow::Cow::from(payment_id_string))
    }
}


pub async fn skip_locker_call_and_migrate_payment_method(
    state: &state::PaymentMethodsState,
    req: &pm_api::PaymentMethodMigrate,
    merchant_id: id_type::MerchantId,
    merchant_context: &merchant_context::MerchantContext,
    card: pm_api::CardDetailFromLocker,
    should_require_connector_mandate_details: bool,
    migration_status: &mut migration::RecordMigrationStatusBuilder,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    let db = &*state.store;
    let customer_id = req.customer_id.clone().get_required_value("customer_id")?;

    // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details.
    //if network token data is present, then connector mandate details are not mandatory

    let connector_mandate_details = if should_require_connector_mandate_details {
        let connector_mandate_details_req = req
            .connector_mandate_details
            .clone()
            .and_then(|c| c.payments)
            .clone()
            .get_required_value("connector mandate details")?;

        Some(
            serde_json::to_value(&connector_mandate_details_req)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to parse connector mandate details")?,
        )
    } else {
        req.connector_mandate_details
            .clone()
            .and_then(|c| c.payments)
            .map(|mandate_details_req| {
                serde_json::to_value(&mandate_details_req)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to parse connector mandate details")
            })
            .transpose()?
    };
    let key_manager_state = &state.into();
    let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
        .billing
        .clone()
        .async_map(|billing| {
            create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                billing,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method billing address")?;

    let customer = db
        .find_customer_by_customer_id_merchant_id(
            &state.into(),
            &customer_id,
            &merchant_id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;

    let payment_method_card_details = pm_api::PaymentMethodsData::Card(
        pm_api::CardDetailsPaymentMethod::from((card.clone(), None)),
    );

    let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = Some(
        create_encrypted_data(
            &state.into(),
            merchant_context.get_merchant_key_store(),
            payment_method_card_details,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method card details")?,
    );

    let payment_method_metadata: Option<serde_json::Value> =
        req.metadata.as_ref().map(|data| data.peek()).cloned();

    let network_transaction_id = req.network_transaction_id.clone();

    let payment_method_id = generate_id(consts::ID_LENGTH, "pm");

    let current_time = common_utils::date_time::now();

    let response = db
        .insert_payment_method(
            &state.into(),
            merchant_context.get_merchant_key_store(),
            domain_pm::PaymentMethod {
                customer_id: customer_id.to_owned(),
                merchant_id: merchant_id.to_owned(),
                payment_method_id: payment_method_id.to_string(),
                locker_id: None,
                payment_method: req.payment_method,
                payment_method_type: req.payment_method_type,
                payment_method_issuer: req.payment_method_issuer.clone(),
                scheme: req.card_network.clone().or(card.scheme.clone()),
                metadata: payment_method_metadata.map(Secret::new),
                payment_method_data: payment_method_data_encrypted,
                connector_mandate_details: connector_mandate_details.clone(),
                customer_acceptance: None,
                client_secret: None,
                status: enums::PaymentMethodStatus::Active,
                network_transaction_id: network_transaction_id.clone(),
                payment_method_issuer_code: None,
                accepted_currency: None,
                token: None,
                cardholder_name: None,
                issuer_name: None,
                issuer_country: None,
                payer_country: None,
                is_stored: None,
                swift_code: None,
                direct_debit_token: None,
                created_at: current_time,
                last_modified: current_time,
                last_used_at: current_time,
                payment_method_billing_address,
                updated_by: None,
                version: common_types::consts::API_VERSION,
                network_token_requestor_reference_id: None,
                network_token_locker_id: None,
                network_token_payment_method_data: None,
            },
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to add payment method in db")?;

    logger::debug!("Payment method inserted in db");

    migration_status.network_transaction_id_migrated(
        network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
    );

    migration_status.connector_mandate_details_migrated(
        connector_mandate_details
            .clone()
            .and_then(|val| if val == json!({}) { None } else { Some(true) })
            .or_else(|| {
                req.connector_mandate_details.clone().and_then(|val| {
                    val.payments
                        .and_then(|payin_val| (!payin_val.0.is_empty()).then_some(false))
                })
            }),
    );

    if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
        let _ = controller
            .set_default_payment_method(&merchant_id, &customer_id, payment_method_id.to_owned())
            .await
            .map_err(|error| logger::error!(?error, "Failed to set the payment method as default"));
    }
    Ok(ApplicationResponse::Json(
        pm_api::PaymentMethodResponse::foreign_from((Some(card), response)),
    ))
}


pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMajorUnitForConnector,
        }
    }


pub struct NovalnetPaymentsResponse {
    result: ResultData,
    transaction: Option<NovalnetPaymentsResponseTransactionData>,
}


pub struct PaysafePaymentsResponse {
    pub id: String,
    pub merchant_ref_num: Option<String>,
    pub status: PaysafePaymentStatus,
    pub settlements: Option<Vec<PaysafeSettlementResponse>>,
}


pub struct InvalidFileStorageConfig(&'static str);


File: crates/router/src/compatibility/stripe/payment_intents/types.rs
 Public functions: 1
 Public structs: 20

use std::str::FromStr;

use api_models::payments;
use common_types::payments as common_payments_types;
use common_utils::{
    crypto::Encryptable,
    date_time,
    ext_traits::StringExt,
    id_type,
    pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy},
    types::MinorUnit,
};
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{
    compatibility::stripe::refunds::types as stripe_refunds,
    connector::utils::AddressData,
    consts,
    core::errors,
    pii::{Email, PeekInterface},
    types::{
        api::{admin, enums as api_enums},
        transformers::{ForeignFrom, ForeignTryFrom},
    },
};

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripeBillingDetails {
    pub address: Option<AddressDetails>,
    pub email: Option<Email>,
    pub name: Option<String>,
    pub phone: Option<masking::Secret<String>>,
}

impl From<StripeBillingDetails> for payments::Address {
    fn from(details: StripeBillingDetails) -> Self {
        Self {
            phone: Some(payments::PhoneDetails {
                number: details.phone,
                country_code: details.address.as_ref().and_then(|address| {
                    address.country.as_ref().map(|country| country.to_string())
                }),
            }),
            email: details.email,
            address: details.address.map(|address| payments::AddressDetails {
                city: address.city,
                country: address.country,
                line1: address.line1,
                line2: address.line2,
                zip: address.postal_code,
                state: address.state,
                first_name: None,
                line3: None,
                last_name: None,
                origin_zip: None,
            }),
        }
    }
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripeCard {
    pub number: cards::CardNumber,
    pub exp_month: masking::Secret<String>,
    pub exp_year: masking::Secret<String>,
    pub cvc: masking::Secret<String>,
    pub holder_name: Option<masking::Secret<String>>,
}

// ApplePay wallet param is not available in stripe Docs
#[derive(Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripeWallet {
    ApplePay(payments::ApplePayWalletData),
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripeUpi {
    pub vpa_id: masking::Secret<String, UpiVpaMaskingStrategy>,
}

#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StripePaymentMethodType {
    #[default]
    Card,
    Wallet,
    Upi,
    BankRedirect,
    RealTimePayment,
}

impl From<StripePaymentMethodType> for api_enums::PaymentMethod {
    fn from(item: StripePaymentMethodType) -> Self {
        match item {
            StripePaymentMethodType::Card => Self::Card,
            StripePaymentMethodType::Wallet => Self::Wallet,
            StripePaymentMethodType::Upi => Self::Upi,
            StripePaymentMethodType::BankRedirect => Self::BankRedirect,
            StripePaymentMethodType::RealTimePayment => Self::RealTimePayment,
        }
    }
}

#[derive(Default, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripePaymentMethodData {
    #[serde(rename = "type")]
    pub stype: StripePaymentMethodType,
    pub billing_details: Option<StripeBillingDetails>,
    #[serde(flatten)]
    pub payment_method_details: Option<StripePaymentMethodDetails>, // enum
    pub metadata: Option<SecretSerdeValue>,
}

#[derive(PartialEq, Eq, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripePaymentMethodDetails {
    Card(StripeCard),
    Wallet(StripeWallet),
    Upi(StripeUpi),
}

impl From<StripeCard> for payments::Card {
    fn from(card: StripeCard) -> Self {
        Self {
            card_number: card.number,
            card_exp_month: card.exp_month,
            card_exp_year: card.exp_year,
            card_holder_name: card.holder_name,
            card_cvc: card.cvc,
            card_issuer: None,
            card_network: None,
            bank_code: None,
            card_issuing_country: None,
            card_type: None,
            nick_name: None,
        }
    }
}

impl From<StripeWallet> for payments::WalletData {
    fn from(wallet: StripeWallet) -> Self {
        match wallet {
            StripeWallet::ApplePay(data) => Self::ApplePay(data),
        }
    }
}

impl From<StripeUpi> for payments::UpiData {
    fn from(upi_data: StripeUpi) -> Self {
        Self::UpiCollect(payments::UpiCollectData {
            vpa_id: Some(upi_data.vpa_id),
        })
    }
}

impl From<StripePaymentMethodDetails> for payments::PaymentMethodData {
    fn from(item: StripePaymentMethodDetails) -> Self {
        match item {
            StripePaymentMethodDetails::Card(card) => Self::Card(payments::Card::from(card)),
            StripePaymentMethodDetails::Wallet(wallet) => {
                Self::Wallet(payments::WalletData::from(wallet))
            }
            StripePaymentMethodDetails::Upi(upi) => Self::Upi(payments::UpiData::from(upi)),
        }
    }
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct Shipping {
    pub address: AddressDetails,
    pub name: Option<masking::Secret<String>>,
    pub carrier: Option<String>,
    pub phone: Option<masking::Secret<String>>,
    pub tracking_number: Option<masking::Secret<String>>,
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct AddressDetails {
    pub city: Option<String>,
    pub country: Option<api_enums::CountryAlpha2>,
    pub line1: Option<masking::Secret<String>>,
    pub line2: Option<masking::Secret<String>>,
    pub postal_code: Option<masking::Secret<String>>,
    pub state: Option<masking::Secret<String>>,
}

impl From<Shipping> for payments::Address {
    fn from(details: Shipping) -> Self {
        Self {
            phone: Some(payments::PhoneDetails {
                number: details.phone,
                country_code: details.address.country.map(|country| country.to_string()),
            }),
            email: None,
            address: Some(payments::AddressDetails {
                city: details.address.city,
                country: details.address.country,
                line1: details.address.line1,
                line2: details.address.line2,
                zip: details.address.postal_code,
                state: details.address.state,
                first_name: details.name,
                line3: None,
                last_name: None,
                origin_zip: None,
            }),
        }
    }
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct MandateData {
    pub customer_acceptance: CustomerAcceptance,
    pub mandate_type: Option<StripeMandateType>,
    pub amount: Option<i64>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub start_date: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub end_date: Option<PrimitiveDateTime>,
}

#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct CustomerAcceptance {
    #[serde(rename = "type")]
    pub acceptance_type: Option<AcceptanceType>,
    pub accepted_at: Option<PrimitiveDateTime>,
    pub online: Option<OnlineMandate>,
}

#[derive(Default, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AcceptanceType {
    Online,
    #[default]
    Offline,
}

#[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct OnlineMandate {
    pub ip_address: masking::Secret<String, IpAddress>,
    pub user_agent: String,
}

#[derive(Deserialize, Clone, Debug)]
pub struct StripePaymentIntentRequest {
    pub id: Option<id_type::PaymentId>,
    pub amount: Option<i64>, // amount in cents, hence passed as integer
    pub connector: Option<Vec<api_enums::RoutableConnectors>>,
    pub currency: Option<String>,
    #[serde(rename = "amount_to_capture")]
    pub amount_capturable: Option<i64>,
    pub confirm: Option<bool>,
    pub capture_method: Option<api_enums::CaptureMethod>,
    pub customer: Option<id_type::CustomerId>,
    pub description: Option<String>,
    pub payment_method_data: Option<StripePaymentMethodData>,
    pub receipt_email: Option<Email>,
    pub return_url: Option<url::Url>,
    pub setup_future_usage: Option<api_enums::FutureUsage>,
    pub shipping: Option<Shipping>,
    pub statement_descriptor: Option<String>,
    pub statement_descriptor_suffix: Option<String>,
    pub metadata: Option<serde_json::Value>,
    pub client_secret: Option<masking::Secret<String>>,
    pub payment_method_options: Option<StripePaymentMethodOptions>,
    pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
    pub mandate: Option<String>,
    pub off_session: Option<bool>,
    pub payment_method_types: Option<api_enums::PaymentMethodType>,
    pub receipt_ipaddress: Option<String>,
    pub user_agent: Option<String>,
    pub mandate_data: Option<MandateData>,
    pub automatic_payment_methods: Option<SecretSerdeValue>, // not used
    pub payment_method: Option<String>,                      // not used
    pub confirmation_method: Option<String>,                 // not used
    pub error_on_requires_action: Option<String>,            // not used
    pub radar_options: Option<SecretSerdeValue>,             // not used
    pub connector_metadata: Option<payments::ConnectorMetadata>,
}

impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn try_from(item: StripePaymentIntentRequest) -> errors::RouterResult<Self> {
        let routable_connector: Option<api_enums::RoutableConnectors> =
            item.connector.and_then(|v| v.into_iter().next());

        let routing = routable_connector
            .map(|connector| {
                api_models::routing::StaticRoutingAlgorithm::Single(Box::new(
                    api_models::routing::RoutableConnectorChoice {
                        choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
                        connector,
                        merchant_connector_id: None,
                    },
                ))
            })
            .map(|r| {
                serde_json::to_value(r)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("converting to routing failed")
            })
            .transpose()?;

        let ip_address = item
            .receipt_ipaddress
            .map(|ip| std::net::IpAddr::from_str(ip.as_str()))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                field_name: "receipt_ipaddress".to_string(),
                expected_format: "127.0.0.1".to_string(),
            })?;

        let amount = item.amount.map(|amount| MinorUnit::new(amount).into());

        let payment_method_data = item.payment_method_data.as_ref().map(|pmd| {
            let billing = pmd.billing_details.clone().map(payments::Address::from);
            let payment_method_data = match pmd.payment_method_details.as_ref() {
                Some(spmd) => Some(payments::PaymentMethodData::from(spmd.to_owned())),
                None => get_pmd_based_on_payment_method_type(
                    item.payment_method_types,
                    billing.clone().map(From::from),
                ),
            };

            payments::PaymentMethodDataRequest {
                payment_method_data,
                billing,
            }
        });

        let request = Ok(Self {
            payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId),
            amount,
            currency: item
                .currency
                .as_ref()
                .map(|c| c.to_uppercase().parse_enum("currency"))
                .transpose()
                .change_context(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "currency",
                })?,
            capture_method: item.capture_method,
            amount_to_capture: item.amount_capturable.map(MinorUnit::new),
            confirm: item.confirm,
            customer_id: item.customer,
            email: item.receipt_email,
            phone: item.shipping.as_ref().and_then(|s| s.phone.clone()),
            description: item.description,
            return_url: item.return_url,
            payment_method_data,
            payment_method: item
                .payment_method_data
                .as_ref()
                .map(|pmd| api_enums::PaymentMethod::from(pmd.stype.to_owned())),
            shipping: item
                .shipping
                .as_ref()
                .map(|s| payments::Address::from(s.to_owned())),
            billing: item
                .payment_method_data
                .and_then(|pmd| pmd.billing_details.map(payments::Address::from)),
            statement_descriptor_name: item.statement_descriptor,
            statement_descriptor_suffix: item.statement_descriptor_suffix,
            metadata: item.metadata,
            client_secret: item.client_secret.map(|s| s.peek().clone()),
            authentication_type: match item.payment_method_options {
                Some(pmo) => {
                    let StripePaymentMethodOptions::Card {
                        request_three_d_secure,
                    }: StripePaymentMethodOptions = pmo;
                    Some(api_enums::AuthenticationType::foreign_from(
                        request_three_d_secure,
                    ))
                }
                None => None,
            },
            mandate_data: ForeignTryFrom::foreign_try_from((
                item.mandate_data,
                item.currency.to_owned(),
            ))?,
            merchant_connector_details: item.merchant_connector_details,
            setup_future_usage: item.setup_future_usage,
            mandate_id: item.mandate,
            off_session: item.off_session,
            payment_method_type: item.payment_method_types,
            routing,
            browser_info: Some(
                serde_json::to_value(crate::types::BrowserInformation {
                    ip_address,
                    user_agent: item.user_agent,
                    ..Default::default()
                })
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("convert to browser info failed")?,
            ),
            connector_metadata: item.connector_metadata,
            ..Self::default()
        });
        request
    }
}

#[derive(Clone, Default, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripePaymentStatus {
    Succeeded,
    Canceled,
    #[default]
    Processing,
    RequiresAction,
    RequiresPaymentMethod,
    RequiresConfirmation,
    RequiresCapture,
}

impl From<api_enums::IntentStatus> for StripePaymentStatus {
    fn from(item: api_enums::IntentStatus) -> Self {
        match item {
            api_enums::IntentStatus::Succeeded | api_enums::IntentStatus::PartiallyCaptured => {
                Self::Succeeded
            }
            api_enums::IntentStatus::Failed | api_enums::IntentStatus::Expired => Self::Canceled,
            api_enums::IntentStatus::Processing => Self::Processing,
            api_enums::IntentStatus::RequiresCustomerAction
            | api_enums::IntentStatus::RequiresMerchantAction
            | api_enums::IntentStatus::Conflicted => Self::RequiresAction,
            api_enums::IntentStatus::RequiresPaymentMethod => Self::RequiresPaymentMethod,
            api_enums::IntentStatus::RequiresConfirmation => Self::RequiresConfirmation,
            api_enums::IntentStatus::RequiresCapture
            | api_enums::IntentStatus::PartiallyCapturedAndCapturable
            | api_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture => {
                Self::RequiresCapture
            }
            api_enums::IntentStatus::Cancelled | api_enums::IntentStatus::CancelledPostCapture => {
                Self::Canceled
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Copy, Clone, strum::Display)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum CancellationReason {
    Duplicate,
    Fraudulent,
    RequestedByCustomer,
    Abandoned,
}

#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
pub struct StripePaymentCancelRequest {
    cancellation_reason: Option<CancellationReason>,
}

impl From<StripePaymentCancelRequest> for payments::PaymentsCancelRequest {
    fn from(item: StripePaymentCancelRequest) -> Self {
        Self {
            cancellation_reason: item.cancellation_reason.map(|c| c.to_string()),
            ..Self::default()
        }
    }
}

#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct StripePaymentIntentResponse {
    pub id: id_type::PaymentId,
    pub object: &'static str,
    pub amount: i64,
    pub amount_received: Option<i64>,
    pub amount_capturable: i64,
    pub currency: String,
    pub status: StripePaymentStatus,
    pub client_secret: Option<masking::Secret<String>>,
    pub created: Option<i64>,
    pub customer: Option<id_type::CustomerId>,
    pub refunds: Option<Vec<stripe_refunds::StripeRefundResponse>>,
    pub mandate: Option<String>,
    pub metadata: Option<serde_json::Value>,
    pub charges: Charges,
    pub connector: Option<String>,
    pub description: Option<String>,
    pub mandate_data: Option<payments::MandateData>,
    pub setup_future_usage: Option<api_models::enums::FutureUsage>,
    pub off_session: Option<bool>,
    pub authentication_type: Option<api_models::enums::AuthenticationType>,
    pub next_action: Option<StripeNextAction>,
    pub cancellation_reason: Option<String>,
    pub payment_method: Option<api_models::enums::PaymentMethod>,
    pub payment_method_data: Option<payments::PaymentMethodDataResponse>,
    pub shipping: Option<payments::Address>,
    pub billing: Option<payments::Address>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub capture_on: Option<PrimitiveDateTime>,
    pub payment_token: Option<String>,
    pub email: Option<Email>,
    pub phone: Option<masking::Secret<String>>,
    pub statement_descriptor_suffix: Option<String>,
    pub statement_descriptor_name: Option<String>,
    pub capture_method: Option<api_models::enums::CaptureMethod>,
    pub name: Option<masking::Secret<String>>,
    pub last_payment_error: Option<LastPaymentError>,
    pub connector_transaction_id: Option<String>,
}

#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct LastPaymentError {
    charge: Option<String>,
    code: Option<String>,
    decline_code: Option<String>,
    message: String,
    param: Option<String>,
    payment_method: StripePaymentMethod,
    #[serde(rename = "type")]
    error_type: String,
}

impl From<payments::PaymentsResponse> for StripePaymentIntentResponse {
    fn from(resp: payments::PaymentsResponse) -> Self {
        Self {
            object: "payment_intent",
            id: resp.payment_id,
            status: StripePaymentStatus::from(resp.status),
            amount: resp.amount.get_amount_as_i64(),
            amount_capturable: resp.amount_capturable.get_amount_as_i64(),
            amount_received: resp.amount_received.map(|amt| amt.get_amount_as_i64()),
            connector: resp.connector,
            client_secret: resp.client_secret,
            created: resp.created.map(|t| t.assume_utc().unix_timestamp()),
            currency: resp.currency.to_lowercase(),
            customer: resp.customer_id,
            description: resp.description,
            refunds: resp
                .refunds
                .map(|a| a.into_iter().map(Into::into).collect()),
            mandate: resp.mandate_id,
            mandate_data: resp.mandate_data,
            setup_future_usage: resp.setup_future_usage,
            off_session: resp.off_session,
            capture_on: resp.capture_on,
            capture_method: resp.capture_method,
            payment_method: resp.payment_method,
            payment_method_data: resp
                .payment_method_data
                .and_then(|pmd| pmd.payment_method_data),
            payment_token: resp.payment_token,
            shipping: resp.shipping,
            billing: resp.billing,
            email: resp.email.map(|inner| inner.into()),
            name: resp.name.map(Encryptable::into_inner),
            phone: resp.phone.map(Encryptable::into_inner),
            authentication_type: resp.authentication_type,
            statement_descriptor_name: resp.statement_descriptor_name,
            statement_descriptor_suffix: resp.statement_descriptor_suffix,
            next_action: into_stripe_next_action(resp.next_action, resp.return_url),
            cancellation_reason: resp.cancellation_reason,
            metadata: resp.metadata,
            charges: Charges::new(),
            last_payment_error: resp.error_code.map(|code| LastPaymentError {
                charge: None,
                code: Some(code.to_owned()),
                decline_code: None,
                message: resp
                    .error_message
                    .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
                param: None,
                payment_method: StripePaymentMethod {
                    payment_method_id: "place_holder_id".to_string(),
                    object: "payment_method",
                    card: None,
                    created: u64::try_from(date_time::now().assume_utc().unix_timestamp())
                        .unwrap_or_default(),
                    method_type: "card".to_string(),
                    livemode: false,
                },
                error_type: code,
            }),
            connector_transaction_id: resp.connector_transaction_id,
        }
    }
}

#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct StripePaymentMethod {
    #[serde(rename = "id")]
    payment_method_id: String,
    object: &'static str,
    card: Option<StripeCard>,
    created: u64,
    #[serde(rename = "type")]
    method_type: String,
    livemode: bool,
}

#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct Charges {
    object: &'static str,
    data: Vec<String>,
    has_more: bool,
    total_count: i32,
    url: String,
}

impl Charges {
    pub fn new() -> Self {
        Self {
            object: "list",
            data: vec![],
            has_more: false,
            total_count: 0,
            url: "http://placeholder".to_string(),
        }
    }
}

#[derive(Clone, Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StripePaymentListConstraints {
    pub customer: Option<id_type::CustomerId>,
    pub starting_after: Option<id_type::PaymentId>,
    pub ending_before: Option<id_type::PaymentId>,
    #[serde(default = "default_limit")]
    pub limit: u32,
    pub created: Option<i64>,
    #[serde(rename = "created[lt]")]
    pub created_lt: Option<i64>,
    #[serde(rename = "created[gt]")]
    pub created_gt: Option<i64>,
    #[serde(rename = "created[lte]")]
    pub created_lte: Option<i64>,
    #[serde(rename = "created[gte]")]
    pub created_gte: Option<i64>,
}

fn default_limit() -> u32 {
    10
}

impl TryFrom<StripePaymentListConstraints> for payments::PaymentListConstraints {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn try_from(item: StripePaymentListConstraints) -> Result<Self, Self::Error> {
        Ok(Self {
            customer_id: item.customer,
            starting_after: item.starting_after,
            ending_before: item.ending_before,
            limit: item.limit,
            created: from_timestamp_to_datetime(item.created)?,
            created_lt: from_timestamp_to_datetime(item.created_lt)?,
            created_gt: from_timestamp_to_datetime(item.created_gt)?,
            created_lte: from_timestamp_to_datetime(item.created_lte)?,
            created_gte: from_timestamp_to_datetime(item.created_gte)?,
        })
    }
}

#[inline]
fn from_timestamp_to_datetime(
    time: Option<i64>,
) -> Result<Option<PrimitiveDateTime>, errors::ApiErrorResponse> {
    if let Some(time) = time {
        let time = time::OffsetDateTime::from_unix_timestamp(time).map_err(|_| {
            errors::ApiErrorResponse::InvalidRequestData {
                message: "Error while converting timestamp".to_string(),
            }
        })?;

        Ok(Some(PrimitiveDateTime::new(time.date(), time.time())))
    } else {
        Ok(None)
    }
}

#[derive(Default, Eq, PartialEq, Serialize)]
pub struct StripePaymentIntentListResponse {
    pub object: String,
    pub url: String,
    pub has_more: bool,
    pub data: Vec<StripePaymentIntentResponse>,
}

impl From<payments::PaymentListResponse> for StripePaymentIntentListResponse {
    fn from(it: payments::PaymentListResponse) -> Self {
        Self {
            object: "list".to_string(),
            url: "/v1/payment_intents".to_string(),
            has_more: false,
            data: it.data.into_iter().map(Into::into).collect(),
        }
    }
}

#[derive(PartialEq, Eq, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum StripePaymentMethodOptions {
    Card {
        request_three_d_secure: Option<Request3DS>,
    },
}

#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StripeMandateType {
    SingleUse,
    MultiUse,
}

#[derive(PartialEq, Eq, Clone, Default, Deserialize, Serialize, Debug)]
pub struct MandateOption {
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub accepted_at: Option<PrimitiveDateTime>,
    pub user_agent: Option<String>,
    pub ip_address: Option<masking::Secret<String, IpAddress>>,
    pub mandate_type: Option<StripeMandateType>,
    pub amount: Option<i64>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub start_date: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub end_date: Option<PrimitiveDateTime>,
}

impl ForeignTryFrom<(Option<MandateData>, Option<String>)> for Option<payments::MandateData> {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(
        (mandate_data, currency): (Option<MandateData>, Option<String>),
    ) -> errors::RouterResult<Self> {
        let currency = currency
            .ok_or(
                errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "currency",
                }
                .into(),
            )
            .and_then(|c| {
                c.to_uppercase().parse_enum("currency").change_context(
                    errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "currency",
                    },
                )
            })?;
        let mandate_data = mandate_data.map(|mandate| payments::MandateData {
            mandate_type: match mandate.mandate_type {
                Some(item) => match item {
                    StripeMandateType::SingleUse => Some(payments::MandateType::SingleUse(
                        payments::MandateAmountData {
                            amount: MinorUnit::new(mandate.amount.unwrap_or_default()),
                            currency,
                            start_date: mandate.start_date,
                            end_date: mandate.end_date,
                            metadata: None,
                        },
                    )),
                    StripeMandateType::MultiUse => Some(payments::MandateType::MultiUse(Some(
                        payments::MandateAmountData {
                            amount: MinorUnit::new(mandate.amount.unwrap_or_default()),
                            currency,
                            start_date: mandate.start_date,
                            end_date: mandate.end_date,
                            metadata: None,
                        },
                    ))),
                },
                None => Some(payments::MandateType::MultiUse(Some(
                    payments::MandateAmountData {
                        amount: MinorUnit::new(mandate.amount.unwrap_or_default()),
                        currency,
                        start_date: mandate.start_date,
                        end_date: mandate.end_date,
                        metadata: None,
                    },
                ))),
            },
            customer_acceptance: Some(common_payments_types::CustomerAcceptance {
                acceptance_type: common_payments_types::AcceptanceType::Online,
                accepted_at: mandate.customer_acceptance.accepted_at,
                online: mandate.customer_acceptance.online.map(|online| {
                    common_payments_types::OnlineMandate {
                        ip_address: Some(online.ip_address),
                        user_agent: online.user_agent,
                    }
                }),
            }),
            update_mandate_id: None,
        });
        Ok(mandate_data)
    }
}

#[derive(Default, Eq, PartialEq, Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Request3DS {
    #[default]
    Automatic,
    Any,
}

impl ForeignFrom<Option<Request3DS>> for api_models::enums::AuthenticationType {
    fn foreign_from(item: Option<Request3DS>) -> Self {
        match item.unwrap_or_default() {
            Request3DS::Automatic => Self::NoThreeDs,
            Request3DS::Any => Self::ThreeDs,
        }
    }
}

#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct RedirectUrl {
    pub return_url: Option<String>,
    pub url: Option<String>,
}

#[derive(Eq, PartialEq, serde::Serialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StripeNextAction {
    RedirectToUrl {
        redirect_to_url: RedirectUrl,
    },
    DisplayBankTransferInformation {
        bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData,
    },
    ThirdPartySdkSessionToken {
        session_token: Option<payments::SessionToken>,
    },
    QrCodeInformation {
        image_data_url: Option<url::Url>,
        display_to_timestamp: Option<i64>,
        qr_code_url: Option<url::Url>,
        border_color: Option<String>,
        display_text: Option<String>,
    },
    FetchQrCodeInformation {
        qr_code_fetch_url: url::Url,
    },
    DisplayVoucherInformation {
        voucher_details: payments::VoucherNextStepData,
    },
    WaitScreenInformation {
        display_from_timestamp: i128,
        display_to_timestamp: Option<i128>,
        poll_config: Option<payments::PollConfig>,
    },
    InvokeSdkClient {
        next_action_data: payments::SdkNextActionData,
    },
    CollectOtp {
        consent_data_required: payments::MobilePaymentConsent,
    },
    InvokeHiddenIframe {
        iframe_data: payments::IframeData,
    },
}

pub(crate) fn into_stripe_next_action(
    next_action: Option<payments::NextActionData>,
    return_url: Option<String>,
) -> Option<StripeNextAction> {
    next_action.map(|next_action_data| match next_action_data {
        payments::NextActionData::RedirectToUrl { redirect_to_url } => {
            StripeNextAction::RedirectToUrl {
                redirect_to_url: RedirectUrl {
                    return_url,
                    url: Some(redirect_to_url),
                },
            }
        }
        payments::NextActionData::RedirectInsidePopup { popup_url, .. } => {
            StripeNextAction::RedirectToUrl {
                redirect_to_url: RedirectUrl {
                    return_url,
                    url: Some(popup_url),
                },
            }
        }
        payments::NextActionData::DisplayBankTransferInformation {
            bank_transfer_steps_and_charges_details,
        } => StripeNextAction::DisplayBankTransferInformation {
            bank_transfer_steps_and_charges_details,
        },
        payments::NextActionData::ThirdPartySdkSessionToken { session_token } => {
            StripeNextAction::ThirdPartySdkSessionToken { session_token }
        }

        payments::NextActionData::QrCodeInformation {
            image_data_url,
            display_to_timestamp,
            qr_code_url,
            border_color,
            display_text,
        } => StripeNextAction::QrCodeInformation {
            image_data_url,
            display_to_timestamp,
            qr_code_url,
            border_color,
            display_text,
        },
        payments::NextActionData::FetchQrCodeInformation { qr_code_fetch_url } => {
            StripeNextAction::FetchQrCodeInformation { qr_code_fetch_url }
        }
        payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
            StripeNextAction::DisplayVoucherInformation { voucher_details }
        }
        payments::NextActionData::WaitScreenInformation {
            display_from_timestamp,
            display_to_timestamp,
            poll_config: _,
        } => StripeNextAction::WaitScreenInformation {
            display_from_timestamp,
            display_to_timestamp,
            poll_config: None,
        },
        payments::NextActionData::ThreeDsInvoke { .. } => StripeNextAction::RedirectToUrl {
            redirect_to_url: RedirectUrl {
                return_url: None,
                url: None,
            },
        },
        payments::NextActionData::InvokeSdkClient { next_action_data } => {
            StripeNextAction::InvokeSdkClient { next_action_data }
        }
        payments::NextActionData::CollectOtp {
            consent_data_required,
        } => StripeNextAction::CollectOtp {
            consent_data_required,
        },
        payments::NextActionData::InvokeHiddenIframe { iframe_data } => {
            StripeNextAction::InvokeHiddenIframe { iframe_data }
        }
    })
}

#[derive(Deserialize, Clone)]
pub struct StripePaymentRetrieveBody {
    pub client_secret: Option<String>,
}

//To handle payment types that have empty payment method data
fn get_pmd_based_on_payment_method_type(
    payment_method_type: Option<api_enums::PaymentMethodType>,
    billing_details: Option<hyperswitch_domain_models::address::Address>,
) -> Option<payments::PaymentMethodData> {
    match payment_method_type {
        Some(api_enums::PaymentMethodType::UpiIntent) => Some(payments::PaymentMethodData::Upi(
            payments::UpiData::UpiIntent(payments::UpiIntentData {}),
        )),
        Some(api_enums::PaymentMethodType::Fps) => {
            Some(payments::PaymentMethodData::RealTimePayment(Box::new(
                payments::RealTimePaymentData::Fps {},
            )))
        }
        Some(api_enums::PaymentMethodType::DuitNow) => {
            Some(payments::PaymentMethodData::RealTimePayment(Box::new(
                payments::RealTimePaymentData::DuitNow {},
            )))
        }
        Some(api_enums::PaymentMethodType::PromptPay) => {
            Some(payments::PaymentMethodData::RealTimePayment(Box::new(
                payments::RealTimePaymentData::PromptPay {},
            )))
        }
        Some(api_enums::PaymentMethodType::VietQr) => {
            Some(payments::PaymentMethodData::RealTimePayment(Box::new(
                payments::RealTimePaymentData::VietQr {},
            )))
        }
        Some(api_enums::PaymentMethodType::Ideal) => Some(
            payments::PaymentMethodData::BankRedirect(payments::BankRedirectData::Ideal {
                billing_details: billing_details.as_ref().map(|billing_data| {
                    payments::BankRedirectBilling {
                        billing_name: billing_data.get_optional_full_name(),
                        email: billing_data.email.clone(),
                    }
                }),
                bank_name: None,
                country: billing_details
                    .as_ref()
                    .and_then(|billing_data| billing_data.get_optional_country()),
            }),
        ),
        Some(api_enums::PaymentMethodType::LocalBankRedirect) => {
            Some(payments::PaymentMethodData::BankRedirect(
                payments::BankRedirectData::LocalBankRedirect {},
            ))
        }
        _ => None,
    }
}


pub async fn create_payment_method_proxy_card_core(
    state: &SessionState,
    req: api::PaymentMethodCreate,
    merchant_context: &domain::MerchantContext,
    profile: &domain::Profile,
    merchant_id: &id_type::MerchantId,
    customer_id: &id_type::GlobalCustomerId,
    payment_method_id: id_type::GlobalPaymentMethodId,
    payment_method_billing_address: Option<
        Encryptable<hyperswitch_domain_models::address::Address>,
    >,
) -> RouterResult<(api::PaymentMethodResponse, domain::PaymentMethod)> {
    let key_manager_state = &(state).into();

    let external_vault_source = profile
        .external_vault_connector_details
        .clone()
        .map(|details| details.vault_connector_id);

    let additional_payment_method_data = Some(
        req.payment_method_data
            .populate_bin_details_for_payment_method(state)
            .await
            .convert_to_additional_payment_method_data()?,
    );

    let encrypted_payment_method_data = additional_payment_method_data
        .async_map(|payment_method_data| {
            cards::create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                payment_method_data,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method data")?
        .map(|encoded_pmd| {
            encoded_pmd.deserialize_inner_value(|value| value.parse_value("PaymentMethodsData"))
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to parse Payment method data")?;

    let external_vault_token_data = req.payment_method_data.get_external_vault_token_data();

    let encrypted_external_vault_token_data = external_vault_token_data
        .async_map(|external_vault_token_data| {
            cards::create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                external_vault_token_data,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt External vault token data")?
        .map(|encoded_data| {
            encoded_data
                .deserialize_inner_value(|value| value.parse_value("ExternalVaultTokenData"))
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to parse External vault token data")?;

    let payment_method = create_payment_method_for_confirm(
        state,
        customer_id,
        payment_method_id,
        external_vault_source,
        merchant_id,
        merchant_context.get_merchant_key_store(),
        merchant_context.get_merchant_account().storage_scheme,
        req.payment_method_type,
        req.payment_method_subtype,
        payment_method_billing_address,
        encrypted_payment_method_data,
        encrypted_external_vault_token_data,
    )
    .await?;

    let payment_method_response =
        pm_transforms::generate_payment_method_response(&payment_method, &None)?;

    Ok((payment_method_response, payment_method))
}


pub struct AirwallexThreeDsData {
    acs_response: Option<Secret<String>>,
}


pub struct RetrieveCardRespPayload {
    pub card: Option<Card>,
    pub enc_card_data: Option<Secret<String>>,
}


impl PaymentsResponseData {
    pub fn get_connector_metadata(&self) -> Option<masking::Secret<serde_json::Value>> {
        match self {
            Self::TransactionResponse {
                connector_metadata, ..
            }
            | Self::PreProcessingResponse {
                connector_metadata, ..
            } => connector_metadata.clone().map(masking::Secret::new),
            _ => None,
        }
    }
    pub fn get_network_transaction_id(&self) -> Option<String> {
        match self {
            Self::TransactionResponse { network_txn_id, .. } => network_txn_id.clone(),
            _ => None,
        }
    }

    pub fn get_connector_transaction_id(
        &self,
    ) -> Result<String, error_stack::Report<ApiErrorResponse>> {
        match self {
            Self::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(txn_id),
                ..
            } => Ok(txn_id.to_string()),
            _ => Err(ApiErrorResponse::MissingRequiredField {
                field_name: "ConnectorTransactionId",
            }
            .into()),
        }
    }

    pub fn merge_transaction_responses(
        auth_response: &Self,
        capture_response: &Self,
    ) -> Result<Self, error_stack::Report<ApiErrorResponse>> {
        match (auth_response, capture_response) {
            (
                Self::TransactionResponse {
                    resource_id: _,
                    redirection_data: auth_redirection_data,
                    mandate_reference: auth_mandate_reference,
                    connector_metadata: auth_connector_metadata,
                    network_txn_id: auth_network_txn_id,
                    connector_response_reference_id: auth_connector_response_reference_id,
                    incremental_authorization_allowed: auth_incremental_auth_allowed,
                    charges: auth_charges,
                },
                Self::TransactionResponse {
                    resource_id: capture_resource_id,
                    redirection_data: capture_redirection_data,
                    mandate_reference: capture_mandate_reference,
                    connector_metadata: capture_connector_metadata,
                    network_txn_id: capture_network_txn_id,
                    connector_response_reference_id: capture_connector_response_reference_id,
                    incremental_authorization_allowed: capture_incremental_auth_allowed,
                    charges: capture_charges,
                },
            ) => Ok(Self::TransactionResponse {
                resource_id: capture_resource_id.clone(),
                redirection_data: Box::new(
                    capture_redirection_data
                        .clone()
                        .or_else(|| *auth_redirection_data.clone()),
                ),
                mandate_reference: Box::new(
                    auth_mandate_reference
                        .clone()
                        .or_else(|| *capture_mandate_reference.clone()),
                ),
                connector_metadata: capture_connector_metadata
                    .clone()
                    .or(auth_connector_metadata.clone()),
                network_txn_id: capture_network_txn_id
                    .clone()
                    .or(auth_network_txn_id.clone()),
                connector_response_reference_id: capture_connector_response_reference_id
                    .clone()
                    .or(auth_connector_response_reference_id.clone()),
                incremental_authorization_allowed: (*capture_incremental_auth_allowed)
                    .or(*auth_incremental_auth_allowed),
                charges: auth_charges.clone().or(capture_charges.clone()),
            }),
            _ => Err(ApiErrorResponse::NotSupported {
                message: "Invalid Flow ".to_owned(),
            }
            .into()),
        }
    }

    #[cfg(feature = "v2")]
    pub fn get_updated_connector_token_details(
        &self,
        original_connector_mandate_request_reference_id: Option<String>,
    ) -> Option<diesel_models::ConnectorTokenDetails> {
        if let Self::TransactionResponse {
            mandate_reference, ..
        } = self
        {
            mandate_reference.clone().map(|mandate_ref| {
                let connector_mandate_id = mandate_ref.connector_mandate_id;
                let connector_mandate_request_reference_id = mandate_ref
                    .connector_mandate_request_reference_id
                    .or(original_connector_mandate_request_reference_id);

                diesel_models::ConnectorTokenDetails {
                    connector_mandate_id,
                    connector_token_request_reference_id: connector_mandate_request_reference_id,
                }
            })
        } else {
            None
        }
    }
}


impl ConnectorSpecifications for Bluesnap {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&BLUESNAP_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*BLUESNAP_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&BLUESNAP_SUPPORTED_WEBHOOK_FLOWS)
    }
}

impl ConnectorSpecifications for for Bluesnap


/// Masked serialization.
///
/// the default behaviour for secrets is to serialize in exposed format since the common use cases
/// for storing the secret to database or sending it over the network requires the secret to be exposed
/// This method allows to serialize the secret in masked format if needed for logs or other insecure exposures
pub fn masked_serialize<T: Serialize>(value: &T) -> Result<Value, serde_json::Error> {
    value.serialize(PIISerializer {
        inner: JsonValueSerializer,
    })
}


impl Health {
    pub fn server(state: AppState) -> Scope {
        web::scope("/v2/health")
            .app_data(web::Data::new(state))
            .service(web::resource("").route(web::get().to(health)))
            .service(web::resource("/ready").route(web::get().to(deep_health_check)))
    }
}