// File: crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs

use common_enums::enums::{AttemptStatus, BankNames, CaptureMethod, CountryAlpha2, Currency};
use common_utils::{pii::Email, request::Method};
use hyperswitch_domain_models::{
    payment_method_data::{BankRedirectData, PaymentMethodData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::{
        payments::Authorize,
        refunds::{Execute, RSync},
    },
    router_request_types::{PaymentsAuthorizeData, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::{api::CurrencyUnit, errors};
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self, CardData, RouterData as RouterDataUtils},
};

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Card {
    pub card_number: cards::CardNumber,
    pub cardholder_name: Secret<String>,
    pub cvv: Secret<String>,
    pub expiry_date: Secret<String>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CardPaymentMethod {
    pub card: Card,
    pub requires_approval: bool,
    pub payment_product_id: u16,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AmountOfMoney {
    pub amount: i64,
    pub currency_code: String,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct References {
    pub merchant_reference: String,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Order {
    pub amount_of_money: AmountOfMoney,
    pub customer: Customer,
    pub references: References,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BillingAddress {
    pub city: Option<String>,
    pub country_code: Option<CountryAlpha2>,
    pub house_number: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
    pub state_code: Option<Secret<String>>,
    pub street: Option<Secret<String>>,
    pub zip: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ContactDetails {
    pub email_address: Option<Email>,
    pub mobile_phone_number: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Customer {
    pub billing_address: BillingAddress,
    pub contact_details: Option<ContactDetails>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Name {
    pub first_name: Option<Secret<String>>,
    pub surname: Option<Secret<String>>,
    pub surname_prefix: Option<Secret<String>>,
    pub title: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Shipping {
    pub city: Option<String>,
    pub country_code: Option<CountryAlpha2>,
    pub house_number: Option<Secret<String>>,
    pub name: Option<Name>,
    pub state: Option<Secret<String>>,
    pub state_code: Option<String>,
    pub street: Option<Secret<String>>,
    pub zip: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum WorldlinePaymentMethod {
    CardPaymentMethodSpecificInput(Box<CardPaymentMethod>),
    RedirectPaymentMethodSpecificInput(Box<RedirectPaymentMethod>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RedirectPaymentMethod {
    pub payment_product_id: u16,
    pub redirection_data: RedirectionData,
    #[serde(flatten)]
    pub payment_method_specific_data: PaymentMethodSpecificData,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RedirectionData {
    pub return_url: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum PaymentMethodSpecificData {
    PaymentProduct816SpecificInput(Box<Giropay>),
    PaymentProduct809SpecificInput(Box<Ideal>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Giropay {
    pub bank_account_iban: BankAccountIban,
}

#[derive(Debug, Serialize)]
pub struct Ideal {
    #[serde(rename = "issuerId")]
    pub issuer_id: Option<WorldlineBic>,
}

#[derive(Debug, Serialize)]
pub enum WorldlineBic {
    #[serde(rename = "ABNANL2A")]
    Abnamro,
    #[serde(rename = "ASNBNL21")]
    Asn,
    #[serde(rename = "FRBKNL2L")]
    Friesland,
    #[serde(rename = "KNABNL2H")]
    Knab,
    #[serde(rename = "RABONL2U")]
    Rabobank,
    #[serde(rename = "RBRBNL21")]
    Regiobank,
    #[serde(rename = "SNSBNL2A")]
    Sns,
    #[serde(rename = "TRIONL2U")]
    Triodos,
    #[serde(rename = "FVLBNL22")]
    Vanlanschot,
    #[serde(rename = "INGBNL2A")]
    Ing,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankAccountIban {
    pub account_holder_name: Secret<String>,
    pub iban: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentsRequest {
    #[serde(flatten)]
    pub payment_data: WorldlinePaymentMethod,
    pub order: Order,
    pub shipping: Option<Shipping>,
}

#[derive(Debug, Serialize)]
pub struct WorldlineRouterData<T> {
    amount: i64,
    router_data: T,
}
impl<T> TryFrom<(&CurrencyUnit, Currency, i64, T)> for WorldlineRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (_currency_unit, _currency, amount, item): (&CurrencyUnit, Currency, i64, T),
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

impl
    TryFrom<
        &WorldlineRouterData<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>>,
    > for PaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &WorldlineRouterData<
            &RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>,
        >,
    ) -> Result<Self, Self::Error> {
        let payment_data =
            match &item.router_data.request.payment_method_data {
                PaymentMethodData::Card(card) => {
                    let card_holder_name = item.router_data.get_optional_billing_full_name();
                    WorldlinePaymentMethod::CardPaymentMethodSpecificInput(Box::new(
                        make_card_request(&item.router_data.request, card, card_holder_name)?,
                    ))
                }
                PaymentMethodData::BankRedirect(bank_redirect) => {
                    WorldlinePaymentMethod::RedirectPaymentMethodSpecificInput(Box::new(
                        make_bank_redirect_request(item.router_data, bank_redirect)?,
                    ))
                }
                PaymentMethodData::CardRedirect(_)
                | PaymentMethodData::Wallet(_)
                | PaymentMethodData::PayLater(_)
                | PaymentMethodData::BankDebit(_)
                | PaymentMethodData::BankTransfer(_)
                | PaymentMethodData::Crypto(_)
                | PaymentMethodData::MandatePayment
                | PaymentMethodData::Reward
                | PaymentMethodData::RealTimePayment(_)
                | PaymentMethodData::MobilePayment(_)
                | PaymentMethodData::Upi(_)
                | PaymentMethodData::Voucher(_)
                | PaymentMethodData::GiftCard(_)
                | PaymentMethodData::OpenBanking(_)
                | PaymentMethodData::CardToken(_)
                | PaymentMethodData::NetworkToken(_)
                | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                    Err(errors::ConnectorError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("worldline"),
                    ))?
                }
            };

        let billing_address = item.router_data.get_billing()?;

        let customer = build_customer_info(billing_address, &item.router_data.request.email)?;
        let order = Order {
            amount_of_money: AmountOfMoney {
                amount: item.amount,
                currency_code: item.router_data.request.currency.to_string().to_uppercase(),
            },
            customer,
            references: References {
                merchant_reference: item.router_data.connector_request_reference_id.clone(),
            },
        };

        let shipping = item
            .router_data
            .get_optional_shipping()
            .and_then(|shipping| shipping.address.clone())
            .map(Shipping::from);
        Ok(Self {
            payment_data,
            order,
            shipping,
        })
    }
}

#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
pub enum Gateway {
    Amex = 2,
    Discover = 128,
    MasterCard = 3,
    Visa = 1,
}

impl TryFrom<utils::CardIssuer> for Gateway {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(issuer: utils::CardIssuer) -> Result<Self, Self::Error> {
        match issuer {
            utils::CardIssuer::AmericanExpress => Ok(Self::Amex),
            utils::CardIssuer::Master => Ok(Self::MasterCard),
            utils::CardIssuer::Discover => Ok(Self::Discover),
            utils::CardIssuer::Visa => Ok(Self::Visa),
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("worldline"),
            )
            .into()),
        }
    }
}

impl TryFrom<&BankNames> for WorldlineBic {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(bank: &BankNames) -> Result<Self, Self::Error> {
        match bank {
            BankNames::AbnAmro => Ok(Self::Abnamro),
            BankNames::AsnBank => Ok(Self::Asn),
            BankNames::Ing => Ok(Self::Ing),
            BankNames::Knab => Ok(Self::Knab),
            BankNames::Rabobank => Ok(Self::Rabobank),
            BankNames::Regiobank => Ok(Self::Regiobank),
            BankNames::SnsBank => Ok(Self::Sns),
            BankNames::TriodosBank => Ok(Self::Triodos),
            BankNames::VanLanschot => Ok(Self::Vanlanschot),
            BankNames::FrieslandBank => Ok(Self::Friesland),
            _ => Err(errors::ConnectorError::FlowNotSupported {
                flow: bank.to_string(),
                connector: "Worldline".to_string(),
            }
            .into()),
        }
    }
}

fn make_card_request(
    req: &PaymentsAuthorizeData,
    ccard: &hyperswitch_domain_models::payment_method_data::Card,
    card_holder_name: Option<Secret<String>>,
) -> Result<CardPaymentMethod, error_stack::Report<errors::ConnectorError>> {
    let expiry_year = ccard.card_exp_year.peek();
    let secret_value = format!(
        "{}{}",
        ccard.card_exp_month.peek(),
        &expiry_year
            .get(expiry_year.len() - 2..)
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?
    );
    let expiry_date: Secret<String> = Secret::new(secret_value);
    let card = Card {
        card_number: ccard.card_number.clone(),
        cardholder_name: card_holder_name.unwrap_or(Secret::new("".to_string())),
        cvv: ccard.card_cvc.clone(),
        expiry_date,
    };
    #[allow(clippy::as_conversions)]
    let payment_product_id = Gateway::try_from(ccard.get_card_issuer()?)? as u16;
    let card_payment_method_specific_input = CardPaymentMethod {
        card,
        requires_approval: matches!(req.capture_method, Some(CaptureMethod::Manual)),
        payment_product_id,
    };
    Ok(card_payment_method_specific_input)
}

fn make_bank_redirect_request(
    req: &PaymentsAuthorizeRouterData,
    bank_redirect: &BankRedirectData,
) -> Result<RedirectPaymentMethod, error_stack::Report<errors::ConnectorError>> {
    let return_url = req.request.router_return_url.clone();
    let redirection_data = RedirectionData { return_url };
    let (payment_method_specific_data, payment_product_id) = match bank_redirect {
        BankRedirectData::Giropay {
            bank_account_iban, ..
        } => (
            {
                PaymentMethodSpecificData::PaymentProduct816SpecificInput(Box::new(Giropay {
                    bank_account_iban: BankAccountIban {
                        account_holder_name: req.get_billing_full_name()?.to_owned(),
                        iban: bank_account_iban.clone(),
                    },
                }))
            },
            816,
        ),
        BankRedirectData::Ideal { bank_name, .. } => (
            {
                PaymentMethodSpecificData::PaymentProduct809SpecificInput(Box::new(Ideal {
                    issuer_id: bank_name
                        .map(|bank_name| WorldlineBic::try_from(&bank_name))
                        .transpose()?,
                }))
            },
            809,
        ),
        BankRedirectData::BancontactCard { .. }
        | BankRedirectData::Bizum {}
        | BankRedirectData::Blik { .. }
        | BankRedirectData::Eft { .. }
        | BankRedirectData::Eps { .. }
        | BankRedirectData::Interac { .. }
        | BankRedirectData::OnlineBankingCzechRepublic { .. }
        | BankRedirectData::OnlineBankingFinland { .. }
        | BankRedirectData::OnlineBankingPoland { .. }
        | BankRedirectData::OnlineBankingSlovakia { .. }
        | BankRedirectData::OpenBankingUk { .. }
        | BankRedirectData::Przelewy24 { .. }
        | BankRedirectData::Sofort { .. }
        | BankRedirectData::Trustly { .. }
        | BankRedirectData::OnlineBankingFpx { .. }
        | BankRedirectData::OnlineBankingThailand { .. }
        | BankRedirectData::LocalBankRedirect {} => {
            return Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("worldline"),
            )
            .into())
        }
    };
    Ok(RedirectPaymentMethod {
        payment_product_id,
        redirection_data,
        payment_method_specific_data,
    })
}

fn get_address(
    billing: &hyperswitch_domain_models::address::Address,
) -> Option<(
    &hyperswitch_domain_models::address::Address,
    &hyperswitch_domain_models::address::AddressDetails,
)> {
    let address = billing.address.as_ref()?;
    address.country.as_ref()?;
    Some((billing, address))
}

fn build_customer_info(
    billing_address: &hyperswitch_domain_models::address::Address,
    email: &Option<Email>,
) -> Result<Customer, error_stack::Report<errors::ConnectorError>> {
    let (billing, address) =
        get_address(billing_address).ok_or(errors::ConnectorError::MissingRequiredField {
            field_name: "billing.address.country",
        })?;

    let number_with_country_code = billing.phone.as_ref().and_then(|phone| {
        phone.number.as_ref().and_then(|number| {
            phone
                .country_code
                .as_ref()
                .map(|cc| Secret::new(format!("{}{}", cc, number.peek())))
        })
    });

    Ok(Customer {
        billing_address: BillingAddress {
            ..address.clone().into()
        },
        contact_details: Some(ContactDetails {
            mobile_phone_number: number_with_country_code,
            email_address: email.clone(),
        }),
    })
}

impl From<hyperswitch_domain_models::address::AddressDetails> for BillingAddress {
    fn from(value: hyperswitch_domain_models::address::AddressDetails) -> Self {
        Self {
            city: value.city,
            country_code: value.country,
            state: value.state,
            zip: value.zip,
            ..Default::default()
        }
    }
}

impl From<hyperswitch_domain_models::address::AddressDetails> for Shipping {
    fn from(value: hyperswitch_domain_models::address::AddressDetails) -> Self {
        Self {
            city: value.city,
            country_code: value.country,
            name: Some(Name {
                first_name: value.first_name,
                surname: value.last_name,
                ..Default::default()
            }),
            state: value.state,
            zip: value.zip,
            ..Default::default()
        }
    }
}

pub struct WorldlineAuthType {
    pub api_key: Secret<String>,
    pub api_secret: Secret<String>,
    pub merchant_account_id: Secret<String>,
}

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

#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentStatus {
    Captured,
    Paid,
    ChargebackNotification,
    Cancelled,
    Rejected,
    RejectedCapture,
    PendingApproval,
    CaptureRequested,
    #[default]
    Processing,
    Created,
    Redirected,
}

fn get_status(item: (PaymentStatus, CaptureMethod)) -> AttemptStatus {
    let (status, capture_method) = item;
    match status {
        PaymentStatus::Captured | PaymentStatus::Paid | PaymentStatus::ChargebackNotification => {
            AttemptStatus::Charged
        }
        PaymentStatus::Cancelled => AttemptStatus::Voided,
        PaymentStatus::Rejected => AttemptStatus::Failure,
        PaymentStatus::RejectedCapture => AttemptStatus::CaptureFailed,
        PaymentStatus::CaptureRequested => {
            if matches!(
                capture_method,
                CaptureMethod::Automatic | CaptureMethod::SequentialAutomatic
            ) {
                AttemptStatus::Pending
            } else {
                AttemptStatus::CaptureInitiated
            }
        }

        PaymentStatus::PendingApproval => AttemptStatus::Authorized,
        PaymentStatus::Created => AttemptStatus::Started,
        PaymentStatus::Redirected => AttemptStatus::AuthenticationPending,
        _ => AttemptStatus::Pending,
    }
}

/// capture_method is not part of response from connector.
/// This is used to decide payment status while converting connector response to RouterData.
/// To keep this try_from logic generic in case of AUTHORIZE, SYNC and CAPTURE flows capture_method will be set from RouterData request.
#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct Payment {
    pub id: String,
    pub status: PaymentStatus,
    #[serde(skip_deserializing)]
    pub capture_method: CaptureMethod,
}

impl<F, T> TryFrom<ResponseRouterData<F, Payment, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, Payment, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: get_status((item.response.status, item.response.capture_method)),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.id.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.id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentResponse {
    pub payment: Payment,
    pub merchant_action: Option<MerchantAction>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantAction {
    pub redirect_data: RedirectData,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RedirectData {
    #[serde(rename = "redirectURL")]
    pub redirect_url: Url,
}

impl<F, T> TryFrom<ResponseRouterData<F, PaymentResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PaymentResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let redirection_data = item
            .response
            .merchant_action
            .map(|action| action.redirect_data.redirect_url)
            .map(|redirect_url| RedirectForm::from((redirect_url, Method::Get)));
        Ok(Self {
            status: get_status((
                item.response.payment.status,
                item.response.payment.capture_method,
            )),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payment.id.clone()),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.payment.id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}
#[derive(Default, Debug, Serialize)]
pub struct ApproveRequest {}

impl TryFrom<&PaymentsCaptureRouterData> for ApproveRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(_item: &PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Ok(Self {})
    }
}

#[derive(Default, Debug, Serialize)]
pub struct WorldlineRefundRequest {
    amount_of_money: AmountOfMoney,
}

impl<F> TryFrom<&RefundsRouterData<F>> for WorldlineRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundsRouterData<F>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount_of_money: AmountOfMoney {
                amount: item.request.refund_amount,
                currency_code: item.request.currency.to_string(),
            },
        })
    }
}

#[allow(dead_code)]
#[derive(Debug, Default, Deserialize, Clone, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RefundStatus {
    Cancelled,
    Rejected,
    Refunded,
    #[default]
    Processing,
}

impl From<RefundStatus> for common_enums::enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Refunded => Self::Success,
            RefundStatus::Cancelled | RefundStatus::Rejected => Self::Failure,
            RefundStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        let refund_status = common_enums::enums::RefundStatus::from(item.response.status);
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.clone(),
                refund_status,
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        let refund_status = common_enums::enums::RefundStatus::from(item.response.status);
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.clone(),
                refund_status,
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Error {
    pub code: Option<String>,
    pub property_name: Option<String>,
    pub message: Option<String>,
}

#[derive(Default, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
    pub error_id: Option<String>,
    pub errors: Vec<Error>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WebhookBody {
    pub api_version: Option<String>,
    pub id: String,
    pub created: String,
    pub merchant_id: common_utils::id_type::MerchantId,
    #[serde(rename = "type")]
    pub event_type: WebhookEvent,
    pub payment: Option<serde_json::Value>,
    pub refund: Option<serde_json::Value>,
    pub payout: Option<serde_json::Value>,
    pub token: Option<serde_json::Value>,
}

#[derive(Debug, Deserialize)]
pub enum WebhookEvent {
    #[serde(rename = "payment.rejected")]
    Rejected,
    #[serde(rename = "payment.rejected_capture")]
    RejectedCapture,
    #[serde(rename = "payment.paid")]
    Paid,
    #[serde(other)]
    Unknown,
}


// File: crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs

use std::collections::HashMap;

use cards::CardNumber;
use common_enums::{enums, PaymentMethod};
use common_utils::{ext_traits::ValueExt, pii::Email, types::MinorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankDebitData, PaymentMethodData},
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        payments::{Authorize, Capture, CompleteAuthorize, PSync},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCaptureData, PaymentsSyncData,
        ResponseId,
    },
    router_response_types::{
        MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsCompleteAuthorizeRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{consts, errors};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{PaymentsCancelResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData,
        PaymentsCompleteAuthorizeRequestData, RefundsRequestData, RouterData as OtherRouterData,
    },
};

pub struct DeutschebankRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

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

pub struct DeutschebankAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) merchant_id: Secret<String>,
    pub(super) client_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for DeutschebankAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Ok(Self {
                client_id: api_key.to_owned(),
                merchant_id: key1.to_owned(),
                client_key: api_secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct DeutschebankAccessTokenRequest {
    pub grant_type: String,
    pub client_id: Secret<String>,
    pub client_secret: Secret<String>,
    pub scope: String,
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct DeutschebankAccessTokenResponse {
    pub access_token: Secret<String>,
    pub expires_in: i64,
    pub expires_on: i64,
    pub scope: String,
    pub token_type: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, DeutschebankAccessTokenResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, DeutschebankAccessTokenResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(AccessToken {
                token: item.response.access_token,
                expires: item.response.expires_in,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DeutschebankSEPAApproval {
    Click,
    Email,
    Sms,
    Dynamic,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankMandatePostRequest {
    approval_by: DeutschebankSEPAApproval,
    email_address: Email,
    iban: Secret<String>,
    first_name: Secret<String>,
    last_name: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum DeutschebankPaymentsRequest {
    MandatePost(DeutschebankMandatePostRequest),
    DirectDebit(DeutschebankDirectDebitRequest),
    CreditCard(Box<DeutschebankThreeDSInitializeRequest>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequest {
    means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment,
    tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data,
    amount_total: DeutschebankThreeDSInitializeRequestAmountTotal,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestMeansOfPayment {
    credit_card: DeutschebankThreeDSInitializeRequestCreditCard,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestCreditCard {
    number: CardNumber,
    expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry,
    code: Secret<String>,
    cardholder: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry {
    year: Secret<String>,
    month: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestAmountTotal {
    amount: MinorUnit,
    currency: api_models::enums::Currency,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestTds20Data {
    communication_data: DeutschebankThreeDSInitializeRequestCommunicationData,
    customer_data: DeutschebankThreeDSInitializeRequestCustomerData,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestCommunicationData {
    method_notification_url: String,
    cres_notification_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestCustomerData {
    billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData,
    cardholder_email: Email,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct DeutschebankThreeDSInitializeRequestCustomerBillingData {
    street: Secret<String>,
    postal_code: Secret<String>,
    city: String,
    state: Secret<String>,
    country: String,
}

impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>>
    for DeutschebankPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &DeutschebankRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item
            .router_data
            .request
            .mandate_id
            .clone()
            .and_then(|mandate_id| mandate_id.mandate_reference_id)
        {
            None => {
                // To facilitate one-off payments via SEPA with Deutsche Bank, we are considering not storing the connector mandate ID in our system if future usage is on-session.
                // We will only check for customer acceptance to make a one-off payment. we will be storing the connector mandate details only when setup future usage is off-session.
                match item.router_data.request.payment_method_data.clone() {
                    PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => {
                        if item.router_data.request.customer_acceptance.is_some() {
                            let billing_address = item.router_data.get_billing_address()?;
                            Ok(Self::MandatePost(DeutschebankMandatePostRequest {
                                approval_by: DeutschebankSEPAApproval::Click,
                                email_address: item.router_data.request.get_email()?,
                                iban: Secret::from(iban.peek().replace(" ", "")),
                                first_name: billing_address.get_first_name()?.clone(),
                                last_name: billing_address.get_last_name()?.clone(),
                            }))
                        } else {
                            Err(errors::ConnectorError::MissingRequiredField {
                                field_name: "customer_acceptance",
                            }
                            .into())
                        }
                    }
                    PaymentMethodData::Card(ccard) => {
                        if !item.router_data.clone().is_three_ds() {
                            Err(errors::ConnectorError::NotSupported {
                                message: "Non-ThreeDs".to_owned(),
                                connector: "deutschebank",
                            }
                            .into())
                        } else {
                            let billing_address = item.router_data.get_billing_address()?;
                            Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest {
                                    means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment {
                                        credit_card: DeutschebankThreeDSInitializeRequestCreditCard {
                                            number: ccard.clone().card_number,
                                            expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry {
                                                year: ccard.get_expiry_year_4_digit(),
                                                month: ccard.card_exp_month,
                                            },
                                            code: ccard.card_cvc,
                                            cardholder: item.router_data.get_billing_full_name()?,
                                        }},
                                    amount_total: DeutschebankThreeDSInitializeRequestAmountTotal {
                                        amount: item.amount,
                                        currency: item.router_data.request.currency,
                                    },
                                    tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data {
                                        communication_data: DeutschebankThreeDSInitializeRequestCommunicationData {
                                            method_notification_url: item.router_data.request.get_complete_authorize_url()?,
                                            cres_notification_url: item.router_data.request.get_complete_authorize_url()?,
                                        },
                                        customer_data: DeutschebankThreeDSInitializeRequestCustomerData {
                                            billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData {
                                                street: billing_address.get_line1()?.clone(),
                                                postal_code: billing_address.get_zip()?.clone(),
                                                city: billing_address.get_city()?.to_string(),
                                                state: billing_address.get_state()?.clone(),
                                                country: item.router_data.get_billing_country()?.to_string(),
                                            },
                                            cardholder_email: item.router_data.request.get_email()?,
                                        }
                                    }
                                })))
                        }
                    }
                    _ => Err(errors::ConnectorError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("deutschebank"),
                    )
                    .into()),
                }
            }
            Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => {
                let mandate_metadata: DeutschebankMandateMetadata = mandate_data
                    .get_mandate_metadata()
                    .ok_or(errors::ConnectorError::MissingConnectorMandateMetadata)?
                    .clone()
                    .parse_value("DeutschebankMandateMetadata")
                    .change_context(errors::ConnectorError::ParsingFailed)?;
                Ok(Self::DirectDebit(DeutschebankDirectDebitRequest {
                    amount_total: DeutschebankAmount {
                        amount: item.amount,
                        currency: item.router_data.request.currency,
                    },
                    means_of_payment: DeutschebankMeansOfPayment {
                        bank_account: DeutschebankBankAccount {
                            account_holder: mandate_metadata.account_holder,
                            iban: mandate_metadata.iban,
                        },
                    },
                    mandate: DeutschebankMandate {
                        reference: mandate_metadata.reference,
                        signed_on: mandate_metadata.signed_on,
                    },
                }))
            }
            Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_))
            | Some(api_models::payments::MandateReferenceId::NetworkMandateId(_)) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("deutschebank"),
                )
                .into())
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankThreeDSInitializeResponse {
    outcome: DeutschebankThreeDSInitializeResponseOutcome,
    challenge_required: Option<DeutschebankThreeDSInitializeResponseChallengeRequired>,
    processed: Option<DeutschebankThreeDSInitializeResponseProcessed>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankThreeDSInitializeResponseProcessed {
    rc: String,
    message: String,
    tx_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeutschebankThreeDSInitializeResponseOutcome {
    Processed,
    ChallengeRequired,
    MethodRequired,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankThreeDSInitializeResponseChallengeRequired {
    acs_url: String,
    creq: String,
}

impl
    TryFrom<
        ResponseRouterData<
            Authorize,
            DeutschebankThreeDSInitializeResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            DeutschebankThreeDSInitializeResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        match item.response.outcome {
            DeutschebankThreeDSInitializeResponseOutcome::Processed => {
                match item.response.processed {
                    Some(processed) => Ok(Self {
                        status: if is_response_success(&processed.rc) {
                            match item.data.request.is_auto_capture()? {
                                true => common_enums::AttemptStatus::Charged,
                                false => common_enums::AttemptStatus::Authorized,
                            }
                        } else {
                            common_enums::AttemptStatus::AuthenticationFailed
                        },
                        response: Ok(PaymentsResponseData::TransactionResponse {
                            resource_id: ResponseId::ConnectorTransactionId(
                                processed.tx_id.clone(),
                            ),
                            redirection_data: Box::new(None),
                            mandate_reference: Box::new(None),
                            connector_metadata: None,
                            network_txn_id: None,
                            connector_response_reference_id: Some(processed.tx_id.clone()),
                            incremental_authorization_allowed: None,
                            charges: None,
                        }),
                        ..item.data
                    }),
                    None => {
                        let response_string = format!("{:?}", item.response);
                        Err(
                            errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from(
                                response_string,
                            ))
                            .into(),
                        )
                    }
                }
            }
            DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => {
                match item.response.challenge_required {
                    Some(challenge) => Ok(Self {
                        status: common_enums::AttemptStatus::AuthenticationPending,
                        response: Ok(PaymentsResponseData::TransactionResponse {
                            resource_id: ResponseId::NoResponseId,
                            redirection_data: Box::new(Some(
                                RedirectForm::DeutschebankThreeDSChallengeFlow {
                                    acs_url: challenge.acs_url,
                                    creq: challenge.creq,
                                },
                            )),
                            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
                    }),
                    None => {
                        let response_string = format!("{:?}", item.response);
                        Err(
                            errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from(
                                response_string,
                            ))
                            .into(),
                        )
                    }
                }
            }
            DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(ErrorResponse {
                    code: consts::NO_ERROR_CODE.to_owned(),
                    message: "METHOD_REQUIRED Flow not supported for deutschebank 3ds payments".to_owned(),
                    reason: Some("METHOD_REQUIRED Flow is not currently supported for deutschebank 3ds payments".to_owned()),
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: None,network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                }),
                ..item.data
            }),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeutschebankSEPAMandateStatus {
    Created,
    PendingApproval,
    PendingSecondaryApproval,
    PendingReview,
    PendingSubmission,
    Submitted,
    Active,
    Failed,
    Discarded,
    Expired,
    Replaced,
}

impl From<DeutschebankSEPAMandateStatus> for common_enums::AttemptStatus {
    fn from(item: DeutschebankSEPAMandateStatus) -> Self {
        match item {
            DeutschebankSEPAMandateStatus::Active
            | DeutschebankSEPAMandateStatus::Created
            | DeutschebankSEPAMandateStatus::PendingApproval
            | DeutschebankSEPAMandateStatus::PendingSecondaryApproval
            | DeutschebankSEPAMandateStatus::PendingReview
            | DeutschebankSEPAMandateStatus::PendingSubmission
            | DeutschebankSEPAMandateStatus::Submitted => Self::AuthenticationPending,
            DeutschebankSEPAMandateStatus::Failed
            | DeutschebankSEPAMandateStatus::Discarded
            | DeutschebankSEPAMandateStatus::Expired
            | DeutschebankSEPAMandateStatus::Replaced => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankMandateMetadata {
    account_holder: Secret<String>,
    iban: Secret<String>,
    reference: Secret<String>,
    signed_on: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankMandatePostResponse {
    rc: String,
    message: String,
    mandate_id: Option<String>,
    reference: Option<String>,
    approval_date: Option<String>,
    language: Option<String>,
    approval_by: Option<DeutschebankSEPAApproval>,
    state: Option<DeutschebankSEPAMandateStatus>,
}

fn get_error_response(error_code: String, error_reason: String, status_code: u16) -> ErrorResponse {
    ErrorResponse {
        code: error_code.to_string(),
        message: error_reason.clone(),
        reason: Some(error_reason),
        status_code,
        attempt_status: None,
        connector_transaction_id: None,
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}

fn is_response_success(rc: &String) -> bool {
    rc == "0"
}

impl
    TryFrom<
        ResponseRouterData<
            Authorize,
            DeutschebankMandatePostResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            DeutschebankMandatePostResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let signed_on = match item.response.approval_date.clone() {
            Some(date) => date.chars().take(10).collect(),
            None => time::OffsetDateTime::now_utc().date().to_string(),
        };
        let response_code = item.response.rc.clone();
        let is_response_success = is_response_success(&response_code);

        match (
            item.response.reference.clone(),
            item.response.state.clone(),
            is_response_success,
        ) {
            (Some(reference), Some(state), true) => Ok(Self {
                status: common_enums::AttemptStatus::from(state),
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::NoResponseId,
                    redirection_data: Box::new(Some(RedirectForm::Form {
                        endpoint: item.data.request.get_complete_authorize_url()?,
                        method: common_utils::request::Method::Get,
                        form_fields: HashMap::from([
                            ("reference".to_string(), reference.clone()),
                            ("signed_on".to_string(), signed_on.clone()),
                        ]),
                    })),
                    mandate_reference: if item.data.request.is_mandate_payment() {
                        Box::new(Some(MandateReference {
                            connector_mandate_id: item.response.mandate_id,
                            payment_method_id: None,
                            mandate_metadata: Some(Secret::new(
                                serde_json::json!(DeutschebankMandateMetadata {
                                account_holder: item.data.get_billing_address()?.get_full_name()?,
                                iban: match item.data.request.payment_method_data.clone() {
                                    PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit {
                                        iban,
                                        ..
                                    }) => Ok(Secret::from(iban.peek().replace(" ", ""))),
                                    _ => Err(errors::ConnectorError::MissingRequiredField {
                                        field_name:
                                            "payment_method_data.bank_debit.sepa_bank_debit.iban"
                                    }),
                                }?,
                                reference: Secret::from(reference.clone()),
                                signed_on,
                            }),
                            )),
                            connector_mandate_request_reference_id: None,
                        }))
                    } else {
                        Box::new(None)
                    },
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: None,
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                ..item.data
            }),
            _ => Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            }),
        }
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Authorize,
            DeutschebankPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            DeutschebankPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        if is_response_success(&response_code) {
            Ok(Self {
                status: match item.data.request.is_auto_capture()? {
                    true => common_enums::AttemptStatus::Charged,
                    false => common_enums::AttemptStatus::Authorized,
                },
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(item.response.tx_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
            })
        } else {
            Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            })
        }
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DeutschebankAmount {
    amount: MinorUnit,
    currency: api_models::enums::Currency,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankMeansOfPayment {
    bank_account: DeutschebankBankAccount,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankBankAccount {
    account_holder: Secret<String>,
    iban: Secret<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankMandate {
    reference: Secret<String>,
    signed_on: String,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankDirectDebitRequest {
    amount_total: DeutschebankAmount,
    means_of_payment: DeutschebankMeansOfPayment,
    mandate: DeutschebankMandate,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum DeutschebankCompleteAuthorizeRequest {
    DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest),
    DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest),
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankThreeDSCompleteAuthorizeRequest {
    cres: String,
}

impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>>
    for DeutschebankCompleteAuthorizeRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if matches!(item.router_data.payment_method, PaymentMethod::Card) {
            let redirect_response_payload = item
                .router_data
                .request
                .get_redirect_response_payload()?
                .expose();

            let cres = redirect_response_payload
                .get("cres")
                .and_then(|v| v.as_str())
                .map(String::from)
                .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })?;

            Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest(
                DeutschebankThreeDSCompleteAuthorizeRequest { cres },
            ))
        } else {
            match item.router_data.request.payment_method_data.clone() {
                Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit {
                    iban, ..
                })) => {
                    let account_holder = item.router_data.get_billing_address()?.get_full_name()?;
                    let redirect_response =
                        item.router_data.request.redirect_response.clone().ok_or(
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "redirect_response",
                            },
                        )?;
                    let queries_params = redirect_response
                        .params
                        .map(|param| {
                            let mut queries = HashMap::<String, String>::new();
                            let values = param.peek().split('&').collect::<Vec<&str>>();
                            for value in values {
                                let pair = value.split('=').collect::<Vec<&str>>();
                                queries.insert(
                                    pair.first()
                                        .ok_or(
                                            errors::ConnectorError::ResponseDeserializationFailed,
                                        )?
                                        .to_string(),
                                    pair.get(1)
                                        .ok_or(
                                            errors::ConnectorError::ResponseDeserializationFailed,
                                        )?
                                        .to_string(),
                                );
                            }
                            Ok::<_, errors::ConnectorError>(queries)
                        })
                        .transpose()?
                        .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?;
                    let reference = Secret::from(
                        queries_params
                            .get("reference")
                            .ok_or(errors::ConnectorError::MissingRequiredField {
                                field_name: "reference",
                            })?
                            .to_owned(),
                    );
                    let signed_on = queries_params
                        .get("signed_on")
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "signed_on",
                        })?
                        .to_owned();
                    Ok(Self::DeutschebankDirectDebitRequest(
                        DeutschebankDirectDebitRequest {
                            amount_total: DeutschebankAmount {
                                amount: item.amount,
                                currency: item.router_data.request.currency,
                            },
                            means_of_payment: DeutschebankMeansOfPayment {
                                bank_account: DeutschebankBankAccount {
                                    account_holder,
                                    iban: Secret::from(iban.peek().replace(" ", "")),
                                },
                            },
                            mandate: {
                                DeutschebankMandate {
                                    reference,
                                    signed_on,
                                }
                            },
                        },
                    ))
                }
                _ => Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("deutschebank"),
                )
                .into()),
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum DeutschebankTXAction {
    Authorization,
    Capture,
    Credit,
    Preauthorization,
    Refund,
    Reversal,
    RiskCheck,
    #[serde(rename = "verify-mop")]
    VerifyMop,
    Payment,
    AccountInformation,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct BankAccount {
    account_holder: Option<Secret<String>>,
    bank_name: Option<Secret<String>>,
    bic: Option<Secret<String>>,
    iban: Option<Secret<String>>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct TransactionBankAccountInfo {
    bank_account: Option<BankAccount>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct DeutschebankTransactionInfo {
    back_state: Option<String>,
    ip_address: Option<Secret<String>>,
    #[serde(rename = "type")]
    pm_type: Option<String>,
    transaction_bankaccount_info: Option<TransactionBankAccountInfo>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct DeutschebankPaymentsResponse {
    rc: String,
    message: String,
    timestamp: String,
    back_ext_id: Option<String>,
    back_rc: Option<String>,
    event_id: Option<String>,
    kind: Option<String>,
    tx_action: Option<DeutschebankTXAction>,
    tx_id: String,
    amount_total: Option<DeutschebankAmount>,
    transaction_info: Option<DeutschebankTransactionInfo>,
}

impl
    TryFrom<
        ResponseRouterData<
            CompleteAuthorize,
            DeutschebankPaymentsResponse,
            CompleteAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<CompleteAuthorize, CompleteAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            CompleteAuthorize,
            DeutschebankPaymentsResponse,
            CompleteAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        if is_response_success(&response_code) {
            Ok(Self {
                status: match item.data.request.is_auto_capture()? {
                    true => common_enums::AttemptStatus::Charged,
                    false => common_enums::AttemptStatus::Authorized,
                },
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(item.response.tx_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
            })
        } else {
            Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            })
        }
    }
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DeutschebankTransactionKind {
    Directdebit,
    #[serde(rename = "CREDITCARD_3DS20")]
    Creditcard3ds20,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct DeutschebankCaptureRequest {
    changed_amount: MinorUnit,
    kind: DeutschebankTransactionKind,
}

impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for DeutschebankCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &DeutschebankRouterData<&PaymentsCaptureRouterData>,
    ) -> Result<Self, Self::Error> {
        if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) {
            Ok(Self {
                changed_amount: item.amount,
                kind: DeutschebankTransactionKind::Directdebit,
            })
        } else if item.router_data.is_three_ds()
            && matches!(item.router_data.payment_method, PaymentMethod::Card)
        {
            Ok(Self {
                changed_amount: item.amount,
                kind: DeutschebankTransactionKind::Creditcard3ds20,
            })
        } else {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("deutschebank"),
            )
            .into())
        }
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Capture,
            DeutschebankPaymentsResponse,
            PaymentsCaptureData,
            PaymentsResponseData,
        >,
    > for RouterData<Capture, PaymentsCaptureData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Capture,
            DeutschebankPaymentsResponse,
            PaymentsCaptureData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        if is_response_success(&response_code) {
            Ok(Self {
                status: common_enums::AttemptStatus::Charged,
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(item.response.tx_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
            })
        } else {
            Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            })
        }
    }
}

impl
    TryFrom<
        ResponseRouterData<
            PSync,
            DeutschebankPaymentsResponse,
            PaymentsSyncData,
            PaymentsResponseData,
        >,
    > for RouterData<PSync, PaymentsSyncData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            PSync,
            DeutschebankPaymentsResponse,
            PaymentsSyncData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        let status = if is_response_success(&response_code) {
            item.response
                .tx_action
                .and_then(|tx_action| match tx_action {
                    DeutschebankTXAction::Preauthorization => {
                        Some(common_enums::AttemptStatus::Authorized)
                    }
                    DeutschebankTXAction::Authorization | DeutschebankTXAction::Capture => {
                        Some(common_enums::AttemptStatus::Charged)
                    }
                    DeutschebankTXAction::Credit
                    | DeutschebankTXAction::Refund
                    | DeutschebankTXAction::Reversal
                    | DeutschebankTXAction::RiskCheck
                    | DeutschebankTXAction::VerifyMop
                    | DeutschebankTXAction::Payment
                    | DeutschebankTXAction::AccountInformation => None,
                })
        } else {
            Some(common_enums::AttemptStatus::Failure)
        };
        match status {
            Some(common_enums::AttemptStatus::Failure) => Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            }),
            Some(status) => Ok(Self {
                status,
                ..item.data
            }),
            None => Ok(Self { ..item.data }),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct DeutschebankReversalRequest {
    kind: DeutschebankTransactionKind,
}

impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        if matches!(item.payment_method, PaymentMethod::BankDebit) {
            Ok(Self {
                kind: DeutschebankTransactionKind::Directdebit,
            })
        } else if item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) {
            Ok(Self {
                kind: DeutschebankTransactionKind::Creditcard3ds20,
            })
        } else {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("deutschebank"),
            )
            .into())
        }
    }
}

impl TryFrom<PaymentsCancelResponseRouterData<DeutschebankPaymentsResponse>>
    for PaymentsCancelRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCancelResponseRouterData<DeutschebankPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        if is_response_success(&response_code) {
            Ok(Self {
                status: common_enums::AttemptStatus::Voided,
                ..item.data
            })
        } else {
            Ok(Self {
                status: common_enums::AttemptStatus::VoidFailed,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            })
        }
    }
}

#[derive(Debug, Serialize)]
pub struct DeutschebankRefundRequest {
    changed_amount: MinorUnit,
    kind: DeutschebankTransactionKind,
}

impl<F> TryFrom<&DeutschebankRouterData<&RefundsRouterData<F>>> for DeutschebankRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &DeutschebankRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) {
            Ok(Self {
                changed_amount: item.amount,
                kind: DeutschebankTransactionKind::Directdebit,
            })
        } else if item.router_data.is_three_ds()
            && matches!(item.router_data.payment_method, PaymentMethod::Card)
        {
            Ok(Self {
                changed_amount: item.amount,
                kind: DeutschebankTransactionKind::Creditcard3ds20,
            })
        } else {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("deutschebank"),
            )
            .into())
        }
    }
}

