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

use base64::Engine;
use common_enums::enums;
use common_utils::{
    consts::BASE64_ENGINE,
    pii::{Email, IpAddress},
    types::MinorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{AccessToken, 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 crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::AccessTokenRequestInfo as _,
};

const WALLET_IDENTIFIER: &str = "PBL";

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

impl<T> TryFrom<(MinorUnit, T)> for PayuRouterData<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,
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentsRequest {
    customer_ip: Secret<String, IpAddress>,
    merchant_pos_id: Secret<String>,
    total_amount: MinorUnit,
    currency_code: enums::Currency,
    description: String,
    pay_methods: PayuPaymentMethod,
    continue_url: Option<String>,
    ext_order_id: Option<String>,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentMethod {
    pay_method: PayuPaymentMethodData,
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum PayuPaymentMethodData {
    Card(PayuCard),
    Wallet(PayuWallet),
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum PayuCard {
    #[serde(rename_all = "camelCase")]
    Card {
        number: cards::CardNumber,
        expiration_month: Secret<String>,
        expiration_year: Secret<String>,
        cvv: Secret<String>,
    },
}

#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuWallet {
    pub value: PayuWalletCode,
    #[serde(rename = "type")]
    pub wallet_type: String,
    pub authorization_code: Secret<String>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PayuWalletCode {
    Ap,
    Jp,
}

impl TryFrom<&PayuRouterData<&types::PaymentsAuthorizeRouterData>> for PayuPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PayuRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let auth_type = PayuAuthType::try_from(&item.router_data.connector_auth_type)?;
        let payment_method = match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(ccard) => Ok(PayuPaymentMethod {
                pay_method: PayuPaymentMethodData::Card(PayuCard::Card {
                    number: ccard.card_number,
                    expiration_month: ccard.card_exp_month,
                    expiration_year: ccard.card_exp_year,
                    cvv: ccard.card_cvc,
                }),
            }),
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                WalletData::GooglePay(data) => Ok(PayuPaymentMethod {
                    pay_method: PayuPaymentMethodData::Wallet({
                        PayuWallet {
                            value: PayuWalletCode::Ap,
                            wallet_type: WALLET_IDENTIFIER.to_string(),
                            authorization_code: Secret::new(
                                BASE64_ENGINE.encode(
                                    data.tokenization_data
                                        .get_encrypted_google_pay_token()
                                        .change_context(
                                            errors::ConnectorError::MissingRequiredField {
                                                field_name: "gpay wallet_token",
                                            },
                                        )?,
                                ),
                            ),
                        }
                    }),
                }),
                WalletData::ApplePay(apple_pay_data) => {
                    let apple_pay_encrypted_data = apple_pay_data
                        .payment_data
                        .get_encrypted_apple_pay_payment_data_mandatory()
                        .change_context(errors::ConnectorError::MissingRequiredField {
                            field_name: "Apple pay encrypted data",
                        })?;
                    Ok(PayuPaymentMethod {
                        pay_method: PayuPaymentMethodData::Wallet({
                            PayuWallet {
                                value: PayuWalletCode::Jp,
                                wallet_type: WALLET_IDENTIFIER.to_string(),
                                authorization_code: Secret::new(
                                    apple_pay_encrypted_data.to_string(),
                                ),
                            }
                        }),
                    })
                }
                _ => Err(errors::ConnectorError::NotImplemented(
                    "Unknown Wallet in Payment Method".to_string(),
                )),
            },
            _ => Err(errors::ConnectorError::NotImplemented(
                "Unknown payment method".to_string(),
            )),
        }?;
        let browser_info = item.router_data.request.browser_info.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info",
            },
        )?;
        Ok(Self {
            customer_ip: Secret::new(
                browser_info
                    .ip_address
                    .ok_or(errors::ConnectorError::MissingRequiredField {
                        field_name: "browser_info.ip_address",
                    })?
                    .to_string(),
            ),
            merchant_pos_id: auth_type.merchant_pos_id,
            ext_order_id: Some(item.router_data.connector_request_reference_id.clone()),
            total_amount: item.amount.to_owned(),
            currency_code: item.router_data.request.currency,
            description: item.router_data.description.clone().ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "item.description",
                },
            )?,
            pay_methods: payment_method,
            continue_url: None,
        })
    }
}

pub struct PayuAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) merchant_pos_id: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for PayuAuthType {
    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 {
                api_key: api_key.to_owned(),
                merchant_pos_id: key1.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayuPaymentStatus {
    Success,
    WarningContinueRedirect,
    #[serde(rename = "WARNING_CONTINUE_3DS")]
    WarningContinue3ds,
    WarningContinueCvv,
    #[default]
    Pending,
}

impl From<PayuPaymentStatus> for enums::AttemptStatus {
    fn from(item: PayuPaymentStatus) -> Self {
        match item {
            PayuPaymentStatus::Success => Self::Pending,
            PayuPaymentStatus::WarningContinue3ds => Self::Pending,
            PayuPaymentStatus::WarningContinueCvv => Self::Pending,
            PayuPaymentStatus::WarningContinueRedirect => Self::Pending,
            PayuPaymentStatus::Pending => Self::Pending,
        }
    }
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentsResponse {
    pub status: PayuPaymentStatusData,
    pub redirect_uri: String,
    pub iframe_allowed: Option<bool>,
    pub three_ds_protocol_version: Option<String>,
    pub order_id: String,
    pub ext_order_id: Option<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, PayuPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PayuPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.status.status_code),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .ext_order_id
                    .or(Some(item.response.order_id)),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            amount_captured: None,
            ..item.data
        })
    }
}

#[derive(Default, Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentsCaptureRequest {
    order_id: String,
    order_status: OrderStatus,
}

impl TryFrom<&types::PaymentsCaptureRouterData> for PayuPaymentsCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            order_id: item.request.connector_transaction_id.clone(),
            order_status: OrderStatus::Completed,
        })
    }
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct PayuPaymentsCaptureResponse {
    status: PayuPaymentStatusData,
}

impl<F, T> TryFrom<ResponseRouterData<F, PayuPaymentsCaptureResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PayuPaymentsCaptureResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
            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,
            }),
            amount_captured: None,
            ..item.data
        })
    }
}

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

impl TryFrom<&types::RefreshTokenRouterData> for PayuAuthUpdateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            grant_type: "client_credentials".to_string(),
            client_id: item.get_request_id()?,
            client_secret: item.request.app_id.clone(),
        })
    }
}
#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct PayuAuthUpdateResponse {
    pub access_token: Secret<String>,
    pub token_type: String,
    pub expires_in: i64,
    pub grant_type: String,
}

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

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentsCancelResponse {
    pub order_id: String,
    pub ext_order_id: Option<String>,
    pub status: PayuPaymentStatusData,
}

