// File: crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 2
// Public structs: 31

use api_models::webhooks::IncomingWebhookEvent;
use cards::CardNumber;
use common_enums::{AttemptStatus, AuthenticationType, CountryAlpha2, Currency, RefundStatus};
use common_utils::{errors::CustomResult, ext_traits::XmlExt, pii::Email, types::FloatMajorUnit};
use error_stack::{report, Report, ResultExt};
use hyperswitch_domain_models::{
    payment_method_data::{
        ApplePayWalletData, Card, GooglePayWalletData, PaymentMethodData, WalletData,
    },
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        Authorize, Capture, CompleteAuthorize, Execute, PreProcessing, RSync, SetupMandate, Void,
    },
    router_request_types::{
        CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCaptureData,
        PaymentsPreProcessingData, ResponseId,
    },
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData,
        PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData,
    },
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{PaymentsResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
    utils::{
        get_unimplemented_payment_method_error_message, to_currency_base_unit_asf64,
        AddressDetailsData as _, CardData as _, PaymentsAuthorizeRequestData,
        PaymentsCompleteAuthorizeRequestData as _, RouterData as _,
    },
};

type Error = Report<ConnectorError>;

#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum TransactionType {
    Auth,
    Capture,
    Refund,
    Sale,
    Validate,
    Void,
}

pub struct NmiAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) public_key: Option<Secret<String>>,
}