impl TryFrom<RefundsResponseRouterData<Execute, DeutschebankPaymentsResponse>>
    for RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, DeutschebankPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        if is_response_success(&response_code) {
            Ok(Self {
                response: Ok(RefundsResponseData {
                    connector_refund_id: item.response.tx_id,
                    refund_status: enums::RefundStatus::Success,
                }),
                ..item.data
            })
        } else {
            Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            })
        }
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, DeutschebankPaymentsResponse>>
    for RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, DeutschebankPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let response_code = item.response.rc.clone();
        let status = if is_response_success(&response_code) {
            item.response
                .tx_action
                .and_then(|tx_action| match tx_action {
                    DeutschebankTXAction::Credit | DeutschebankTXAction::Refund => {
                        Some(enums::RefundStatus::Success)
                    }
                    DeutschebankTXAction::Preauthorization
                    | DeutschebankTXAction::Authorization
                    | DeutschebankTXAction::Capture
                    | DeutschebankTXAction::Reversal
                    | DeutschebankTXAction::RiskCheck
                    | DeutschebankTXAction::VerifyMop
                    | DeutschebankTXAction::Payment
                    | DeutschebankTXAction::AccountInformation => None,
                })
        } else {
            Some(enums::RefundStatus::Failure)
        };

        match status {
            Some(enums::RefundStatus::Failure) => Ok(Self {
                status: common_enums::AttemptStatus::Failure,
                response: Err(get_error_response(
                    response_code.clone(),
                    item.response.message.clone(),
                    item.http_code,
                )),
                ..item.data
            }),
            Some(refund_status) => Ok(Self {
                response: Ok(RefundsResponseData {
                    refund_status,
                    connector_refund_id: item.data.request.get_connector_refund_id()?,
                }),
                ..item.data
            }),
            None => Ok(Self { ..item.data }),
        }
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct PaymentsErrorResponse {
    pub rc: String,
    pub message: String,
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct AccessTokenErrorResponse {
    pub cause: String,
    pub description: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum DeutschebankError {
    PaymentsErrorResponse(PaymentsErrorResponse),
    AccessTokenErrorResponse(AccessTokenErrorResponse),
}


// File: crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs

use std::collections::HashMap;

use api_models::payments::PollConfig;
use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    pii::{self, Email, IpAddress},
    types::MinorUnit,
};
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, UpiData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime};