impl<F, T> TryFrom<ResponseRouterData<F, PayuPaymentsCancelResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PayuPaymentsCancelResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .ext_order_id
                    .or(Some(item.response.order_id)),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            amount_captured: None,
            ..item.data
        })
    }
}

#[allow(dead_code)]
#[derive(Debug, Serialize, Eq, PartialEq, Default, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OrderStatus {
    New,
    Canceled,
    Completed,
    WaitingForConfirmation,
    #[default]
    Pending,
}

impl From<OrderStatus> for enums::AttemptStatus {
    fn from(item: OrderStatus) -> Self {
        match item {
            OrderStatus::New => Self::PaymentMethodAwaited,
            OrderStatus::Canceled => Self::Voided,
            OrderStatus::Completed => Self::Charged,
            OrderStatus::Pending => Self::Pending,
            OrderStatus::WaitingForConfirmation => Self::Authorized,
        }
    }
}

#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuPaymentStatusData {
    status_code: PayuPaymentStatus,
    severity: Option<String>,
    status_desc: Option<String>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuProductData {
    name: String,
    unit_price: String,
    quantity: String,
    #[serde(rename = "virtual")]
    virtually: Option<bool>,
    listing_date: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuOrderResponseData {
    order_id: String,
    ext_order_id: Option<String>,
    order_create_date: String,
    notify_url: Option<String>,
    customer_ip: Secret<String, IpAddress>,
    merchant_pos_id: Secret<String>,
    description: String,
    validity_time: Option<String>,
    currency_code: enums::Currency,
    total_amount: String,
    buyer: Option<PayuOrderResponseBuyerData>,
    pay_method: Option<PayuOrderResponsePayMethod>,
    products: Option<Vec<PayuProductData>>,
    status: OrderStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuOrderResponseBuyerData {
    ext_customer_id: Option<String>,
    email: Option<Email>,
    phone: Option<Secret<String>>,
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
    #[serde(rename = "nin")]
    national_identification_number: Option<Secret<String>>,
    language: Option<String>,
    delivery: Option<String>,
    customer_id: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayuOrderResponsePayMethod {
    CardToken,
    Pbl,
    Installemnts,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PayuOrderResponseProperty {
    name: String,
    value: String,
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct PayuPaymentsSyncResponse {
    orders: Vec<PayuOrderResponseData>,
    status: PayuPaymentStatusData,
    properties: Option<Vec<PayuOrderResponseProperty>>,
}

impl<F, T> TryFrom<ResponseRouterData<F, PayuPaymentsSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PayuPaymentsSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let order = match item.response.orders.first() {
            Some(order) => order,
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        Ok(Self {
            status: enums::AttemptStatus::from(order.status.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(order.order_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: order
                    .ext_order_id
                    .clone()
                    .or(Some(order.order_id.clone())),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            amount_captured: Some(
                order
                    .total_amount
                    .parse::<i64>()
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?,
            ),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
pub struct PayuRefundRequestData {
    description: String,
    amount: Option<MinorUnit>,
}

#[derive(Default, Debug, Serialize)]
pub struct PayuRefundRequest {
    refund: PayuRefundRequestData,
}

impl<F> TryFrom<&PayuRouterData<&types::RefundsRouterData<F>>> for PayuRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PayuRouterData<&types::RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            refund: PayuRefundRequestData {
                description: item.router_data.request.reason.clone().ok_or(
                    errors::ConnectorError::MissingRequiredField {
                        field_name: "item.request.reason",
                    },
                )?,
                amount: None,
            },
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Serialize, Eq, PartialEq, Default, Deserialize, Clone)]
#[serde(rename_all = "UPPERCASE")]
pub enum RefundStatus {
    Finalized,
    Completed,
    Canceled,
    #[default]
    Pending,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Finalized | RefundStatus::Completed => Self::Success,
            RefundStatus::Canceled => Self::Failure,
            RefundStatus::Pending => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayuRefundResponseData {
    refund_id: String,
    ext_refund_id: String,
    amount: String,
    currency_code: enums::Currency,
    description: String,
    creation_date_time: String,
    status: RefundStatus,
    status_date_time: Option<String>,
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    refund: PayuRefundResponseData,
}

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

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RefundSyncResponse {
    refunds: Vec<PayuRefundResponseData>,
}
impl TryFrom<RefundsResponseRouterData<RSync, RefundSyncResponse>>
    for types::RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundSyncResponse>,
    ) -> Result<Self, Self::Error> {
        let refund = match item.response.refunds.first() {
            Some(refund) => refund,
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: refund.refund_id.clone(),
                refund_status: enums::RefundStatus::from(refund.status.clone()),
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayuErrorData {
    pub status_code: String,
    pub code: Option<String>,
    pub code_literal: Option<String>,
    pub status_desc: String,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct PayuErrorResponse {
    pub status: PayuErrorData,
}

#[derive(Deserialize, Debug, Serialize)]
pub struct PayuAccessTokenErrorResponse {
    pub error: String,
    pub error_description: String,
}


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

use cards::CardNumber;
use common_enums::{enums, Currency};
use common_utils::{pii::Email, types::StringMajorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{ConnectorAuthType, ErrorResponse},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData,
        RefundSyncRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{consts::NO_ERROR_CODE, errors};
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Deserializer, Serialize};

use crate::{
    types::{
        PaymentsCaptureResponseRouterData, PaymentsResponseRouterData,
        PaymentsSyncResponseRouterData, RefundsResponseRouterData,
    },
    utils::{CardData, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData as _},
};

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

impl<T> From<(StringMajorUnit, T)> for ElavonRouterData<T> {
    fn from((amount, item): (StringMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum TransactionType {
    CcSale,
    CcAuthOnly,
    CcComplete,
    CcReturn,
    TxnQuery,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "UPPERCASE")]
pub enum SyncTransactionType {
    Sale,
    AuthOnly,
    Return,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ElavonPaymentsRequest {
    Card(CardPaymentRequest),
    MandatePayment(MandatePaymentRequest),
}
#[derive(Debug, Serialize)]
pub struct CardPaymentRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_amount: StringMajorUnit,
    pub ssl_card_number: CardNumber,
    pub ssl_exp_date: Secret<String>,
    pub ssl_cvv2cvc2: Secret<String>,
    pub ssl_email: Email,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ssl_add_token: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ssl_get_token: Option<String>,
    pub ssl_transaction_currency: Currency,
}
#[derive(Debug, Serialize)]
pub struct MandatePaymentRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_amount: StringMajorUnit,
    pub ssl_email: Email,
    pub ssl_token: Secret<String>,
}

impl TryFrom<&ElavonRouterData<&PaymentsAuthorizeRouterData>> for ElavonPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ElavonRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?;
        if item.router_data.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Card 3DS".to_string(),
                connector: "Elavon",
            })?
        };
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(req_card) => Ok(Self::Card(CardPaymentRequest {
                ssl_transaction_type: match item.router_data.request.is_auto_capture()? {
                    true => TransactionType::CcSale,
                    false => TransactionType::CcAuthOnly,
                },
                ssl_account_id: auth.account_id.clone(),
                ssl_user_id: auth.user_id.clone(),
                ssl_pin: auth.pin.clone(),
                ssl_amount: item.amount.clone(),
                ssl_card_number: req_card.card_number.clone(),
                ssl_exp_date: req_card.get_expiry_date_as_mmyy()?,
                ssl_cvv2cvc2: req_card.card_cvc,
                ssl_email: item.router_data.get_billing_email()?,
                ssl_add_token: match item.router_data.request.is_mandate_payment() {
                    true => Some("Y".to_string()),
                    false => None,
                },
                ssl_get_token: match item.router_data.request.is_mandate_payment() {
                    true => Some("Y".to_string()),
                    false => None,
                },
                ssl_transaction_currency: item.router_data.request.currency,
            })),
            PaymentMethodData::MandatePayment => Ok(Self::MandatePayment(MandatePaymentRequest {
                ssl_transaction_type: match item.router_data.request.is_auto_capture()? {
                    true => TransactionType::CcSale,
                    false => TransactionType::CcAuthOnly,
                },
                ssl_account_id: auth.account_id.clone(),
                ssl_user_id: auth.user_id.clone(),
                ssl_pin: auth.pin.clone(),
                ssl_amount: item.amount.clone(),
                ssl_email: item.router_data.get_billing_email()?,
                ssl_token: Secret::new(item.router_data.request.get_connector_mandate_id()?),
            })),
            _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
        }
    }
}

pub struct ElavonAuthType {
    pub(super) account_id: Secret<String>,
    pub(super) user_id: Secret<String>,
    pub(super) pin: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for ElavonAuthType {
    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 {
                account_id: api_key.to_owned(),
                user_id: key1.to_owned(),
                pin: api_secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
enum SslResult {
    #[serde(rename = "0")]
    ImportedBatchFile,
    #[serde(other)]
    DeclineOrUnauthorized,
}

#[derive(Debug, Clone, Serialize)]
pub struct ElavonPaymentsResponse {
    pub result: ElavonResult,
}
#[derive(Debug, Clone, Serialize)]
pub enum ElavonResult {
    Success(PaymentResponse),
    Error(ElavonErrorResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ElavonErrorResponse {
    error_code: Option<String>,
    error_message: String,
    error_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentResponse {
    ssl_result: SslResult,
    ssl_txn_id: String,
    ssl_result_message: String,
    ssl_token: Option<Secret<String>>,
}
impl<'de> Deserialize<'de> for ElavonPaymentsResponse {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize, Debug)]
        #[serde(rename = "txn")]
        struct XmlResponse {
            // Error fields
            #[serde(rename = "errorCode", default)]
            error_code: Option<String>,
            #[serde(rename = "errorMessage", default)]
            error_message: Option<String>,
            #[serde(rename = "errorName", default)]
            error_name: Option<String>,

            // Success fields
            #[serde(rename = "ssl_result", default)]
            ssl_result: Option<SslResult>,
            #[serde(rename = "ssl_txn_id", default)]
            ssl_txn_id: Option<String>,
            #[serde(rename = "ssl_result_message", default)]
            ssl_result_message: Option<String>,
            #[serde(rename = "ssl_token", default)]
            ssl_token: Option<Secret<String>>,
        }

        let xml_res = XmlResponse::deserialize(deserializer)?;

        let result = match (xml_res.error_message.clone(), xml_res.error_name.clone()) {
            (Some(error_message), Some(error_name)) => ElavonResult::Error(ElavonErrorResponse {
                error_code: xml_res.error_code.clone(),
                error_message,
                error_name,
            }),
            _ => {
                if let (Some(ssl_result), Some(ssl_txn_id), Some(ssl_result_message)) = (
                    xml_res.ssl_result.clone(),
                    xml_res.ssl_txn_id.clone(),
                    xml_res.ssl_result_message.clone(),
                ) {
                    ElavonResult::Success(PaymentResponse {
                        ssl_result,
                        ssl_txn_id,
                        ssl_result_message,
                        ssl_token: xml_res.ssl_token.clone(),
                    })
                } else {
                    return Err(serde::de::Error::custom(
                        "Invalid Response XML structure - neither error nor success",
                    ));
                }
            }
        };

        Ok(Self { result })
    }
}
impl TryFrom<PaymentsResponseRouterData<ElavonPaymentsResponse>> for PaymentsAuthorizeRouterData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsResponseRouterData<ElavonPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let status =
            get_payment_status(&item.response.result, item.data.request.is_auto_capture()?);
        let response = match &item.response.result {
            ElavonResult::Error(error) => Err(ErrorResponse {
                code: error
                    .error_code
                    .clone()
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: error.error_message.clone(),
                reason: Some(error.error_message.clone()),
                attempt_status: None,
                connector_transaction_id: None,
                status_code: item.http_code,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            ElavonResult::Success(response) => {
                if status == enums::AttemptStatus::Failure {
                    Err(ErrorResponse {
                        code: response.ssl_result_message.clone(),
                        message: response.ssl_result_message.clone(),
                        reason: Some(response.ssl_result_message.clone()),
                        attempt_status: None,
                        connector_transaction_id: Some(response.ssl_txn_id.clone()),
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    })
                } else {
                    Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(
                            response.ssl_txn_id.clone(),
                        ),
                        redirection_data: Box::new(None),
                        mandate_reference: Box::new(Some(MandateReference {
                            connector_mandate_id: response
                                .ssl_token
                                .as_ref()
                                .map(|secret| secret.clone().expose()),
                            payment_method_id: None,
                            mandate_metadata: None,
                            connector_mandate_request_reference_id: None,
                        })),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: Some(response.ssl_txn_id.clone()),
                        incremental_authorization_allowed: None,
                        charges: None,
                    })
                }
            }
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub enum TransactionSyncStatus {
    PEN, // Pended
    OPN, // Unpended / release / open
    REV, // Review
    STL, // Settled
    PST, // Failed due to post-auth rule
    FPR, // Failed due to fraud prevention rules
    PRE, // Failed due to pre-auth rule
}

#[derive(Debug, Serialize)]
#[serde(rename = "txn")]
pub struct PaymentsCaptureRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_amount: StringMajorUnit,
    pub ssl_txn_id: String,
}
#[derive(Debug, Serialize)]
#[serde(rename = "txn")]
pub struct PaymentsVoidRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_txn_id: String,
}

#[derive(Debug, Serialize)]
#[serde(rename = "txn")]
pub struct ElavonRefundRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_amount: StringMajorUnit,
    pub ssl_txn_id: String,
}

#[derive(Debug, Serialize)]
#[serde(rename = "txn")]
pub struct SyncRequest {
    pub ssl_transaction_type: TransactionType,
    pub ssl_account_id: Secret<String>,
    pub ssl_user_id: Secret<String>,
    pub ssl_pin: Secret<String>,
    pub ssl_txn_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "txn")]
pub struct ElavonSyncResponse {
    pub ssl_trans_status: TransactionSyncStatus,
    pub ssl_transaction_type: SyncTransactionType,
    pub ssl_txn_id: String,
}
impl TryFrom<&RefundSyncRouterData> for SyncRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundSyncRouterData) -> Result<Self, Self::Error> {
        let auth = ElavonAuthType::try_from(&item.connector_auth_type)?;
        Ok(Self {
            ssl_txn_id: item.request.get_connector_refund_id()?,
            ssl_transaction_type: TransactionType::TxnQuery,
            ssl_account_id: auth.account_id.clone(),
            ssl_user_id: auth.user_id.clone(),
            ssl_pin: auth.pin.clone(),
        })
    }
}
impl TryFrom<&PaymentsSyncRouterData> for SyncRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsSyncRouterData) -> Result<Self, Self::Error> {
        let auth = ElavonAuthType::try_from(&item.connector_auth_type)?;
        Ok(Self {
            ssl_txn_id: item
                .request
                .connector_transaction_id
                .get_connector_transaction_id()
                .change_context(errors::ConnectorError::MissingConnectorTransactionID)?,
            ssl_transaction_type: TransactionType::TxnQuery,
            ssl_account_id: auth.account_id.clone(),
            ssl_user_id: auth.user_id.clone(),
            ssl_pin: auth.pin.clone(),
        })
    }
}
impl<F> TryFrom<&ElavonRouterData<&RefundsRouterData<F>>> for ElavonRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &ElavonRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?;
        Ok(Self {
            ssl_txn_id: item.router_data.request.connector_transaction_id.clone(),
            ssl_amount: item.amount.clone(),
            ssl_transaction_type: TransactionType::CcReturn,
            ssl_account_id: auth.account_id.clone(),
            ssl_user_id: auth.user_id.clone(),
            ssl_pin: auth.pin.clone(),
        })
    }
}