impl TryFrom<&ConnectorAuthType> for NmiAuthType {
    type Error = Error;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
                public_key: None,
            }),
            ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
                api_key: api_key.to_owned(),
                public_key: Some(key1.to_owned()),
            }),
            _ => Err(ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct NmiRouterData<T> {
    pub amount: FloatMajorUnit,
    pub router_data: T,
}

impl<T> From<(FloatMajorUnit, T)> for NmiRouterData<T> {
    fn from((amount, router_data): (FloatMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct NmiVaultRequest {
    security_key: Secret<String>,
    ccnumber: CardNumber,
    ccexp: Secret<String>,
    cvv: Secret<String>,
    first_name: Secret<String>,
    last_name: Secret<String>,
    address1: Option<Secret<String>>,
    address2: Option<Secret<String>>,
    city: Option<String>,
    state: Option<Secret<String>>,
    zip: Option<Secret<String>>,
    country: Option<CountryAlpha2>,
    customer_vault: CustomerAction,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CustomerAction {
    AddCustomer,
    UpdateCustomer,
}

impl TryFrom<&PaymentsPreProcessingRouterData> for NmiVaultRequest {
    type Error = Error;
    fn try_from(item: &PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
        let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
        let (ccnumber, ccexp, cvv) = get_card_details(item.request.payment_method_data.clone())?;
        let billing_details = item.get_billing_address()?;
        let first_name = billing_details.get_first_name()?;

        Ok(Self {
            security_key: auth_type.api_key,
            ccnumber,
            ccexp,
            cvv,
            first_name: first_name.clone(),
            last_name: billing_details
                .get_last_name()
                .unwrap_or(first_name)
                .clone(),
            address1: billing_details.line1.clone(),
            address2: billing_details.line2.clone(),
            city: billing_details.city.clone(),
            state: billing_details.state.clone(),
            country: billing_details.country,
            zip: billing_details.zip.clone(),
            customer_vault: CustomerAction::AddCustomer,
        })
    }
}

fn get_card_details(
    payment_method_data: Option<PaymentMethodData>,
) -> CustomResult<(CardNumber, Secret<String>, Secret<String>), ConnectorError> {
    match payment_method_data {
        Some(PaymentMethodData::Card(ref card_details)) => Ok((
            card_details.card_number.clone(),
            card_details.get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?,
            card_details.card_cvc.clone(),
        )),
        _ => Err(
            ConnectorError::NotImplemented(get_unimplemented_payment_method_error_message("Nmi"))
                .into(),
        ),
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct NmiVaultResponse {
    pub response: Response,
    pub responsetext: String,
    pub customer_vault_id: Option<Secret<String>>,
    pub response_code: String,
    pub transactionid: String,
}

impl
    TryFrom<
        ResponseRouterData<
            PreProcessing,
            NmiVaultResponse,
            PaymentsPreProcessingData,
            PaymentsResponseData,
        >,
    > for PaymentsPreProcessingRouterData
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<
            PreProcessing,
            NmiVaultResponse,
            PaymentsPreProcessingData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let auth_type: NmiAuthType = (&item.data.connector_auth_type).try_into()?;
        let amount_data = item
            .data
            .request
            .amount
            .ok_or(ConnectorError::MissingRequiredField {
                field_name: "amount",
            })?;
        let currency_data =
            item.data
                .request
                .currency
                .ok_or(ConnectorError::MissingRequiredField {
                    field_name: "currency",
                })?;
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::NoResponseId,
                    redirection_data: Box::new(Some(RedirectForm::Nmi {
                        amount: to_currency_base_unit_asf64(amount_data, currency_data.to_owned())?
                            .to_string(),
                        currency: currency_data,
                        customer_vault_id: item
                            .response
                            .customer_vault_id
                            .ok_or(ConnectorError::MissingRequiredField {
                                field_name: "customer_vault_id",
                            })?
                            .peek()
                            .to_string(),
                        public_key: auth_type.public_key.ok_or(
                            ConnectorError::InvalidConnectorConfig {
                                config: "public_key",
                            },
                        )?,
                        order_id: item.data.connector_request_reference_id.clone(),
                    })),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.transactionid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                AttemptStatus::AuthenticationPending,
            ),
            Response::Declined | Response::Error => (
                Err(ErrorResponse {
                    code: item.response.response_code,
                    message: item.response.responsetext.to_owned(),
                    reason: Some(item.response.responsetext),
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: Some(item.response.transactionid),
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                }),
                AttemptStatus::Failure,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct NmiCompleteRequest {
    amount: FloatMajorUnit,
    #[serde(rename = "type")]
    transaction_type: TransactionType,
    security_key: Secret<String>,
    orderid: Option<String>,
    customer_vault_id: Secret<String>,
    email: Option<Email>,
    cardholder_auth: Option<String>,
    cavv: Option<String>,
    xid: Option<String>,
    eci: Option<String>,
    cvv: Secret<String>,
    three_ds_version: Option<String>,
    directory_server_id: Option<Secret<String>>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NmiRedirectResponse {
    NmiRedirectResponseData(NmiRedirectResponseData),
    NmiErrorResponseData(NmiErrorResponseData),
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NmiErrorResponseData {
    pub code: String,
    pub message: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NmiRedirectResponseData {
    cavv: Option<String>,
    xid: Option<String>,
    eci: Option<String>,
    card_holder_auth: Option<String>,
    three_ds_version: Option<String>,
    order_id: Option<String>,
    directory_server_id: Option<Secret<String>>,
    customer_vault_id: Secret<String>,
}

impl TryFrom<&NmiRouterData<&PaymentsCompleteAuthorizeRouterData>> for NmiCompleteRequest {
    type Error = Error;
    fn try_from(
        item: &NmiRouterData<&PaymentsCompleteAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let transaction_type = match item.router_data.request.is_auto_capture()? {
            true => TransactionType::Sale,
            false => TransactionType::Auth,
        };
        let auth_type: NmiAuthType = (&item.router_data.connector_auth_type).try_into()?;
        let payload_data = item
            .router_data
            .request
            .get_redirect_response_payload()?
            .expose();

        let three_ds_data: NmiRedirectResponseData = serde_json::from_value(payload_data)
            .change_context(ConnectorError::MissingConnectorRedirectionPayload {
                field_name: "three_ds_data",
            })?;

        let (_, _, cvv) = get_card_details(item.router_data.request.payment_method_data.clone())?;

        Ok(Self {
            amount: item.amount,
            transaction_type,
            security_key: auth_type.api_key,
            orderid: three_ds_data.order_id,
            customer_vault_id: three_ds_data.customer_vault_id,
            email: item.router_data.request.email.clone(),
            cvv,
            cardholder_auth: three_ds_data.card_holder_auth,
            cavv: three_ds_data.cavv,
            xid: three_ds_data.xid,
            eci: three_ds_data.eci,
            three_ds_version: three_ds_data.three_ds_version,
            directory_server_id: three_ds_data.directory_server_id,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct NmiCompleteResponse {
    pub response: Response,
    pub responsetext: String,
    pub authcode: Option<String>,
    pub transactionid: String,
    pub avsresponse: Option<String>,
    pub cvvresponse: Option<String>,
    pub orderid: String,
    pub response_code: String,
    customer_vault_id: Option<Secret<String>>,
}

impl
    TryFrom<
        ResponseRouterData<
            CompleteAuthorize,
            NmiCompleteResponse,
            CompleteAuthorizeData,
            PaymentsResponseData,
        >,
    > for PaymentsCompleteAuthorizeRouterData
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<
            CompleteAuthorize,
            NmiCompleteResponse,
            CompleteAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(item.response.transactionid),
                    redirection_data: Box::new(None),
                    mandate_reference: match item.response.customer_vault_id {
                        Some(vault_id) => Box::new(Some(
                            hyperswitch_domain_models::router_response_types::MandateReference {
                                connector_mandate_id: Some(vault_id.expose()),
                                payment_method_id: None,
                                mandate_metadata: None,
                                connector_mandate_request_reference_id: None,
                            },
                        )),
                        None => Box::new(None),
                    },
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.orderid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                if item.data.request.is_auto_capture()? {
                    AttemptStatus::Charged
                } else {
                    AttemptStatus::Authorized
                },
            ),
            Response::Declined | Response::Error => (
                Err(get_nmi_error_response(item.response, item.http_code)),
                AttemptStatus::Failure,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

fn get_nmi_error_response(response: NmiCompleteResponse, http_code: u16) -> ErrorResponse {
    ErrorResponse {
        code: response.response_code,
        message: response.responsetext.to_owned(),
        reason: Some(response.responsetext),
        status_code: http_code,
        attempt_status: None,
        connector_transaction_id: Some(response.transactionid),
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}

#[derive(Debug, Serialize)]
pub struct NmiValidateRequest {
    #[serde(rename = "type")]
    transaction_type: TransactionType,
    security_key: Secret<String>,
    ccnumber: CardNumber,
    ccexp: Secret<String>,
    cvv: Secret<String>,
    orderid: String,
    customer_vault: CustomerAction,
}

#[derive(Debug, Serialize)]
pub struct NmiPaymentsRequest {
    #[serde(rename = "type")]
    transaction_type: TransactionType,
    amount: FloatMajorUnit,
    security_key: Secret<String>,
    currency: Currency,
    #[serde(flatten)]
    payment_method: PaymentMethod,
    #[serde(flatten)]
    merchant_defined_field: Option<NmiMerchantDefinedField>,
    orderid: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    customer_vault: Option<CustomerAction>,
}

#[derive(Debug, Serialize)]
pub struct NmiMerchantDefinedField {
    #[serde(flatten)]
    inner: std::collections::BTreeMap<String, Secret<String>>,
}

impl NmiMerchantDefinedField {
    pub fn new(metadata: &serde_json::Value) -> Self {
        let metadata_as_string = metadata.to_string();
        let hash_map: std::collections::BTreeMap<String, serde_json::Value> =
            serde_json::from_str(&metadata_as_string).unwrap_or(std::collections::BTreeMap::new());
        let inner = hash_map
            .into_iter()
            .enumerate()
            .map(|(index, (hs_key, hs_value))| {
                let nmi_key = format!("merchant_defined_field_{}", index + 1);
                let nmi_value = format!("{hs_key}={hs_value}");
                (nmi_key, Secret::new(nmi_value))
            })
            .collect();
        Self { inner }
    }
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum PaymentMethod {
    CardNonThreeDs(Box<CardData>),
    CardThreeDs(Box<CardThreeDsData>),
    GPay(Box<GooglePayData>),
    ApplePay(Box<ApplePayData>),
    MandatePayment(Box<MandatePayment>),
}

#[derive(Debug, Serialize)]
pub struct MandatePayment {
    customer_vault_id: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct CardData {
    ccnumber: CardNumber,
    ccexp: Secret<String>,
    cvv: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct CardThreeDsData {
    ccnumber: CardNumber,
    ccexp: Secret<String>,
    email: Option<Email>,
    cardholder_auth: Option<String>,
    cavv: Option<Secret<String>>,
    eci: Option<String>,
    cvv: Secret<String>,
    three_ds_version: Option<String>,
    directory_server_id: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
pub struct GooglePayData {
    googlepay_payment_data: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct ApplePayData {
    applepay_payment_data: Secret<String>,
}

impl TryFrom<&NmiRouterData<&PaymentsAuthorizeRouterData>> for NmiPaymentsRequest {
    type Error = Error;
    fn try_from(item: &NmiRouterData<&PaymentsAuthorizeRouterData>) -> Result<Self, Self::Error> {
        let transaction_type = match item.router_data.request.is_auto_capture()? {
            true => TransactionType::Sale,
            false => TransactionType::Auth,
        };
        let auth_type: NmiAuthType = (&item.router_data.connector_auth_type).try_into()?;
        let amount = item.amount;

        match item
            .router_data
            .request
            .mandate_id
            .clone()
            .and_then(|mandate_ids| mandate_ids.mandate_reference_id)
        {
            Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
                connector_mandate_id,
            )) => Ok(Self {
                transaction_type,
                security_key: auth_type.api_key,
                amount,
                currency: item.router_data.request.currency,
                payment_method: PaymentMethod::MandatePayment(Box::new(MandatePayment {
                    customer_vault_id: Secret::new(
                        connector_mandate_id
                            .get_connector_mandate_id()
                            .ok_or(ConnectorError::MissingConnectorMandateID)?,
                    ),
                })),
                merchant_defined_field: item
                    .router_data
                    .request
                    .metadata
                    .as_ref()
                    .map(NmiMerchantDefinedField::new),
                orderid: item.router_data.connector_request_reference_id.clone(),
                customer_vault: None,
            }),
            Some(api_models::payments::MandateReferenceId::NetworkMandateId(_))
            | Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_)) => {
                Err(ConnectorError::NotImplemented(
                    get_unimplemented_payment_method_error_message("nmi"),
                ))?
            }
            None => {
                let payment_method = PaymentMethod::try_from((
                    &item.router_data.request.payment_method_data,
                    Some(item.router_data),
                ))?;

                Ok(Self {
                    transaction_type,
                    security_key: auth_type.api_key,
                    amount,
                    currency: item.router_data.request.currency,
                    payment_method,
                    merchant_defined_field: item
                        .router_data
                        .request
                        .metadata
                        .as_ref()
                        .map(NmiMerchantDefinedField::new),
                    orderid: item.router_data.connector_request_reference_id.clone(),
                    customer_vault: item
                        .router_data
                        .request
                        .is_mandate_payment()
                        .then_some(CustomerAction::AddCustomer),
                })
            }
        }
    }
}

impl TryFrom<(&PaymentMethodData, Option<&PaymentsAuthorizeRouterData>)> for PaymentMethod {
    type Error = Error;
    fn try_from(
        item: (&PaymentMethodData, Option<&PaymentsAuthorizeRouterData>),
    ) -> Result<Self, Self::Error> {
        let (payment_method_data, router_data) = item;
        match payment_method_data {
            PaymentMethodData::Card(ref card) => match router_data {
                Some(data) => match data.auth_type {
                    AuthenticationType::NoThreeDs => Ok(Self::try_from(card)?),
                    AuthenticationType::ThreeDs => Ok(Self::try_from((card, &data.request))?),
                },
                None => Ok(Self::try_from(card)?),
            },
            PaymentMethodData::Wallet(ref wallet_type) => match wallet_type {
                WalletData::GooglePay(ref googlepay_data) => Ok(Self::try_from(googlepay_data)?),
                WalletData::ApplePay(ref applepay_data) => Ok(Self::try_from(applepay_data)?),
                WalletData::AliPayQr(_)
                | WalletData::AliPayRedirect(_)
                | WalletData::AliPayHkRedirect(_)
                | WalletData::AmazonPayRedirect(_)
                | WalletData::Paysera(_)
                | WalletData::Skrill(_)
                | WalletData::BluecodeRedirect {}
                | WalletData::MomoRedirect(_)
                | WalletData::KakaoPayRedirect(_)
                | WalletData::GoPayRedirect(_)
                | WalletData::GcashRedirect(_)
                | WalletData::ApplePayRedirect(_)
                | WalletData::ApplePayThirdPartySdk(_)
                | WalletData::DanaRedirect {}
                | WalletData::GooglePayRedirect(_)
                | WalletData::GooglePayThirdPartySdk(_)
                | WalletData::MbWayRedirect(_)
                | WalletData::MobilePayRedirect(_)
                | WalletData::PaypalRedirect(_)
                | WalletData::PaypalSdk(_)
                | WalletData::Paze(_)
                | WalletData::SamsungPay(_)
                | WalletData::AmazonPay(_)
                | WalletData::TwintRedirect {}
                | WalletData::VippsRedirect {}
                | WalletData::TouchNGoRedirect(_)
                | WalletData::WeChatPayRedirect(_)
                | WalletData::WeChatPayQr(_)
                | WalletData::CashappQr(_)
                | WalletData::SwishQr(_)
                | WalletData::Mifinity(_)
                | WalletData::RevolutPay(_) => Err(report!(ConnectorError::NotImplemented(
                    get_unimplemented_payment_method_error_message("nmi"),
                ))),
            },
            PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Err(
                ConnectorError::NotImplemented(get_unimplemented_payment_method_error_message(
                    "nmi",
                ))
                .into(),
            ),
        }
    }
}

impl TryFrom<(&Card, &PaymentsAuthorizeData)> for PaymentMethod {
    type Error = Error;
    fn try_from(val: (&Card, &PaymentsAuthorizeData)) -> Result<Self, Self::Error> {
        let (card_data, item) = val;
        let auth_data = &item.get_authentication_data()?;
        let ccexp = card_data.get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?;

        let card_3ds_details = CardThreeDsData {
            ccnumber: card_data.card_number.clone(),
            ccexp,
            cvv: card_data.card_cvc.clone(),
            email: item.email.clone(),
            cavv: Some(auth_data.cavv.clone()),
            eci: auth_data.eci.clone(),
            cardholder_auth: None,
            three_ds_version: auth_data
                .message_version
                .clone()
                .map(|version| version.to_string()),
            directory_server_id: auth_data
                .threeds_server_transaction_id
                .clone()
                .map(Secret::new),
        };

        Ok(Self::CardThreeDs(Box::new(card_3ds_details)))
    }
}

impl TryFrom<&Card> for PaymentMethod {
    type Error = Error;
    fn try_from(card: &Card) -> Result<Self, Self::Error> {
        let ccexp = card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?;
        let card = CardData {
            ccnumber: card.card_number.clone(),
            ccexp,
            cvv: card.card_cvc.clone(),
        };
        Ok(Self::CardNonThreeDs(Box::new(card)))
    }
}

impl TryFrom<&GooglePayWalletData> for PaymentMethod {
    type Error = Report<ConnectorError>;
    fn try_from(wallet_data: &GooglePayWalletData) -> Result<Self, Self::Error> {
        let gpay_data = GooglePayData {
            googlepay_payment_data: Secret::new(
                wallet_data
                    .tokenization_data
                    .get_encrypted_google_pay_token()
                    .change_context(ConnectorError::MissingRequiredField {
                        field_name: "gpay wallet_token",
                    })?
                    .clone(),
            ),
        };
        Ok(Self::GPay(Box::new(gpay_data)))
    }
}

impl TryFrom<&ApplePayWalletData> for PaymentMethod {
    type Error = Error;
    fn try_from(apple_pay_wallet_data: &ApplePayWalletData) -> Result<Self, Self::Error> {
        let apple_pay_encrypted_data = apple_pay_wallet_data
            .payment_data
            .get_encrypted_apple_pay_payment_data_mandatory()
            .change_context(ConnectorError::MissingRequiredField {
                field_name: "Apple pay encrypted data",
            })?;

        let apple_pay_data = ApplePayData {
            applepay_payment_data: Secret::new(apple_pay_encrypted_data.clone()),
        };
        Ok(Self::ApplePay(Box::new(apple_pay_data)))
    }
}

impl TryFrom<&SetupMandateRouterData> for NmiValidateRequest {
    type Error = Error;
    fn try_from(item: &SetupMandateRouterData) -> Result<Self, Self::Error> {
        match item.request.amount {
            Some(amount) if amount > 0 => Err(ConnectorError::FlowNotSupported {
                flow: "Setup Mandate with non zero amount".to_string(),
                connector: "NMI".to_string(),
            }
            .into()),
            _ => {
                if let PaymentMethodData::Card(card_details) = &item.request.payment_method_data {
                    let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?;
                    Ok(Self {
                        transaction_type: TransactionType::Validate,
                        security_key: auth_type.api_key,
                        ccnumber: card_details.card_number.clone(),
                        ccexp: card_details
                            .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?,
                        cvv: card_details.card_cvc.clone(),
                        orderid: item.connector_request_reference_id.clone(),
                        customer_vault: CustomerAction::AddCustomer,
                    })
                } else {
                    Err(ConnectorError::NotImplemented(
                        get_unimplemented_payment_method_error_message("Nmi"),
                    )
                    .into())
                }
            }
        }
    }
}

#[derive(Debug, Serialize)]
pub struct NmiSyncRequest {
    pub order_id: String,
    pub security_key: Secret<String>,
}

impl TryFrom<&PaymentsSyncRouterData> for NmiSyncRequest {
    type Error = Error;
    fn try_from(item: &PaymentsSyncRouterData) -> Result<Self, Self::Error> {
        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
        Ok(Self {
            security_key: auth.api_key,
            order_id: item.attempt_id.clone(),
        })
    }
}

#[derive(Debug, Serialize)]
pub struct NmiCaptureRequest {
    #[serde(rename = "type")]
    pub transaction_type: TransactionType,
    pub security_key: Secret<String>,
    pub transactionid: String,
    pub amount: Option<FloatMajorUnit>,
}

impl TryFrom<&NmiRouterData<&PaymentsCaptureRouterData>> for NmiCaptureRequest {
    type Error = Error;
    fn try_from(item: &NmiRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let auth = NmiAuthType::try_from(&item.router_data.connector_auth_type)?;
        Ok(Self {
            transaction_type: TransactionType::Capture,
            security_key: auth.api_key,
            transactionid: item.router_data.request.connector_transaction_id.clone(),
            amount: Some(item.amount),
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<Capture, StandardResponse, PaymentsCaptureData, PaymentsResponseData>,
    > for RouterData<Capture, PaymentsCaptureData, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<
            Capture,
            StandardResponse,
            PaymentsCaptureData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.transactionid.to_owned(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.orderid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                AttemptStatus::Charged,
            ),
            Response::Declined | Response::Error => (
                Err(get_standard_error_response(item.response, item.http_code)),
                AttemptStatus::CaptureFailed,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct NmiCancelRequest {
    #[serde(rename = "type")]
    pub transaction_type: TransactionType,
    pub security_key: Secret<String>,
    pub transactionid: String,
    pub void_reason: NmiVoidReason,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NmiVoidReason {
    Fraud,
    UserCancel,
    IccRejected,
    IccCardRemoved,
    IccNoConfirmation,
    PosTimeout,
}

impl TryFrom<&PaymentsCancelRouterData> for NmiCancelRequest {
    type Error = Error;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let auth = NmiAuthType::try_from(&item.connector_auth_type)?;
        match &item.request.cancellation_reason {
            Some(cancellation_reason) => {
                let void_reason: NmiVoidReason = serde_json::from_str(&format!("\"{cancellation_reason}\"", ))
                    .map_err(|_| ConnectorError::NotSupported {
                        message: format!("Json deserialise error: unknown variant `{cancellation_reason}` expected to be one of `fraud`, `user_cancel`, `icc_rejected`,  `icc_card_removed`, `icc_no_confirmation`, `pos_timeout`. This cancellation_reason"),
                        connector: "nmi"
                    })?;
                Ok(Self {
                    transaction_type: TransactionType::Void,
                    security_key: auth.api_key,
                    transactionid: item.request.connector_transaction_id.clone(),
                    void_reason,
                })
            }
            None => Err(ConnectorError::MissingRequiredField {
                field_name: "cancellation_reason",
            }
            .into()),
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub enum Response {
    #[serde(alias = "1")]
    Approved,
    #[serde(alias = "2")]
    Declined,
    #[serde(alias = "3")]
    Error,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StandardResponse {
    pub response: Response,
    pub responsetext: String,
    pub authcode: Option<String>,
    pub transactionid: String,
    pub avsresponse: Option<String>,
    pub cvvresponse: Option<String>,
    pub orderid: String,
    pub response_code: String,
    pub customer_vault_id: Option<Secret<String>>,
}

impl<T> TryFrom<ResponseRouterData<SetupMandate, StandardResponse, T, PaymentsResponseData>>
    for RouterData<SetupMandate, T, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<SetupMandate, StandardResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.transactionid.clone(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: match item.response.customer_vault_id {
                        Some(vault_id) => Box::new(Some(
                            hyperswitch_domain_models::router_response_types::MandateReference {
                                connector_mandate_id: Some(vault_id.expose()),
                                payment_method_id: None,
                                mandate_metadata: None,
                                connector_mandate_request_reference_id: None,
                            },
                        )),
                        None => Box::new(None),
                    },
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.orderid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                AttemptStatus::Charged,
            ),
            Response::Declined | Response::Error => (
                Err(get_standard_error_response(item.response, item.http_code)),
                AttemptStatus::Failure,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}
fn get_standard_error_response(response: StandardResponse, http_code: u16) -> ErrorResponse {
    ErrorResponse {
        code: response.response_code,
        message: response.responsetext.to_owned(),
        reason: Some(response.responsetext),
        status_code: http_code,
        attempt_status: None,
        connector_transaction_id: Some(response.transactionid),
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}

impl TryFrom<PaymentsResponseRouterData<StandardResponse>>
    for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            StandardResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.transactionid.clone(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: match item.response.customer_vault_id {
                        Some(vault_id) => Box::new(Some(
                            hyperswitch_domain_models::router_response_types::MandateReference {
                                connector_mandate_id: Some(vault_id.expose()),
                                payment_method_id: None,
                                mandate_metadata: None,
                                connector_mandate_request_reference_id: None,
                            },
                        )),
                        None => Box::new(None),
                    },
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.orderid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                if item.data.request.is_auto_capture()? {
                    AttemptStatus::Charged
                } else {
                    AttemptStatus::Authorized
                },
            ),
            Response::Declined | Response::Error => (
                Err(get_standard_error_response(item.response, item.http_code)),
                AttemptStatus::Failure,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

impl<T> TryFrom<ResponseRouterData<Void, StandardResponse, T, PaymentsResponseData>>
    for RouterData<Void, T, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<Void, StandardResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (response, status) = match item.response.response {
            Response::Approved => (
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.transactionid.clone(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.orderid),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                AttemptStatus::VoidInitiated,
            ),
            Response::Declined | Response::Error => (
                Err(get_standard_error_response(item.response, item.http_code)),
                AttemptStatus::VoidFailed,
            ),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NmiStatus {
    Abandoned,
    Cancelled,
    Pendingsettlement,
    Pending,
    Failed,
    Complete,
    InProgress,
    Unknown,
}

impl<F, T> TryFrom<ResponseRouterData<F, SyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<F, SyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response.transaction {


// Function: generate_env_specific_merchant_id
// File: crates/router/src/utils/user.rs
// Module: router

pub fn generate_env_specific_merchant_id(value: String) -> UserResult<id_type::MerchantId>


// Implementation: impl api::PaymentVoid for for Fiservemea
// File: crates/hyperswitch_connectors/src/connectors/fiservemea.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentVoid for for Fiservemea


// File: crates/analytics/src/sdk_events/accumulator.rs
// Module: analytics
// Public functions: 1
// Public structs: 3

use api_models::analytics::sdk_events::SdkEventMetricsBucketValue;
use router_env::logger;

use super::metrics::SdkEventMetricRow;

#[derive(Debug, Default)]
pub struct SdkEventMetricsAccumulator {
    pub payment_attempts: CountAccumulator,
    pub payment_methods_call_count: CountAccumulator,
    pub average_payment_time: CountAccumulator,
    pub load_time: CountAccumulator,
    pub sdk_initiated_count: CountAccumulator,
    pub sdk_rendered_count: CountAccumulator,
    pub payment_method_selected_count: CountAccumulator,
    pub payment_data_filled_count: CountAccumulator,
}

#[derive(Debug, Default)]
#[repr(transparent)]
pub struct CountAccumulator {
    pub count: Option<i64>,
}

#[derive(Debug, Default)]
pub struct AverageAccumulator {
    pub total: u32,
    pub count: u32,
}

pub trait SdkEventMetricAccumulator {
    type MetricOutput;

    fn add_metrics_bucket(&mut self, metrics: &SdkEventMetricRow);

    fn collect(self) -> Self::MetricOutput;
}

impl SdkEventMetricAccumulator for CountAccumulator {
    type MetricOutput = Option<u64>;
    #[inline]
    fn add_metrics_bucket(&mut self, metrics: &SdkEventMetricRow) {
        self.count = match (self.count, metrics.count) {
            (None, None) => None,
            (None, i @ Some(_)) | (i @ Some(_), None) => i,
            (Some(a), Some(b)) => Some(a + b),
        }
    }
    #[inline]
    fn collect(self) -> Self::MetricOutput {
        self.count.and_then(|i| u64::try_from(i).ok())
    }
}

impl SdkEventMetricAccumulator for AverageAccumulator {
    type MetricOutput = Option<f64>;

    fn add_metrics_bucket(&mut self, metrics: &SdkEventMetricRow) {
        let total = metrics
            .total
            .as_ref()
            .and_then(bigdecimal::ToPrimitive::to_u32);
        let count = metrics.count.and_then(|total| u32::try_from(total).ok());

        match (total, count) {
            (Some(total), Some(count)) => {
                self.total += total;
                self.count += count;
            }
            _ => {
                logger::error!(message="Dropping metrics for average accumulator", metric=?metrics);
            }
        }
    }

    fn collect(self) -> Self::MetricOutput {
        if self.count == 0 {
            None
        } else {
            Some(f64::from(self.total) / f64::from(self.count))
        }
    }
}

impl SdkEventMetricsAccumulator {
    #[allow(dead_code)]
    pub fn collect(self) -> SdkEventMetricsBucketValue {
        SdkEventMetricsBucketValue {
            payment_attempts: self.payment_attempts.collect(),
            payment_methods_call_count: self.payment_methods_call_count.collect(),
            average_payment_time: self.average_payment_time.collect(),
            load_time: self.load_time.collect(),
            sdk_initiated_count: self.sdk_initiated_count.collect(),
            sdk_rendered_count: self.sdk_rendered_count.collect(),
            payment_method_selected_count: self.payment_method_selected_count.collect(),
            payment_data_filled_count: self.payment_data_filled_count.collect(),
        }
    }
}


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

pub struct BarclaycardAuthValidateRequest


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

impl api::MandateSetup for for Hipay


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

pub fn payouts_list_available_filters_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<id_type::ProfileId>>,
    time_range: common_utils::types::TimeRange,
) -> RouterResponse<api::PayoutListFilters>


// Struct: ZenPaymentData
// File: crates/hyperswitch_connectors/src/connectors/zen/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ZenPaymentData


// Function: server
// File: crates/router/src/routes/app.rs
// Module: router

pub fn server(state: AppState) -> Scope


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

impl api::PaymentSession for for Stripebilling


// Implementation: impl ProxyRecord
// File: crates/router/src/core/proxy/utils.rs
// Module: router
// Methods: 3 total (0 public)

impl ProxyRecord


// Struct: MandateData
// File: crates/router/src/compatibility/stripe/payment_intents/types.rs
// Module: router
// Implementations: 0

pub struct MandateData


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

pub fn qr_code_next_steps_check(
    payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::QrCodeInformation>>


// Struct: ArchipelCaptureOrderRequest
// File: crates/hyperswitch_connectors/src/connectors/archipel/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ArchipelCaptureOrderRequest


// Function: get_internal_error_mut
// File: crates/api_models/src/errors/types.rs
// Module: api_models

pub fn get_internal_error_mut(&mut self) -> &mut ApiError


// Struct: DynamicAlgorithmWithTimestamp
// File: crates/api_models/src/routing.rs
// Module: api_models
// Implementations: 1

pub struct DynamicAlgorithmWithTimestamp<T>


// Implementation: impl Capturable for for SdkPaymentsSessionUpdateData
// File: crates/router/src/types.rs
// Module: router
// Methods: 0 total (0 public)

impl Capturable for for SdkPaymentsSessionUpdateData


// Struct: TokenizedBankRedirectValue1
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct TokenizedBankRedirectValue1


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

impl api::Payment for for Bitpay


// Struct: ForteRouterData
// File: crates/hyperswitch_connectors/src/connectors/forte/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ForteRouterData<T>


// Function: generic_user_roles_list_for_user
// File: crates/diesel_models/src/query/user_role.rs
// Module: diesel_models

pub fn generic_user_roles_list_for_user(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: Option<id_type::OrganizationId>,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        entity_id: Option<String>,
        status: Option<UserStatus>,
        version: Option<UserRoleVersion>,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>>


// Implementation: impl FraudCheckRecordReturn for for Signifyd
// File: crates/hyperswitch_connectors/src/connectors/signifyd.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl FraudCheckRecordReturn for for Signifyd


                    "parsing failed with error: '{param}' at position: {position}"
                ),
                param: Some(param),
            },
            serde_qs::Error::Unsupported => Self::SerdeQsError {
                error_message: "Given request format is not supported".to_owned(),
                param: None,
            },
            serde_qs::Error::FromUtf8(_) => Self::SerdeQsError {
                error_message: "Failed to parse request to from utf-8".to_owned(),
                param: None,
            },
            serde_qs::Error::Io(_) => Self::SerdeQsError {
                error_message: "Failed to parse request".to_owned(),
                param: None,
            },
            serde_qs::Error::ParseInt(_) => Self::SerdeQsError {
                error_message: "Failed to parse integer in request".to_owned(),
                param: None,
            },
            serde_qs::Error::Utf8(_) => Self::SerdeQsError {
                error_message: "Failed to convert utf8 to string".to_owned(),
                param: None,
            },
        }
    }
}

impl ErrorSwitch<StripeErrorCode> for errors::ApiErrorResponse {
    fn switch(&self) -> StripeErrorCode {
        self.clone().into()
    }
}

impl crate::services::EmbedError for error_stack::Report<StripeErrorCode> {}

impl ErrorSwitch<StripeErrorCode> for CustomersErrorResponse {
    fn switch(&self) -> StripeErrorCode {
        use StripeErrorCode as SC;
        match self {
            Self::CustomerRedacted => SC::CustomerRedacted,
            Self::InternalServerError => SC::InternalServerError,
            Self::MandateActive => SC::MandateActive,
            Self::CustomerNotFound => SC::CustomerNotFound,
            Self::CustomerAlreadyExists => SC::DuplicateCustomer,
        }
    }
}


// Struct: VoidStatus
// File: crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct VoidStatus


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

impl api::Refund for for Nmi


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

impl api::MandateSetup for for Payu


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

impl ConnectorCommon for for Worldpay


// Struct: InvoiceEstimate
// File: crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct InvoiceEstimate


// Struct: TokenioPaymentObjectV2
// File: crates/hyperswitch_connectors/src/connectors/tokenio/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TokenioPaymentObjectV2


// Function: get_default_payout_connector
// File: crates/router/src/core/payouts/helpers.rs
// Module: router

pub fn get_default_payout_connector(
    _state: &SessionState,
    request_connector: Option<serde_json::Value>,
) -> CustomResult<api::ConnectorChoice, errors::ApiErrorResponse>


// Struct: MpgsErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/mpgs/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct MpgsErrorResponse


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

impl Sift


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

impl api::Payment for for Barclaycard


// File: crates/hyperswitch_connectors/src/connectors/paysafe/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 8
// Public structs: 41

use std::collections::HashMap;

use base64::Engine;
use cards::CardNumber;
use common_enums::{enums, Currency};
use common_types::payments::{ApplePayPaymentData, ApplePayPredecryptData};
use common_utils::{
    id_type,
    pii::{Email, IpAddress, SecretSerdeValue},
    request::Method,
    types::MinorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        ApplePayWalletData, BankRedirectData, GiftCardData, PaymentMethodData, WalletData,
    },
    router_data::{ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{
        CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsPreProcessingData, PaymentsSyncData,
        ResponseId,
    },
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{consts, errors};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, missing_field_err, to_connector_meta, BrowserInformationData, CardData,
        PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
        PaymentsPreProcessingRequestData, RouterData as _,
    },
};

pub struct PaysafeRouterData<T> {
    pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> From<(MinorUnit, T)> for PaysafeRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaysafeConnectorMetadataObject {
    pub account_id: PaysafePaymentMethodDetails,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PaysafePaymentMethodDetails {
    pub apple_pay: Option<HashMap<Currency, ApplePayAccountDetails>>,
    pub card: Option<HashMap<Currency, CardAccountId>>,
    pub interac: Option<HashMap<Currency, RedirectAccountId>>,
    pub pay_safe_card: Option<HashMap<Currency, RedirectAccountId>>,
    pub skrill: Option<HashMap<Currency, RedirectAccountId>>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CardAccountId {
    no_three_ds: Option<Secret<String>>,
    three_ds: Option<Secret<String>>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ApplePayAccountDetails {
    encrypt: Option<Secret<String>>,
    decrypt: Option<Secret<String>>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct RedirectAccountId {
    three_ds: Option<Secret<String>>,
}

impl TryFrom<&Option<SecretSerdeValue>> for PaysafeConnectorMetadataObject {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(meta_data: &Option<SecretSerdeValue>) -> Result<Self, Self::Error> {
        let metadata: Self = utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "merchant_connector_account.metadata",
            })?;
        Ok(metadata)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDs {
    pub merchant_url: String,
    pub device_channel: DeviceChannel,
    pub message_category: ThreeDsMessageCategory,
    pub authentication_purpose: ThreeDsAuthenticationPurpose,
    pub requestor_challenge_preference: ThreeDsChallengePreference,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum DeviceChannel {
    Browser,
    Sdk,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ThreeDsMessageCategory {
    Payment,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ThreeDsAuthenticationPurpose {
    PaymentTransaction,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ThreeDsChallengePreference {
    ChallengeMandated,
    NoPreference,
    NoChallengeRequested,
    ChallengeRequested,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafePaymentHandleRequest {
    pub merchant_ref_num: String,
    pub amount: MinorUnit,
    pub settle_with_auth: bool,
    #[serde(flatten)]
    pub payment_method: PaysafePaymentMethod,
    pub currency_code: Currency,
    pub payment_type: PaysafePaymentType,
    pub transaction_type: TransactionType,
    pub return_links: Vec<ReturnLink>,
    pub account_id: Secret<String>,
    pub three_ds: Option<ThreeDs>,
    pub profile: Option<PaysafeProfile>,
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeProfile {
    pub first_name: Secret<String>,
    pub last_name: Secret<String>,
    pub email: Email,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum PaysafePaymentMethod {
    ApplePay {
        #[serde(rename = "applePay")]
        apple_pay: Box<PaysafeApplepayPayment>,
    },
    Card {
        card: PaysafeCard,
    },
    Interac {
        #[serde(rename = "interacEtransfer")]
        interac_etransfer: InteracBankRedirect,
    },
    PaysafeCard {
        #[serde(rename = "paysafecard")]
        pay_safe_card: PaysafeGiftCard,
    },
    Skrill {
        skrill: SkrillWallet,
    },
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplepayPayment {
    pub label: Option<String>,
    pub request_billing_address: Option<bool>,
    #[serde(rename = "applePayPaymentToken")]
    pub apple_pay_payment_token: PaysafeApplePayPaymentToken,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayPaymentToken {
    pub token: PaysafeApplePayToken,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub billing_contact: Option<PaysafeApplePayBillingContact>,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayToken {
    pub payment_data: PaysafeApplePayPaymentData,
    pub payment_method: PaysafeApplePayPaymentMethod,
    pub transaction_identifier: String,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum PaysafeApplePayPaymentData {
    Encrypted(PaysafeApplePayEncryptedData),
    Decrypted(PaysafeApplePayDecryptedDataWrapper),
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayEncryptedData {
    pub data: Secret<String>,
    pub signature: Secret<String>,
    pub header: PaysafeApplePayHeader,
    pub version: Secret<String>,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayDecryptedDataWrapper {
    pub decrypted_data: PaysafeApplePayDecryptedData,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayDecryptedData {
    pub application_primary_account_number: CardNumber,
    pub application_expiration_date: Secret<String>,
    pub currency_code: String,
    pub transaction_amount: Option<MinorUnit>,
    pub cardholder_name: Option<Secret<String>>,
    pub device_manufacturer_identifier: Option<String>,
    pub payment_data_type: Option<String>,
    pub payment_data: PaysafeApplePayDecryptedPaymentData,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayDecryptedPaymentData {
    pub online_payment_cryptogram: Secret<String>,
    pub eci_indicator: Option<String>,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayHeader {
    pub public_key_hash: String,
    pub ephemeral_public_key: String,
    pub transaction_id: String,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayPaymentMethod {
    pub display_name: Secret<String>,
    pub network: Secret<String>,
    #[serde(rename = "type")]
    pub method_type: Secret<String>,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeApplePayBillingContact {
    pub address_lines: Vec<Option<Secret<String>>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub administrative_area: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,
    pub country_code: api_models::enums::CountryAlpha2,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub family_name: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub given_name: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub locality: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phonetic_family_name: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phonetic_given_name: Option<Secret<String>>,
    pub postal_code: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sub_administrative_area: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sub_locality: Option<Secret<String>>,
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SkrillWallet {
    pub consumer_id: Email,
    pub country_code: Option<api_models::enums::CountryAlpha2>,
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct InteracBankRedirect {
    pub consumer_id: Email,
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeGiftCard {
    pub consumer_id: id_type::CustomerId,
}

#[derive(Debug, Serialize)]
pub struct ReturnLink {
    pub rel: LinkType,
    pub href: String,
    pub method: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum LinkType {
    OnCompleted,
    OnFailed,
    OnCancelled,
    Default,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaysafePaymentType {
    // For Apple Pay and Google Pay, paymentType is 'CARD' as per Paysafe docs and is not reserved for card payments only
    Card,
    Skrill,
    InteracEtransfer,
    Paysafecard,
}

#[derive(Debug, Serialize)]
pub enum TransactionType {
    #[serde(rename = "PAYMENT")]
    Payment,
}

impl PaysafePaymentMethodDetails {
    pub fn get_applepay_encrypt_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.apple_pay
            .as_ref()
            .and_then(|apple_pay| apple_pay.get(&currency))
            .and_then(|flow| flow.encrypt.clone())
            .ok_or_else(|| errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing ApplePay encrypt account_id",
            })
    }

    pub fn get_applepay_decrypt_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.apple_pay
            .as_ref()
            .and_then(|apple_pay| apple_pay.get(&currency))
            .and_then(|flow| flow.decrypt.clone())
            .ok_or_else(|| errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing ApplePay decrypt account_id",
            })
    }

    pub fn get_no_three_ds_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.card
            .as_ref()
            .and_then(|cards| cards.get(&currency))
            .and_then(|card| card.no_three_ds.clone())
            .ok_or(errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing no_3ds account_id",
            })
    }

    pub fn get_three_ds_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.card
            .as_ref()
            .and_then(|cards| cards.get(&currency))
            .and_then(|card| card.three_ds.clone())
            .ok_or(errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing 3ds account_id",
            })
    }

    pub fn get_skrill_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.skrill
            .as_ref()
            .and_then(|wallets| wallets.get(&currency))
            .and_then(|skrill| skrill.three_ds.clone())
            .ok_or(errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing skrill account_id",
            })
    }

    pub fn get_interac_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.interac
            .as_ref()
            .and_then(|redirects| redirects.get(&currency))
            .and_then(|interac| interac.three_ds.clone())
            .ok_or(errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing interac account_id",
            })
    }

    pub fn get_paysafe_gift_card_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError> {
        self.pay_safe_card
            .as_ref()
            .and_then(|gift_cards| gift_cards.get(&currency))
            .and_then(|pay_safe_card| pay_safe_card.three_ds.clone())
            .ok_or(errors::ConnectorError::InvalidConnectorConfig {
                config: "Missing paysafe gift card account_id",
            })
    }
}

impl TryFrom<&PaysafeRouterData<&PaymentsPreProcessingRouterData>> for PaysafePaymentHandleRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
    ) -> Result<Self, Self::Error> {
        let metadata: PaysafeConnectorMetadataObject =
            utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())
                .change_context(errors::ConnectorError::InvalidConnectorConfig {
                    config: "merchant_connector_account.metadata",
                })?;

        let amount = item.amount;
        let currency_code = item.router_data.request.get_currency()?;
        let redirect_url = item.router_data.request.get_router_return_url()?;
        let return_links = vec![
            ReturnLink {
                rel: LinkType::Default,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnCompleted,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnFailed,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnCancelled,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
        ];
        let settle_with_auth = matches!(
            item.router_data.request.capture_method,
            Some(enums::CaptureMethod::Automatic) | None
        );
        let transaction_type = TransactionType::Payment;

        let (payment_method, payment_type, account_id) =
            match item.router_data.request.get_payment_method_data()?.clone() {
                PaymentMethodData::Card(req_card) => {
                    let card = PaysafeCard {
                        card_num: req_card.card_number.clone(),
                        card_expiry: PaysafeCardExpiry {
                            month: req_card.card_exp_month.clone(),
                            year: req_card.get_expiry_year_4_digit(),
                        },
                        cvv: if req_card.card_cvc.clone().expose().is_empty() {
                            None
                        } else {
                            Some(req_card.card_cvc.clone())
                        },
                        holder_name: item.router_data.get_optional_billing_full_name(),
                    };

                    let payment_method = PaysafePaymentMethod::Card { card: card.clone() };
                    let payment_type = PaysafePaymentType::Card;
                    let account_id = metadata
                        .account_id
                        .get_no_three_ds_account_id(currency_code)?;
                    (payment_method, payment_type, account_id)
                }
                PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                    WalletData::ApplePay(applepay_data) => {
                        let is_encrypted = matches!(
                            applepay_data.payment_data,
                            ApplePayPaymentData::Encrypted(_)
                        );

                        let account_id = if is_encrypted {
                            metadata
                                .account_id
                                .get_applepay_encrypt_account_id(currency_code)?
                        } else {
                            metadata
                                .account_id
                                .get_applepay_decrypt_account_id(currency_code)?
                        };

                        let applepay_payment =
                            PaysafeApplepayPayment::try_from((&applepay_data, item))?;

                        let payment_method = PaysafePaymentMethod::ApplePay {
                            apple_pay: Box::new(applepay_payment),
                        };

                        let payment_type = PaysafePaymentType::Card;

                        (payment_method, payment_type, account_id)
                    }
                    WalletData::AliPayQr(_)
                    | WalletData::AliPayRedirect(_)
                    | WalletData::AliPayHkRedirect(_)
                    | WalletData::AmazonPay(_)
                    | WalletData::AmazonPayRedirect(_)
                    | WalletData::Paysera(_)
                    | WalletData::Skrill(_)
                    | WalletData::BluecodeRedirect {}
                    | WalletData::MomoRedirect(_)
                    | WalletData::KakaoPayRedirect(_)
                    | WalletData::GoPayRedirect(_)
                    | WalletData::GcashRedirect(_)
                    | WalletData::ApplePayRedirect(_)
                    | WalletData::ApplePayThirdPartySdk(_)
                    | WalletData::DanaRedirect {}
                    | WalletData::GooglePayRedirect(_)
                    | WalletData::GooglePay(_)
                    | WalletData::GooglePayThirdPartySdk(_)
                    | WalletData::MbWayRedirect(_)
                    | WalletData::MobilePayRedirect(_)
                    | WalletData::PaypalSdk(_)
                    | WalletData::PaypalRedirect(_)
                    | WalletData::Paze(_)
                    | WalletData::SamsungPay(_)
                    | WalletData::TwintRedirect {}
                    | WalletData::VippsRedirect {}
                    | WalletData::TouchNGoRedirect(_)
                    | WalletData::WeChatPayRedirect(_)
                    | WalletData::CashappQr(_)
                    | WalletData::SwishQr(_)
                    | WalletData::WeChatPayQr(_)
                    | WalletData::RevolutPay(_)
                    | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("Paysafe"),
                    ))?,
                },
                _ => Err(errors::ConnectorError::NotImplemented(
                    "Payment Method".to_string(),
                ))?,
            };

        Ok(Self {
            merchant_ref_num: item.router_data.connector_request_reference_id.clone(),
            amount,
            settle_with_auth,
            payment_method,
            currency_code,
            payment_type,
            transaction_type,
            return_links,
            account_id,
            three_ds: None,
            profile: None,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafePaymentHandleResponse {
    pub id: String,
    pub merchant_ref_num: String,
    pub payment_handle_token: Secret<String>,
    pub status: PaysafePaymentHandleStatus,
    pub links: Option<Vec<PaymentLink>>,
    pub error: Option<Error>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentLink {
    pub rel: String,
    pub href: String,
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum PaysafePaymentHandleStatus {
    Initiated,
    Payable,
    #[default]
    Processing,
    Failed,
    Expired,
    Completed,
    Error,
}

impl TryFrom<PaysafePaymentHandleStatus> for common_enums::AttemptStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: PaysafePaymentHandleStatus) -> Result<Self, Self::Error> {
        match item {
            PaysafePaymentHandleStatus::Completed => Ok(Self::Authorized),
            PaysafePaymentHandleStatus::Failed
            | PaysafePaymentHandleStatus::Expired
            | PaysafePaymentHandleStatus::Error => Ok(Self::Failure),
            // We get an `Initiated` status, with a redirection link from the connector, which indicates that further action is required by the customer,
            PaysafePaymentHandleStatus::Initiated => Ok(Self::AuthenticationPending),
            PaysafePaymentHandleStatus::Payable | PaysafePaymentHandleStatus::Processing => {
                Ok(Self::Pending)
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PaysafeMeta {
    pub payment_handle_token: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            PaysafePaymentHandleResponse,
            PaymentsPreProcessingData,
            PaymentsResponseData,
        >,
    > for RouterData<F, PaymentsPreProcessingData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            PaysafePaymentHandleResponse,
            PaymentsPreProcessingData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::try_from(item.response.status)?,
            preprocessing_id: Some(
                item.response
                    .payment_handle_token
                    .to_owned()
                    .peek()
                    .to_string(),
            ),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::NoResponseId,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F>
    TryFrom<
        ResponseRouterData<F, PaysafePaymentsResponse, PaymentsAuthorizeData, PaymentsResponseData>,
    > for RouterData<F, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            PaysafePaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: get_paysafe_payment_status(
                item.response.status,
                item.data.request.capture_method,
            ),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            PaysafePaymentHandleResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<F, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            PaysafePaymentHandleResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let redirection_data = item
            .response
            .links
            .as_ref()
            .and_then(|links| links.first())
            .map(|link| RedirectForm::Form {
                endpoint: link.href.clone(),
                method: Method::Get,
                form_fields: Default::default(),
            });
        let connector_metadata = serde_json::json!(PaysafeMeta {
            payment_handle_token: item.response.payment_handle_token.clone(),
        });
        Ok(Self {
            status: common_enums::AttemptStatus::try_from(item.response.status)?,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::NoResponseId,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: Some(connector_metadata),
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaysafePaymentsRequest {
    pub merchant_ref_num: String,
    pub amount: MinorUnit,
    pub settle_with_auth: bool,
    pub payment_handle_token: Secret<String>,
    pub currency_code: Currency,
    pub customer_ip: Option<Secret<String, IpAddress>>,
}

#[derive(Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaysafeCard {
    pub card_num: CardNumber,
    pub card_expiry: PaysafeCardExpiry,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cvv: Option<Secret<String>>,
    pub holder_name: Option<Secret<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaysafeCardExpiry {
    pub month: Secret<String>,
    pub year: Secret<String>,
}

#[derive(Debug, Deserialize)]
struct DecryptedApplePayTokenData {
    data: Secret<String>,
    signature: Secret<String>,
    header: DecryptedApplePayTokenHeader,
    version: Secret<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DecryptedApplePayTokenHeader {
    public_key_hash: String,
    ephemeral_public_key: String,
    transaction_id: String,
}

fn get_apple_pay_decrypt_data(
    apple_pay_predecrypt_data: &ApplePayPredecryptData,
    item: &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
) -> Result<PaysafeApplePayDecryptedData, error_stack::Report<errors::ConnectorError>> {
    Ok(PaysafeApplePayDecryptedData {
        application_primary_account_number: apple_pay_predecrypt_data
            .application_primary_account_number
            .clone(),
        application_expiration_date: apple_pay_predecrypt_data
            .get_expiry_date_as_yymm()
            .change_context(errors::ConnectorError::InvalidDataFormat {
                field_name: "application_expiration_date",
            })?,
        currency_code: Currency::iso_4217(
            item.router_data
                .request
                .currency
                .ok_or_else(missing_field_err("currency"))?,
        )
        .to_string(),

        transaction_amount: Some(item.amount),
        cardholder_name: None,
        device_manufacturer_identifier: Some("Apple".to_string()),
        payment_data_type: Some("3DSecure".to_string()),
        payment_data: PaysafeApplePayDecryptedPaymentData {
            online_payment_cryptogram: apple_pay_predecrypt_data
                .payment_data
                .online_payment_cryptogram
                .clone(),
            eci_indicator: apple_pay_predecrypt_data.payment_data.eci_indicator.clone(),
        },
    })
}

impl
    TryFrom<(
        &ApplePayWalletData,
        &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
    )> for PaysafeApplepayPayment
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (wallet_data, item): (
            &ApplePayWalletData,
            &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
        ),
    ) -> Result<Self, Self::Error> {
        let apple_pay_payment_token = PaysafeApplePayPaymentToken {
            token: PaysafeApplePayToken {
                payment_data: if let Ok(PaymentMethodToken::ApplePayDecrypt(ref token)) =
                    item.router_data.get_payment_method_token()
                {
                    PaysafeApplePayPaymentData::Decrypted(PaysafeApplePayDecryptedDataWrapper {
                        decrypted_data: get_apple_pay_decrypt_data(token, item)?,
                    })
                } else {
                    match &wallet_data.payment_data {
                        ApplePayPaymentData::Decrypted(applepay_predecrypt_data) => {
                            PaysafeApplePayPaymentData::Decrypted(
                                PaysafeApplePayDecryptedDataWrapper {
                                    decrypted_data: get_apple_pay_decrypt_data(
                                        applepay_predecrypt_data,
                                        item,
                                    )?,
                                },
                            )
                        }
                        ApplePayPaymentData::Encrypted(applepay_encrypt_data) => {
                            let decoded_data = base64::prelude::BASE64_STANDARD
                                .decode(applepay_encrypt_data)
                                .change_context(errors::ConnectorError::InvalidDataFormat {
                                    field_name: "apple_pay_encrypted_data",
                                })?;

                            let apple_pay_token: DecryptedApplePayTokenData =
                                serde_json::from_slice(&decoded_data).change_context(
                                    errors::ConnectorError::InvalidDataFormat {
                                        field_name: "apple_pay_token_json",
                                    },
                                )?;

                            PaysafeApplePayPaymentData::Encrypted(PaysafeApplePayEncryptedData {
                                data: apple_pay_token.data,
                                signature: apple_pay_token.signature,
                                header: PaysafeApplePayHeader {
                                    public_key_hash: apple_pay_token.header.public_key_hash,
                                    ephemeral_public_key: apple_pay_token
                                        .header
                                        .ephemeral_public_key,
                                    transaction_id: apple_pay_token.header.transaction_id,
                                },
                                version: apple_pay_token.version,
                            })
                        }
                    }
                },
                payment_method: PaysafeApplePayPaymentMethod {
                    display_name: Secret::new(wallet_data.payment_method.display_name.clone()),
                    network: Secret::new(wallet_data.payment_method.network.clone()),
                    method_type: Secret::new(wallet_data.payment_method.pm_type.clone()),
                },
                transaction_identifier: wallet_data.transaction_identifier.clone(),
            },
            billing_contact: Some(PaysafeApplePayBillingContact {
                address_lines: vec![
                    item.router_data.get_optional_billing_line1(),
                    item.router_data.get_optional_billing_line2(),
                ],
                postal_code: item.router_data.get_billing_zip()?,
                country_code: item.router_data.get_billing_country()?,
                country: None,
                family_name: None,
                given_name: None,
                locality: None,
                phonetic_family_name: None,
                phonetic_given_name: None,
                sub_administrative_area: None,
                administrative_area: None,
                sub_locality: None,
            }),
        };

        Ok(Self {
            label: None,
            request_billing_address: Some(false),
            apple_pay_payment_token,
        })
    }
}

impl TryFrom<&PaysafeRouterData<&PaymentsAuthorizeRouterData>> for PaysafePaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PaysafeRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let payment_handle_token = Secret::new(item.router_data.get_preprocessing_id()?);
        let amount = item.amount;
        let customer_ip = Some(
            item.router_data
                .request
                .get_browser_info()?
                .get_ip_address()?,
        );

        Ok(Self {
            merchant_ref_num: item.router_data.connector_request_reference_id.clone(),
            payment_handle_token,
            amount,
            settle_with_auth: item.router_data.request.is_auto_capture()?,
            currency_code: item.router_data.request.currency,
            customer_ip,
        })
    }
}

impl TryFrom<&PaysafeRouterData<&PaymentsAuthorizeRouterData>> for PaysafePaymentHandleRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PaysafeRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let metadata: PaysafeConnectorMetadataObject =
            utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())
                .change_context(errors::ConnectorError::InvalidConnectorConfig {
                    config: "merchant_connector_account.metadata",
                })?;
        let redirect_url_success = item.router_data.request.get_complete_authorize_url()?;
        let redirect_url = item.router_data.request.get_router_return_url()?;
        let return_links = vec![
            ReturnLink {
                rel: LinkType::Default,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnCompleted,
                href: redirect_url_success.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnFailed,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
            ReturnLink {
                rel: LinkType::OnCancelled,
                href: redirect_url.clone(),
                method: Method::Get.to_string(),
            },
        ];
        let amount = item.amount;
        let currency_code = item.router_data.request.currency;
        let settle_with_auth = matches!(
            item.router_data.request.capture_method,
            Some(enums::CaptureMethod::Automatic) | None
        );
        let transaction_type = TransactionType::Payment;
        let (payment_method, payment_type, account_id, three_ds, profile) =
            match item.router_data.request.payment_method_data.clone() {
                PaymentMethodData::Card(req_card) => {
                    let card = PaysafeCard {
                        card_num: req_card.card_number.clone(),
                        card_expiry: PaysafeCardExpiry {
                            month: req_card.card_exp_month.clone(),
                            year: req_card.get_expiry_year_4_digit(),
                        },
                        cvv: if req_card.card_cvc.clone().expose().is_empty() {
                            None
                        } else {
                            Some(req_card.card_cvc.clone())
                        },
                        holder_name: item.router_data.get_optional_billing_full_name(),
                    };
                    let payment_method = PaysafePaymentMethod::Card { card: card.clone() };
                    let payment_type = PaysafePaymentType::Card;

                    let headers = item.router_data.header_payload.clone();
                    let platform = headers.as_ref().and_then(|h| h.x_client_platform.clone());
                    let device_channel = match platform {
                        Some(common_enums::ClientPlatform::Web)
                        | Some(common_enums::ClientPlatform::Unknown)
                        | None => DeviceChannel::Browser,
                        Some(common_enums::ClientPlatform::Ios)
                        | Some(common_enums::ClientPlatform::Android) => DeviceChannel::Sdk,
                    };

                    let account_id = metadata.account_id.get_three_ds_account_id(currency_code)?;
                    let three_ds = Some(ThreeDs {
                        merchant_url: item.router_data.request.get_router_return_url()?,
                        device_channel,
                        message_category: ThreeDsMessageCategory::Payment,
                        authentication_purpose: ThreeDsAuthenticationPurpose::PaymentTransaction,
                        requestor_challenge_preference:
                            ThreeDsChallengePreference::ChallengeMandated,
                    });

                    (payment_method, payment_type, account_id, three_ds, None)
                }

                PaymentMethodData::Wallet(WalletData::Skrill(_)) => {
                    let payment_method = PaysafePaymentMethod::Skrill {
                        skrill: SkrillWallet {
                            consumer_id: item.router_data.get_billing_email()?,
                            country_code: item.router_data.get_optional_billing_country(),
                        },
                    };
                    let payment_type = PaysafePaymentType::Skrill;
                    let account_id = metadata.account_id.get_skrill_account_id(currency_code)?;
                    (payment_method, payment_type, account_id, None, None)
                }
                PaymentMethodData::Wallet(_) => Err(errors::ConnectorError::NotImplemented(
                    "Payment Method".to_string(),
                ))?,

                PaymentMethodData::BankRedirect(BankRedirectData::Interac { .. }) => {
                    let payment_method = PaysafePaymentMethod::Interac {
                        interac_etransfer: InteracBankRedirect {
                            consumer_id: item.router_data.get_billing_email()?,
                        },
                    };
                    let payment_type = PaysafePaymentType::InteracEtransfer;
                    let account_id = metadata.account_id.get_interac_account_id(currency_code)?;
                    let profile = Some(PaysafeProfile {
                        first_name: item.router_data.get_billing_first_name()?,
                        last_name: item.router_data.get_billing_last_name()?,
                        email: item.router_data.get_billing_email()?,
                    });
                    (payment_method, payment_type, account_id, None, profile)
                }
                PaymentMethodData::BankRedirect(_) => Err(errors::ConnectorError::NotImplemented(
                    "Payment Method".to_string(),
                ))?,


// Function: call_psync_api
// File: crates/router/src/core/revenue_recovery/api.rs
// Module: router

pub fn call_psync_api(
    state: &SessionState,
    global_payment_id: &id_type::GlobalPaymentId,
    revenue_recovery_data: &revenue_recovery_types::RevenueRecoveryPaymentData,
    force_sync_bool: bool,
    expand_attempts_bool: bool,
) -> RouterResult<payments_domain::PaymentStatusData<api_types::PSync>>


// Function: skip_ws
// File: crates/euclid/src/frontend/ast/parser.rs
// Module: euclid

pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&'a str, O>
where
    F: FnMut(&'a str) -> ParseResult<&'a str, O> + 'a,


// Struct: StripeUpdateRefundRequest
// File: crates/router/src/compatibility/stripe/refunds/types.rs
// Module: router
// Implementations: 0

pub struct StripeUpdateRefundRequest


// File: crates/router/src/types/api/payments_v2.rs
// Module: router

pub use hyperswitch_interfaces::api::payments_v2::{
    ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2,
    PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2,
    PaymentPostCaptureVoidV2, PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2,
    PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2,
    PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2,
    TaxCalculationV2,
};


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

impl api::RefundSync for for Cryptopay


// Struct: CompleteAuthorizeData
// File: crates/hyperswitch_domain_models/src/router_request_types.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct CompleteAuthorizeData


// Struct: BankAccountConnectorDetails
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct BankAccountConnectorDetails


// Implementation: impl PayoutAttemptNew
// File: crates/diesel_models/src/query/payout_attempt.rs
// Module: diesel_models
// Methods: 1 total (0 public)

impl PayoutAttemptNew


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

impl api::MandateSetup for for Nuvei


// Implementation: impl ConnectorCommon for for Mifinity
// File: crates/hyperswitch_connectors/src/connectors/mifinity.rs
// Module: hyperswitch_connectors
// Methods: 6 total (0 public)

impl ConnectorCommon for for Mifinity


// Struct: BamboraPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct BamboraPaymentsResponse


// Implementation: impl SecretsHandler for for AnalyticsConfig
// File: crates/analytics/src/lib.rs
// Module: analytics
// Methods: 1 total (0 public)

impl SecretsHandler for for AnalyticsConfig


// File: crates/hyperswitch_domain_models/src/ext_traits.rs
// Module: hyperswitch_domain_models

use common_utils::{
    errors::{self, CustomResult},
    ext_traits::ValueExt,
    fp_utils::when,
};
use error_stack::ResultExt;

use crate::errors::api_error_response;

pub type DomainResult<T> = CustomResult<T, api_error_response::ApiErrorResponse>;
pub trait OptionExt<T> {
    fn check_value_present(&self, field_name: &'static str) -> DomainResult<()>;

    fn get_required_value(self, field_name: &'static str) -> DomainResult<T>;

    fn parse_enum<E>(self, enum_name: &'static str) -> CustomResult<E, errors::ParsingError>
    where
        T: AsRef<str>,
        E: std::str::FromStr,
        // Requirement for converting the `Err` variant of `FromStr` to `Report<Err>`
        <E as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static;

    fn parse_value<U>(self, type_name: &'static str) -> CustomResult<U, errors::ParsingError>
    where
        T: ValueExt,
        U: serde::de::DeserializeOwned;

    fn update_value(&mut self, value: Option<T>);
}

impl<T> OptionExt<T> for Option<T> {
    fn check_value_present(&self, field_name: &'static str) -> DomainResult<()> {
        when(self.is_none(), || {
            Err(error_stack::Report::new(
                api_error_response::ApiErrorResponse::MissingRequiredField { field_name },
            )
            .attach_printable(format!("Missing required field {field_name}")))
        })
    }

    // This will allow the error message that was generated in this function to point to the call site
    #[track_caller]
    fn get_required_value(self, field_name: &'static str) -> DomainResult<T> {
        match self {
            Some(v) => Ok(v),
            None => Err(error_stack::Report::new(
                api_error_response::ApiErrorResponse::MissingRequiredField { field_name },
            )
            .attach_printable(format!("Missing required field {field_name}"))),
        }
    }

    fn parse_enum<E>(self, enum_name: &'static str) -> CustomResult<E, errors::ParsingError>
    where
        T: AsRef<str>,
        E: std::str::FromStr,
        <E as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
    {
        let value = self
            .get_required_value(enum_name)
            .change_context(errors::ParsingError::UnknownError)?;

        E::from_str(value.as_ref())
            .change_context(errors::ParsingError::UnknownError)
            .attach_printable_lazy(|| format!("Invalid {{ {enum_name} }} "))
    }

    fn parse_value<U>(self, type_name: &'static str) -> CustomResult<U, errors::ParsingError>
    where
        T: ValueExt,
        U: serde::de::DeserializeOwned,
    {
        let value = self
            .get_required_value(type_name)
            .change_context(errors::ParsingError::UnknownError)?;
        value.parse_value(type_name)
    }

    fn update_value(&mut self, value: Self) {
        if let Some(a) = value {
            *self = Some(a)
        }
    }
}


// Struct: FiservResponseOrders
// File: crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct FiservResponseOrders


// Struct: PayeezyErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayeezyErrorResponse


// Module Structure
// File: crates/router/src/routes/disputes.rs
// Module: router

// Public submodules:
pub mod utils;