use crate::{
    types::{
        CreateOrderResponseRouterData, PaymentsResponseRouterData, RefundsResponseRouterData,
        ResponseRouterData,
    },
    utils::{
        get_unimplemented_payment_method_error_message, missing_field_err,
        PaymentsAuthorizeRequestData, RouterData as OtherRouterData,
    },
};

pub struct RazorpayRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

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

pub const VERSION: i32 = 1;

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayOrderRequest {
    pub amount: MinorUnit,
    pub currency: enums::Currency,
    pub receipt: String,
    pub partial_payment: Option<bool>,
    pub first_payment_min_amount: Option<MinorUnit>,
    pub notes: Option<RazorpayNotes>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RazorpayNotes {
    Map(HashMap<String, String>),
    EmptyMap(HashMap<String, String>),
}

impl TryFrom<&RazorpayRouterData<&types::CreateOrderRouterData>> for RazorpayOrderRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &RazorpayRouterData<&types::CreateOrderRouterData>,
    ) -> Result<Self, Self::Error> {
        let currency = item.router_data.request.currency;
        let receipt = item.router_data.connector_request_reference_id.clone();

        Ok(Self {
            amount: item.amount,
            currency,
            receipt,
            partial_payment: None,
            first_payment_min_amount: None,
            notes: None,
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RazorpayMetaData {
    pub order_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayOrderResponse {
    pub id: String,
}

impl TryFrom<CreateOrderResponseRouterData<RazorpayOrderResponse>>
    for types::CreateOrderRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: CreateOrderResponseRouterData<RazorpayOrderResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::PaymentsCreateOrderResponse {
                order_id: item.response.id.clone(),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct UpiDetails {
    flow: UpiFlow,
    vpa: Secret<String, pii::UpiVpaMaskingStrategy>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum UpiFlow {
    Collect,
    Intent,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayPaymentsRequest {
    amount: MinorUnit,
    currency: enums::Currency,
    order_id: String,
    email: Email,
    contact: Secret<String>,
    method: RazorpayPaymentMethod,
    upi: UpiDetails,
    #[serde(skip_serializing_if = "Option::is_none")]
    ip: Option<Secret<String, IpAddress>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    user_agent: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RazorpayPaymentMethod {
    Upi,
}

impl TryFrom<&RazorpayRouterData<&types::PaymentsAuthorizeRouterData>> for RazorpayPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let payment_router_data = item.router_data;
        let router_request = &payment_router_data.request;
        let payment_method_data = &router_request.payment_method_data;

        let (razorpay_payment_method, upi_details) = match payment_method_data {
            PaymentMethodData::Upi(upi_type_data) => match upi_type_data {
                UpiData::UpiCollect(upi_collect_data) => {
                    let vpa_secret = upi_collect_data
                        .vpa_id
                        .clone()
                        .ok_or_else(missing_field_err("payment_method_data.upi.collect.vpa_id"))?;
                    (
                        RazorpayPaymentMethod::Upi,
                        UpiDetails {
                            flow: UpiFlow::Collect,
                            vpa: vpa_secret,
                        },
                    )
                }
                UpiData::UpiIntent(_) | UpiData::UpiQr(_) => {
                    Err(errors::ConnectorError::NotImplemented(
                        get_unimplemented_payment_method_error_message("razorpay"),
                    ))?
                }
            },
            _ => Err(errors::ConnectorError::NotImplemented(
                get_unimplemented_payment_method_error_message("razorpay"),
            ))?,
        };

        let contact_number = item.router_data.get_billing_phone_number()?;
        let order_id = router_request.get_order_id()?;
        let email = item.router_data.get_billing_email()?;
        let ip = router_request.get_ip_address_as_optional();
        let user_agent = router_request.get_optional_user_agent();

        Ok(Self {
            amount: item.amount,
            currency: router_request.currency,
            order_id,
            email,
            contact: contact_number,
            method: razorpay_payment_method,
            upi: upi_details,
            ip,
            user_agent,
        })
    }
}

pub struct RazorpayAuthType {
    pub(super) razorpay_id: Secret<String>,
    pub(super) razorpay_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for RazorpayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
                razorpay_id: api_key.to_owned(),
                razorpay_secret: key1.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NextAction {
    pub action: String,
    pub url: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RazorpayPaymentsResponse {
    pub razorpay_payment_id: String,
}

impl TryFrom<PaymentsResponseRouterData<RazorpayPaymentsResponse>>
    for types::PaymentsAuthorizeRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsResponseRouterData<RazorpayPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let connector_metadata = get_wait_screen_metadata()?;
        let order_id = item.data.request.get_order_id()?;
        Ok(Self {
            status: enums::AttemptStatus::AuthenticationPending,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(
                    item.response.razorpay_payment_id.clone(),
                ),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata,
                network_txn_id: None,
                connector_response_reference_id: Some(order_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WaitScreenData {
    display_from_timestamp: i128,
    display_to_timestamp: Option<i128>,
    poll_config: Option<PollConfig>,
}

pub fn get_wait_screen_metadata() -> CustomResult<Option<serde_json::Value>, errors::ConnectorError>
{
    let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos();
    Ok(Some(serde_json::json!(WaitScreenData {
        display_from_timestamp: current_time,
        display_to_timestamp: Some(current_time + Duration::minutes(5).whole_nanoseconds()),
        poll_config: Some(PollConfig {
            delay_in_secs: 5,
            frequency: 5,
        }),
    })))
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RazorpaySyncResponse {
    items: Vec<RazorpaySyncItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RazorpaySyncItem {
    id: String,
    status: RazorpayStatus,
}

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RazorpayStatus {
    Created,
    Authorized,
    Captured,
    Refunded,
    Failed,
}

fn get_psync_razorpay_payment_status(razorpay_status: RazorpayStatus) -> enums::AttemptStatus {
    match razorpay_status {
        RazorpayStatus::Created => enums::AttemptStatus::Pending,
        RazorpayStatus::Authorized => enums::AttemptStatus::Authorized,
        RazorpayStatus::Captured | RazorpayStatus::Refunded => enums::AttemptStatus::Charged,
        RazorpayStatus::Failed => enums::AttemptStatus::Failure,
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, RazorpaySyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, RazorpaySyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = match item.response.items.last() {
            Some(last_item) => {
                let razorpay_status = last_item.status;
                get_psync_razorpay_payment_status(razorpay_status)
            }
            None => item.data.status,
        };
        Ok(Self {
            status,
            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
        })
    }
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayRefundRequest {
    pub amount: MinorUnit,
}

impl<F> TryFrom<&RazorpayRouterData<&types::RefundsRouterData<F>>> for RazorpayRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &RazorpayRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount,
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayRefundResponse {
    pub id: String,
    pub status: RazorpayRefundStatus,
}

#[derive(Debug, Serialize, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RazorpayRefundStatus {
    Created,
    Processed,
    Failed,
    Pending,
}

impl From<RazorpayRefundStatus> for enums::RefundStatus {
    fn from(item: RazorpayRefundStatus) -> Self {
        match item {
            RazorpayRefundStatus::Processed => Self::Success,
            RazorpayRefundStatus::Pending | RazorpayRefundStatus::Created => Self::Pending,
            RazorpayRefundStatus::Failed => Self::Failure,
        }
    }
}

impl TryFrom<RefundsResponseRouterData<Execute, RazorpayRefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RazorpayRefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

// This code can be used later when Razorpay webhooks are implemented

// #[derive(Debug, Deserialize, Serialize)]
// #[serde(untagged)]
// pub enum RazorpayPaymentsResponseData {
//     PsyncResponse(RazorpaySyncResponse),
//     WebhookResponse(WebhookPaymentEntity),
// }

// impl From<RazorpayWebhookPaymentStatus> for enums::AttemptStatus {
//     fn from(status: RazorpayWebhookPaymentStatus) -> Self {
//         match status {
//             RazorpayWebhookPaymentStatus::Authorized => Self::Authorized,
//             RazorpayWebhookPaymentStatus::Captured => Self::Charged,
//             RazorpayWebhookPaymentStatus::Failed => Self::Failure,
//         }
//     }
// }

// impl<F, T> TryFrom<ResponseRouterData<F, RazorpayPaymentsResponseData, T, PaymentsResponseData>>
//     for RouterData<F, T, PaymentsResponseData>
// {
//     type Error = error_stack::Report<errors::ConnectorError>;
//     fn try_from(
//         item: ResponseRouterData<F, RazorpayPaymentsResponseData, T, PaymentsResponseData>,
//     ) -> Result<Self, Self::Error> {
//         match item.response {
//             RazorpayPaymentsResponseData::PsyncResponse(sync_response) => {
//                 let status = get_psync_razorpay_payment_status(sync_response.status.clone());
//                 Ok(Self {
//                     status,
//                     response: if is_payment_failure(status) {
//                         Err(RouterErrorResponse {
//                             code: sync_response.status.clone().to_string(),
//                             message: sync_response.status.clone().to_string(),
//                             reason: Some(sync_response.status.to_string()),
//                             status_code: item.http_code,
//                             attempt_status: Some(status),
//                             connector_transaction_id: None,
//                             network_advice_code: None,
//                             network_decline_code: None,
//                             network_error_message: None,
//                         })
//                     } else {
//                         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
//                 })
//             }
//             RazorpayPaymentsResponseData::WebhookResponse(webhook_payment_entity) => {
//                 let razorpay_status = webhook_payment_entity.status;
//                 let status = enums::AttemptStatus::from(razorpay_status.clone());

//                 Ok(Self {
//                     status,
//                     response: if is_payment_failure(status) {
//                         Err(RouterErrorResponse {
//                             code: razorpay_status.clone().to_string(),
//                             message: razorpay_status.clone().to_string(),
//                             reason: Some(razorpay_status.to_string()),
//                             status_code: item.http_code,
//                             attempt_status: Some(status),
//                             connector_transaction_id: Some(webhook_payment_entity.id.clone()),
//                             network_advice_code: None,
//                             network_decline_code: None,
//                             network_error_message: None,
//                         })
//                     } else {
//                         Ok(PaymentsResponseData::TransactionResponse {
//                             resource_id: ResponseId::ConnectorTransactionId(
//                                 webhook_payment_entity.id.clone(),
//                             ),
//                             redirection_data: Box::new(None),
//                             mandate_reference: Box::new(None),
//                             connector_metadata: None,
//                             network_txn_id: None,
//                             connector_response_reference_id: Some(webhook_payment_entity.id),
//                             incremental_authorization_allowed: None,
//                             charges: None,
//                         })
//                     },
//                     ..item.data
//                 })
//             }
//         }
//     }
// }

impl TryFrom<RefundsResponseRouterData<RSync, RazorpayRefundResponse>>
    for types::RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RazorpayRefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ErrorResponse {
    RazorpayErrorResponse(RazorpayErrorResponse),
    RazorpayStringError(String),
    RazorpayError(RazorpayErrorMessage),
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayErrorMessage {
    pub message: String,
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayErrorResponse {
    pub error: RazorpayError,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct RazorpayError {
    pub code: String,
    pub description: String,
    pub source: Option<String>,
    pub step: Option<String>,
    pub reason: Option<String>,
    pub metadata: Option<Metadata>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Metadata {
    pub order_id: Option<String>,
}

// This code can be used later when Razorpay webhooks are implemented

// #[derive(Debug, Serialize, Deserialize)]

// pub struct RazorpayWebhookPayload {
//     pub event: RazorpayWebhookEventType,
//     pub payload: RazorpayWebhookPayloadBody,
// }

// #[derive(Debug, Serialize, Deserialize)]
// #[serde(untagged)]
// pub enum RazorpayWebhookEventType {
//     Payments(RazorpayWebhookPaymentEvent),
//     Refunds(RazorpayWebhookRefundEvent),
// }

// #[derive(Debug, Serialize, Deserialize)]
// pub struct RazorpayWebhookPayloadBody {
//     pub refund: Option<RazorpayRefundWebhookPayload>,
//     pub payment: RazorpayPaymentWebhookPayload,
// }

// #[derive(Debug, Serialize, Deserialize)]
// pub struct RazorpayPaymentWebhookPayload {
//     pub entity: WebhookPaymentEntity,
// }

// #[derive(Debug, Serialize, Deserialize)]
// pub struct RazorpayRefundWebhookPayload {
//     pub entity: WebhookRefundEntity,
// }

// #[derive(Debug, Serialize, Deserialize)]
// pub struct WebhookRefundEntity {
//     pub id: String,
//     pub status: RazorpayWebhookRefundEvent,
// }

// #[derive(Debug, Serialize, Eq, PartialEq, Deserialize)]
// pub enum RazorpayWebhookRefundEvent {
//     #[serde(rename = "refund.created")]
//     Created,
//     #[serde(rename = "refund.processed")]
//     Processed,
//     #[serde(rename = "refund.failed")]
//     Failed,
//     #[serde(rename = "refund.speed_change")]
//     SpeedChange,
// }

// #[derive(Debug, Serialize, Deserialize)]
// pub struct WebhookPaymentEntity {
//     pub id: String,
//     pub status: RazorpayWebhookPaymentStatus,
// }

// #[derive(Debug, Serialize, Eq, PartialEq, Clone, Deserialize)]
// #[serde(rename_all = "snake_case")]
// pub enum RazorpayWebhookPaymentStatus {
//     Authorized,
//     Captured,
//     Failed,
// }

// #[derive(Debug, Serialize, Eq, PartialEq, Deserialize)]
// pub enum RazorpayWebhookPaymentEvent {
//     #[serde(rename = "payment.authorized")]
//     Authorized,
//     #[serde(rename = "payment.captured")]
//     Captured,
//     #[serde(rename = "payment.failed")]
//     Failed,
// }

// impl TryFrom<RazorpayWebhookEventType> for api_models::webhooks::IncomingWebhookEvent {
//     type Error = errors::ConnectorError;

//     fn try_from(event_type: RazorpayWebhookEventType) -> Result<Self, Self::Error> {
//         match event_type {
//             RazorpayWebhookEventType::Payments(payment_event) => match payment_event {
//                 RazorpayWebhookPaymentEvent::Authorized => {
//                     Ok(Self::PaymentIntentAuthorizationSuccess)
//                 }
//                 RazorpayWebhookPaymentEvent::Captured => Ok(Self::PaymentIntentSuccess),
//                 RazorpayWebhookPaymentEvent::Failed => Ok(Self::PaymentIntentFailure),
//             },
//             RazorpayWebhookEventType::Refunds(refund_event) => match refund_event {
//                 RazorpayWebhookRefundEvent::Processed => Ok(Self::RefundSuccess),
//                 RazorpayWebhookRefundEvent::Created => Ok(Self::RefundSuccess),
//                 RazorpayWebhookRefundEvent::Failed => Ok(Self::RefundFailure),
//                 RazorpayWebhookRefundEvent::SpeedChange => Ok(Self::EventNotSupported),
//             },
//         }
//     }
// }