impl TryFrom<&ElavonRouterData<&PaymentsCaptureRouterData>> for PaymentsCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &ElavonRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?;
        Ok(Self {
            ssl_txn_id: item.router_data.request.connector_transaction_id.clone(),
            ssl_amount: item.amount.clone(),
            ssl_transaction_type: TransactionType::CcComplete,
            ssl_account_id: auth.account_id.clone(),
            ssl_user_id: auth.user_id.clone(),
            ssl_pin: auth.pin.clone(),
        })
    }
}

impl TryFrom<PaymentsSyncResponseRouterData<ElavonSyncResponse>> for PaymentsSyncRouterData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsSyncResponseRouterData<ElavonSyncResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: get_sync_status(item.data.status, &item.response),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.ssl_txn_id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}
impl TryFrom<RefundsResponseRouterData<RSync, ElavonSyncResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, ElavonSyncResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.ssl_txn_id.clone(),
                refund_status: get_refund_status(item.data.request.refund_status, &item.response),
            }),
            ..item.data
        })
    }
}

impl TryFrom<PaymentsCaptureResponseRouterData<ElavonPaymentsResponse>>
    for PaymentsCaptureRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCaptureResponseRouterData<ElavonPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let status = map_payment_status(&item.response.result, enums::AttemptStatus::Charged);
        let response = match &item.response.result {
            ElavonResult::Error(error) => Err(ErrorResponse {
                code: error
                    .error_code
                    .clone()
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: error.error_message.clone(),
                reason: Some(error.error_message.clone()),
                attempt_status: None,
                connector_transaction_id: None,
                status_code: item.http_code,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            ElavonResult::Success(response) => {
                if status == enums::AttemptStatus::Failure {
                    Err(ErrorResponse {
                        code: response.ssl_result_message.clone(),
                        message: response.ssl_result_message.clone(),
                        reason: Some(response.ssl_result_message.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    })
                } else {
                    Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(
                            response.ssl_txn_id.clone(),
                        ),
                        redirection_data: Box::new(None),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: Some(response.ssl_txn_id.clone()),
                        incremental_authorization_allowed: None,
                        charges: None,
                    })
                }
            }
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}
impl TryFrom<RefundsResponseRouterData<Execute, ElavonPaymentsResponse>>
    for RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, ElavonPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let status = enums::RefundStatus::from(&item.response.result);
        let response = match &item.response.result {
            ElavonResult::Error(error) => Err(ErrorResponse {
                code: error
                    .error_code
                    .clone()
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: error.error_message.clone(),
                reason: Some(error.error_message.clone()),
                attempt_status: None,
                connector_transaction_id: None,
                status_code: item.http_code,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            ElavonResult::Success(response) => {
                if status == enums::RefundStatus::Failure {
                    Err(ErrorResponse {
                        code: response.ssl_result_message.clone(),
                        message: response.ssl_result_message.clone(),
                        reason: Some(response.ssl_result_message.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    })
                } else {
                    Ok(RefundsResponseData {
                        connector_refund_id: response.ssl_txn_id.clone(),
                        refund_status: enums::RefundStatus::from(&item.response.result),
                    })
                }
            }
        };
        Ok(Self {
            response,
            ..item.data
        })
    }
}

trait ElavonResponseValidator {
    fn is_successful(&self) -> bool;
}
impl ElavonResponseValidator for ElavonResult {
    fn is_successful(&self) -> bool {
        matches!(self, Self::Success(response) if response.ssl_result == SslResult::ImportedBatchFile)
    }
}

fn map_payment_status(
    item: &ElavonResult,
    success_status: enums::AttemptStatus,
) -> enums::AttemptStatus {
    if item.is_successful() {
        success_status
    } else {
        enums::AttemptStatus::Failure
    }
}

impl From<&ElavonResult> for enums::RefundStatus {
    fn from(item: &ElavonResult) -> Self {
        if item.is_successful() {
            Self::Success
        } else {
            Self::Failure
        }
    }
}
fn get_refund_status(
    prev_status: enums::RefundStatus,
    item: &ElavonSyncResponse,
) -> enums::RefundStatus {
    match item.ssl_trans_status {
        TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => {
            prev_status
        }
        TransactionSyncStatus::STL => enums::RefundStatus::Success,
        TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => {
            enums::RefundStatus::Failure
        }
    }
}
impl From<&ElavonSyncResponse> for enums::AttemptStatus {
    fn from(item: &ElavonSyncResponse) -> Self {
        match item.ssl_trans_status {
            TransactionSyncStatus::REV
            | TransactionSyncStatus::OPN
            | TransactionSyncStatus::PEN => Self::Pending,
            TransactionSyncStatus::STL => match item.ssl_transaction_type {
                SyncTransactionType::Sale => Self::Charged,
                SyncTransactionType::AuthOnly => Self::Authorized,
                SyncTransactionType::Return => Self::Pending,
            },
            TransactionSyncStatus::PST
            | TransactionSyncStatus::FPR
            | TransactionSyncStatus::PRE => Self::Failure,
        }
    }
}
fn get_sync_status(
    prev_status: enums::AttemptStatus,
    item: &ElavonSyncResponse,
) -> enums::AttemptStatus {
    match item.ssl_trans_status {
        TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => {
            prev_status
        }
        TransactionSyncStatus::STL => match item.ssl_transaction_type {
            SyncTransactionType::Sale => enums::AttemptStatus::Charged,
            SyncTransactionType::AuthOnly => enums::AttemptStatus::Authorized,
            SyncTransactionType::Return => enums::AttemptStatus::Pending,
        },
        TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => {
            enums::AttemptStatus::Failure
        }
    }
}

fn get_payment_status(item: &ElavonResult, is_auto_capture: bool) -> enums::AttemptStatus {
    if item.is_successful() {
        if is_auto_capture {
            enums::AttemptStatus::Charged
        } else {
            enums::AttemptStatus::Authorized
        }
    } else {
        enums::AttemptStatus::Failure
    }
}


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

use common_enums::enums;
use common_utils::{pii, request::Method, types::MinorUnit};
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsSyncRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::{
    types::{self, RefundsResponseRouterData},
    utils::{PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData as _},
};

const NO_REFUND_REASON: &str = "No reason provided";

//TODO: Fill the struct with respective fields
pub struct PayjustnowRouterData<T> {
    pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> From<(MinorUnit, T)> for PayjustnowRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        //Todo :  use utils to convert the amount to the type of amount that a connector accepts
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowPaymentsRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    request_id: Option<String>,
    payjustnow: PayjustnowRequest,
    checkout_total_cents: MinorUnit,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowRequest {
    merchant_order_reference: String,
    order_amount_cents: MinorUnit,
    #[serde(skip_serializing_if = "Option::is_none")]
    order_items: Option<Vec<OrderItem>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    customer: Option<Customer>,
    #[serde(skip_serializing_if = "Option::is_none")]
    billing_address: Option<Address>,
    #[serde(skip_serializing_if = "Option::is_none")]
    shipping_address: Option<Address>,
    confirm_redirect_url: String,
    cancel_redirect_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderItem {
    name: String,
    sku: String,
    quantity: u32,
    price_cents: MinorUnit,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Customer {
    #[serde(skip_serializing_if = "Option::is_none")]
    first_name: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    last_name: Option<Secret<String>>,
    email: pii::Email,
    #[serde(skip_serializing_if = "Option::is_none")]
    phone_number: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Address {
    #[serde(skip_serializing_if = "Option::is_none")]
    address_line1: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    address_line2: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    city: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    province: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    postal_code: Option<Secret<String>>,
}

impl Address {
    fn is_empty(&self) -> bool {
        self.address_line1.is_none()
            && self.address_line2.is_none()
            && self.city.is_none()
            && self.province.is_none()
            && self.postal_code.is_none()
    }
}

impl TryFrom<&PayjustnowRouterData<&PaymentsAuthorizeRouterData>> for PayjustnowPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PayjustnowRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let router_data = item.router_data;
        let order_items = router_data
            .request
            .order_details
            .as_ref()
            .map(|order_details| {
                order_details
                    .iter()
                    .map(|order| {
                        Ok(OrderItem {
                            name: order.product_name.clone(),
                            sku: order.product_id.clone().unwrap_or_default(),
                            quantity: u32::from(order.quantity),
                            price_cents: order.amount,
                        })
                    })
                    .collect::<Result<Vec<OrderItem>, errors::ConnectorError>>()
            })
            .transpose()?;

        let customer = router_data
            .get_optional_billing_email()
            .or_else(|| item.router_data.request.email.clone())
            .map(|email| Customer {
                first_name: router_data.get_optional_billing_first_name(),
                last_name: router_data.get_optional_billing_last_name(),
                email,
                phone_number: router_data.get_optional_billing_phone_number(),
            });

        let billing_address = {
            let addr = Address {
                address_line1: router_data.get_optional_billing_line1(),
                address_line2: router_data.get_optional_billing_line2(),
                city: router_data.get_optional_billing_city(),
                province: router_data.get_optional_billing_state(),
                postal_code: item.router_data.get_optional_billing_zip(),
            };

            if addr.is_empty() {
                None
            } else {
                Some(addr)
            }
        };

        let shipping_address = {
            let addr = Address {
                address_line1: item.router_data.get_optional_shipping_line1(),
                address_line2: item.router_data.get_optional_shipping_line2(),
                city: item.router_data.get_optional_shipping_city(),
                province: item.router_data.get_optional_shipping_state(),
                postal_code: item.router_data.get_optional_shipping_zip(),
            };

            if addr.is_empty() {
                None
            } else {
                Some(addr)
            }
        };

        let router_return_url = item.router_data.request.get_router_return_url()?;

        let payjustnow_request = PayjustnowRequest {
            merchant_order_reference: item
                .router_data
                .request
                .merchant_order_reference_id
                .clone()
                .unwrap_or(item.router_data.payment_id.clone()),
            order_amount_cents: item.amount,
            order_items,
            customer,
            billing_address,
            shipping_address,
            confirm_redirect_url: router_return_url.clone(),
            cancel_redirect_url: router_return_url,
        };

        Ok(Self {
            request_id: Some(item.router_data.connector_request_reference_id.clone()),
            payjustnow: payjustnow_request,
            checkout_total_cents: item.amount,
        })
    }
}

//TODO: Fill the struct with respective fields
// Auth Struct
pub struct PayjustnowAuthType {
    pub(super) merchant_account_id: Secret<String>,
    pub(super) signing_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for PayjustnowAuthType {
    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 {
                signing_key: api_key.to_owned(),
                merchant_account_id: key1.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowPaymentsResponse {
    payment_url: String,
    checkout_token: String,
}

impl<F, T>
    TryFrom<types::ResponseRouterData<F, PayjustnowPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: types::ResponseRouterData<F, PayjustnowPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let redirection_data = url::Url::parse(&item.response.payment_url.clone())
            .ok()
            .map(|url| RedirectForm::from((url, Method::Get)));
        Ok(Self {
            status: enums::AttemptStatus::AuthenticationPending,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.checkout_token),
                redirection_data: Box::new(redirection_data),
                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(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowSyncRequest {
    checkout_token: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowCancelRequest {
    checkout_token: String,
}

impl TryFrom<&PaymentsCancelRouterData> for PayjustnowCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            checkout_token: item.request.connector_transaction_id.clone(),
        })
    }
}

impl TryFrom<&PaymentsSyncRouterData> for PayjustnowSyncRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsSyncRouterData) -> Result<Self, Self::Error> {
        let checkout_token = item.request.get_connector_transaction_id()?;
        Ok(Self { checkout_token })
    }
}

impl TryFrom<&RefundsRouterData<RSync>> for PayjustnowSyncRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundsRouterData<RSync>) -> Result<Self, Self::Error> {
        Ok(Self {
            checkout_token: item.request.connector_transaction_id.clone(),
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayjustnowCheckoutStatus {
    PendingOrder,
    PendingPayment,
    PaidPendingCallback,
    Paid,
    CancelledByMerchant,
    CancelledByConsumer,
    Expired,
}

impl From<PayjustnowCheckoutStatus> for enums::AttemptStatus {
    fn from(item: PayjustnowCheckoutStatus) -> Self {
        match item {
            PayjustnowCheckoutStatus::Paid | PayjustnowCheckoutStatus::PaidPendingCallback => {
                Self::Charged
            }
            PayjustnowCheckoutStatus::PendingOrder | PayjustnowCheckoutStatus::PendingPayment => {
                Self::AuthenticationPending
            }
            PayjustnowCheckoutStatus::CancelledByMerchant
            | PayjustnowCheckoutStatus::CancelledByConsumer => Self::Voided,
            PayjustnowCheckoutStatus::Expired => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowSyncResponse {
    checkout_token: String,
    payment_url: Option<String>,
    checkout_payment_status: PayjustnowCheckoutStatus,
    payment_reference: Option<i64>,
}

impl<F, T> TryFrom<types::ResponseRouterData<F, PayjustnowSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: types::ResponseRouterData<F, PayjustnowSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = enums::AttemptStatus::from(item.response.checkout_payment_status);
        let redirection_data = item.response.payment_url.and_then(|url_string| {
            url::Url::parse(&url_string)
                .ok()
                .map(|url| RedirectForm::from((url, Method::Get)))
        });

        Ok(Self {
            status,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.checkout_token),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .payment_reference
                    .map(|id| id.to_string()),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

//TODO: Fill the struct with respective fields
// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowRefundRequest {
    request_id: Option<String>,
    checkout_token: String,
    merchant_refund_reference: String,
    refund_amount_cents: MinorUnit,
    refund_description: String,
}

impl<F> TryFrom<&RefundsRouterData<F>> for PayjustnowRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundsRouterData<F>) -> Result<Self, Self::Error> {
        Ok(Self {
            request_id: Some(item.connector_request_reference_id.clone()),
            checkout_token: item.request.connector_transaction_id.clone(),
            merchant_refund_reference: item.request.refund_id.clone(),
            refund_amount_cents: item.request.minor_refund_amount,
            refund_description: item
                .request
                .reason
                .clone()
                .unwrap_or(NO_REFUND_REASON.to_string()),
        })
    }
}

// Type definition for Refund Response

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

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

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowRefundResponse {
    request_id: String,
    refunded_amount_cents: MinorUnit,
    refund_status: RefundStatus,
    refund_status_at: String,
    refund_status_description: String,
}

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

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

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayjustnowRsyncStatus {
    FullyRefunded,
    PartiallyRefunded,
    PaidPendingCallback,
    Paid,
}

impl From<PayjustnowRsyncStatus> for enums::RefundStatus {
    fn from(item: PayjustnowRsyncStatus) -> Self {
        match item {
            PayjustnowRsyncStatus::FullyRefunded | PayjustnowRsyncStatus::PartiallyRefunded => {
                Self::Success
            }
            PayjustnowRsyncStatus::Paid | PayjustnowRsyncStatus::PaidPendingCallback => {
                Self::Failure
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowRsyncResponse {
    request_id: String,
    checkout_payment_status: PayjustnowRsyncStatus,
}

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

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct PayjustnowError {
    pub message: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PayjustnowErrorResponse {
    Structured(PayjustnowError),
    Message(String),
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayjustnowWebhookDetails {
    pub checkout_token: String,
    pub checkout_payment_status: PayjustnowWebhookStatus,
}


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

use api_models::webhooks::IncomingWebhookEvent;
#[cfg(feature = "payouts")]
use api_models::{
    self,
    payouts::{BankRedirect, PayoutMethodData},
};
use common_enums::{enums, Currency};
use common_utils::{
    id_type,
    pii::{self, Email, IpAddress},
    request::Method,
    types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankRedirectData, PaymentMethodData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::Execute,
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_flow_types::PoQuote, router_response_types::PayoutsResponseData,
    types::PayoutsRouterData,
};
use hyperswitch_interfaces::errors;
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};

#[cfg(feature = "payouts")]
use crate::{types::PayoutsResponseRouterData, utils::PayoutsData as _};
use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self, BrowserInformationData, PaymentsAuthorizeRequestData, RouterData as _},
};

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

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

const CONNECTOR_BASE_URL: &str = "https://interac.express-connect.com/";

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GigadatConnectorMetadataObject {
    pub site: String,
}

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

// CPI (Combined Pay-in) Request Structure for Gigadat
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GigadatCpiRequest {
    pub user_id: id_type::CustomerId,
    pub site: String,
    pub user_ip: Secret<String, IpAddress>,
    pub currency: Currency,
    pub amount: FloatMajorUnit,
    pub transaction_id: String,
    #[serde(rename = "type")]
    pub transaction_type: GidadatTransactionType,
    pub sandbox: bool,
    pub name: Secret<String>,
    pub email: Email,
    pub mobile: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum GidadatTransactionType {
    Cpi,
    Eto,
}

impl TryFrom<&GigadatRouterData<&PaymentsAuthorizeRouterData>> for GigadatCpiRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GigadatRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let metadata: GigadatConnectorMetadataObject =
            utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())
                .change_context(errors::ConnectorError::InvalidConnectorConfig {
                    config: "merchant_connector_account.metadata",
                })?;
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::BankRedirect(BankRedirectData::Interac { .. }) => {
                let router_data = item.router_data;
                let name = router_data.get_billing_full_name()?;
                let email = router_data.get_billing_email()?;
                let mobile = router_data.get_billing_phone_number()?;
                let currency = item.router_data.request.currency;
                let sandbox = match item.router_data.test_mode {
                    Some(true) => true,
                    Some(false) | None => false,
                };
                let user_ip = router_data.request.get_browser_info()?.get_ip_address()?;
                Ok(Self {
                    user_id: router_data.get_customer_id()?,
                    site: metadata.site,
                    user_ip,
                    currency,
                    amount: item.amount,
                    transaction_id: router_data.connector_request_reference_id.clone(),
                    transaction_type: GidadatTransactionType::Cpi,
                    name,
                    sandbox,
                    email,
                    mobile,
                })
            }
            PaymentMethodData::BankRedirect(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Gigadat"),
            ))?,

            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Gigadat"),
            )
            .into()),
        }
    }
}

#[derive(Debug, Clone)]
pub struct GigadatAuthType {
    pub campaign_id: Secret<String>,
    pub access_token: Secret<String>,
    pub security_token: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for GigadatAuthType {
    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 {
                security_token: api_secret.to_owned(),
                access_token: api_key.to_owned(),
                campaign_id: key1.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigadatPaymentResponse {
    pub token: Secret<String>,
    pub data: GigadatPaymentData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GigadatPaymentData {
    pub transaction_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GigadatTransactionStatus {
    StatusInited,
    StatusSuccess,
    StatusRejected,
    StatusRejected1,
    StatusExpired,
    StatusAborted1,
    StatusPending,
    StatusFailed,
}

impl From<GigadatTransactionStatus> for enums::AttemptStatus {
    fn from(item: GigadatTransactionStatus) -> Self {
        match item {
            GigadatTransactionStatus::StatusSuccess => Self::Charged,
            GigadatTransactionStatus::StatusInited | GigadatTransactionStatus::StatusPending => {
                Self::Pending
            }
            GigadatTransactionStatus::StatusRejected
            | GigadatTransactionStatus::StatusExpired
            | GigadatTransactionStatus::StatusRejected1
            | GigadatTransactionStatus::StatusAborted1
            | GigadatTransactionStatus::StatusFailed => Self::Failure,
        }
    }
}

pub enum GigadatFlow {
    Payment,
    #[cfg(feature = "payouts")]
    Payout,
}

impl GigadatFlow {
    pub fn get_flow(webhook_type: &str) -> Result<Self, errors::ConnectorError> {
        match webhook_type {
            #[cfg(feature = "payouts")]
            "ETO" | "RTO" | "RTX" | "ANR" | "ANX" => Ok(Self::Payout),

            "ETI" | "RFM" | "CPI" | "ACK" => Ok(Self::Payment),
            _ => Err(errors::ConnectorError::NotImplemented(
                "Invalid transaction type ".to_string(),
            )),
        }
    }
}

pub fn get_gigadat_webhook_event_type(
    status: GigadatTransactionStatus,
    flow: GigadatFlow,
) -> IncomingWebhookEvent {
    match flow {
        GigadatFlow::Payment => match status {
            GigadatTransactionStatus::StatusSuccess => IncomingWebhookEvent::PaymentIntentSuccess,
            GigadatTransactionStatus::StatusFailed
            | GigadatTransactionStatus::StatusRejected
            | GigadatTransactionStatus::StatusRejected1
            | GigadatTransactionStatus::StatusExpired
            | GigadatTransactionStatus::StatusAborted1 => {
                IncomingWebhookEvent::PaymentIntentFailure
            }
            GigadatTransactionStatus::StatusInited | GigadatTransactionStatus::StatusPending => {
                IncomingWebhookEvent::PaymentIntentProcessing
            }
        },
        #[cfg(feature = "payouts")]
        GigadatFlow::Payout => match status {
            GigadatTransactionStatus::StatusSuccess => IncomingWebhookEvent::PayoutSuccess,
            GigadatTransactionStatus::StatusFailed
            | GigadatTransactionStatus::StatusRejected
            | GigadatTransactionStatus::StatusRejected1
            | GigadatTransactionStatus::StatusExpired
            | GigadatTransactionStatus::StatusAborted1 => IncomingWebhookEvent::PayoutFailure,
            GigadatTransactionStatus::StatusInited | GigadatTransactionStatus::StatusPending => {
                IncomingWebhookEvent::PayoutProcessing
            }
        },
    }
}

impl TryFrom<String> for GigadatTransactionStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: String) -> Result<Self, Self::Error> {
        match value.as_str() {
            "STATUS_INITED" => Ok(Self::StatusInited),
            "STATUS_SUCCESS" => Ok(Self::StatusSuccess),
            "STATUS_REJECTED" => Ok(Self::StatusRejected),
            "STATUS_REJECTED1" => Ok(Self::StatusRejected1),
            "STATUS_EXPIRED" => Ok(Self::StatusExpired),
            "STATUS_ABORTED1" => Ok(Self::StatusAborted1),
            "STATUS_PENDING" => Ok(Self::StatusPending),
            "STATUS_FAILED" => Ok(Self::StatusFailed),
            _ => Err(errors::ConnectorError::WebhookBodyDecodingFailed.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigadatTransactionStatusResponse {
    pub status: GigadatTransactionStatus,
}

impl<F, T> TryFrom<ResponseRouterData<F, GigadatPaymentResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, GigadatPaymentResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        // Will be raising a sepearte PR to populate a field connect_base_url in routerData and use it here
        let base_url = CONNECTOR_BASE_URL;

        let redirect_url = format!(
            "{}webflow?transaction={}&token={}",
            base_url,
            item.data.connector_request_reference_id,
            item.response.token.peek()
        );

        let redirection_data = Some(RedirectForm::Form {
            endpoint: redirect_url,
            method: Method::Get,
            form_fields: Default::default(),
        });
        Ok(Self {
            status: enums::AttemptStatus::AuthenticationPending,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.data.transaction_id),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, GigadatTransactionStatusResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, GigadatTransactionStatusResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.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
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct GigadatRefundRequest {
    pub amount: FloatMajorUnit,
    pub transaction_id: String,
    pub campaign_id: Secret<String>,
}

impl<F> TryFrom<&GigadatRouterData<&RefundsRouterData<F>>> for GigadatRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &GigadatRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth_type = GigadatAuthType::try_from(&item.router_data.connector_auth_type)?;
        Ok(Self {
            amount: item.amount.to_owned(),
            transaction_id: item.router_data.request.connector_transaction_id.clone(),
            campaign_id: auth_type.campaign_id,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    success: bool,
    data: GigadatPaymentData,
}

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 = match item.http_code {
            200 => enums::RefundStatus::Success,
            400 | 401 | 422 => enums::RefundStatus::Failure,
            _ => enums::RefundStatus::Pending,
        };

        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.data.transaction_id.to_string(),
                refund_status,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GigadatPayoutQuoteRequest {
    pub amount: FloatMajorUnit,
    pub campaign: Secret<String>,
    pub currency: Currency,
    pub email: Email,
    pub mobile: Secret<String>,
    pub name: Secret<String>,
    pub site: String,
    pub transaction_id: String,
    #[serde(rename = "type")]
    pub transaction_type: GidadatTransactionType,
    pub user_id: id_type::CustomerId,
    pub user_ip: Secret<String, IpAddress>,
    pub sandbox: bool,
}

// Payouts fulfill request transform
#[cfg(feature = "payouts")]
impl TryFrom<&GigadatRouterData<&PayoutsRouterData<PoQuote>>> for GigadatPayoutQuoteRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GigadatRouterData<&PayoutsRouterData<PoQuote>>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.get_payout_method_data()? {
            PayoutMethodData::BankRedirect(BankRedirect::Interac(interac_data)) => {
                let metadata: GigadatConnectorMetadataObject =
                    utils::to_connector_meta_from_secret(
                        item.router_data.connector_meta_data.clone(),
                    )
                    .change_context(
                        errors::ConnectorError::InvalidConnectorConfig {
                            config: "merchant_connector_account.metadata",
                        },
                    )?;

                let router_data = item.router_data;
                let name = router_data.get_billing_full_name()?;
                let email = interac_data.email;
                let mobile = router_data.get_billing_phone_number()?;
                let currency = item.router_data.request.destination_currency;

                let user_ip = router_data.request.get_browser_info()?.get_ip_address()?;
                let auth_type = GigadatAuthType::try_from(&item.router_data.connector_auth_type)?;
                let sandbox = match item.router_data.test_mode {
                    Some(true) => true,
                    Some(false) | None => false,
                };

                Ok(Self {
                    user_id: router_data.get_customer_id()?,
                    site: metadata.site,
                    user_ip,
                    currency,
                    amount: item.amount,
                    transaction_id: router_data.connector_request_reference_id.clone(),
                    transaction_type: GidadatTransactionType::Eto,
                    name,
                    email,
                    mobile,
                    campaign: auth_type.campaign_id,
                    sandbox,
                })
            }
            PayoutMethodData::Card(_)
            | PayoutMethodData::Bank(_)
            | PayoutMethodData::Wallet(_)
            | PayoutMethodData::Passthrough(_) => Err(errors::ConnectorError::NotSupported {
                message: "Payment Method Not Supported".to_string(),
                connector: "Gigadat",
            })?,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigadatPayoutQuoteResponse {
    pub token: Secret<String>,
    pub data: GigadatPayoutData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GigadatPayoutData {
    pub transaction_id: String,
    #[serde(rename = "type")]
    pub transaction_type: String,
}

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

#[cfg(feature = "payouts")]
impl<F> TryFrom<PayoutsResponseRouterData<F, GigadatPayoutQuoteResponse>> for PayoutsRouterData<F> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PayoutsResponseRouterData<F, GigadatPayoutQuoteResponse>,
    ) -> Result<Self, Self::Error> {
        let connector_meta = serde_json::json!(GigadatPayoutMeta {
            token: item.response.token,
        });
        Ok(Self {
            response: Ok(PayoutsResponseData {
                status: None,
                connector_payout_id: Some(item.response.data.transaction_id),
                payout_eligible: None,
                should_add_next_step_to_process_tracker: false,
                error_code: None,
                error_message: None,
                payout_connector_metadata: Some(Secret::new(connector_meta)),
            }),
            ..item.data
        })
    }
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigadatPayoutResponse {
    pub id: String,
    pub status: GigadatPayoutStatus,
    pub data: GigadatPayoutData,
}

#[cfg(feature = "payouts")]
impl<F> TryFrom<PayoutsResponseRouterData<F, GigadatPayoutResponse>> for PayoutsRouterData<F> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PayoutsResponseRouterData<F, GigadatPayoutResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PayoutsResponseData {
                status: Some(enums::PayoutStatus::from(item.response.status)),
                connector_payout_id: Some(item.response.data.transaction_id),
                payout_eligible: None,
                should_add_next_step_to_process_tracker: false,
                error_code: None,
                error_message: None,
                payout_connector_metadata: None,
            }),
            ..item.data
        })
    }
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigadatPayoutSyncResponse {
    pub status: GigadatPayoutStatus,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GigadatPayoutStatus {
    StatusInited,
    StatusSuccess,
    StatusRejected,
    StatusRejected1,
    StatusExpired,
    StatusAborted1,
    StatusPending,
    StatusFailed,
}

#[cfg(feature = "payouts")]
impl From<GigadatPayoutStatus> for enums::PayoutStatus {
    fn from(item: GigadatPayoutStatus) -> Self {
        match item {
            GigadatPayoutStatus::StatusSuccess => Self::Success,
            GigadatPayoutStatus::StatusPending => Self::RequiresFulfillment,
            GigadatPayoutStatus::StatusInited => Self::Pending,
            GigadatPayoutStatus::StatusRejected
            | GigadatPayoutStatus::StatusExpired
            | GigadatPayoutStatus::StatusRejected1
            | GigadatPayoutStatus::StatusAborted1
            | GigadatPayoutStatus::StatusFailed => Self::Failed,
        }
    }
}

#[cfg(feature = "payouts")]
impl<F> TryFrom<PayoutsResponseRouterData<F, GigadatPayoutSyncResponse>> for PayoutsRouterData<F> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PayoutsResponseRouterData<F, GigadatPayoutSyncResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PayoutsResponseData {
                status: Some(enums::PayoutStatus::from(item.response.status)),
                connector_payout_id: None,
                payout_eligible: None,
                should_add_next_step_to_process_tracker: false,
                error_code: None,
                error_message: None,
                payout_connector_metadata: None,
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct GigadatErrorResponse {
    pub err: String,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct GigadatRefundErrorResponse {
    pub error: Vec<Error>,
    pub message: String,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Error {
    pub code: Option<String>,
    pub detail: String,
}

#[derive(Debug, Deserialize)]
pub struct GigadatWebhookQueryParameters {
    pub transaction: String,
    pub status: GigadatTransactionStatus,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GigadatWebhookKeyValue {
    pub key: String,
    pub value: String,
}