File: crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs
 Public structs: 28

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,
}


pub fn is_disabled(&self) -> bool {
        self.disabled.unwrap_or(false)
    }


pub trait GsmValidation<F: Send + Clone + Sync, FData: Send + Sync, Resp> {
    // TODO : move this function to appropriate place later.
    fn should_call_gsm(&self) -> bool;
}


                let val = secret_val
                    .into_iter()
                    .map(|secret| secret.expose())
                    .collect();
                serde_json::Value::Array(val)
                    .parse_value("PaymentMethods")
                    .change_context(errors::ApiErrorResponse::InternalServerError)?
            }
            None => None,
        };
        let frm_configs = match item.frm_configs {
            Some(ref frm_value) => {
                let configs_for_frm : Vec<api_models::admin::FrmConfigs> = frm_value
                    .iter()
                    .map(|config| { config
                        .peek()
                        .clone()
                        .parse_value("FrmConfigs")
                        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                            field_name: "frm_configs".to_string(),
                            expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","payment_method_types": [{"payment_method_type": "credit","card_networks": ["Visa"],"flow": "pre","action": "cancel_txn"}]}]}]"#.to_string(),
                        })
                    })
                    .collect::<Result<Vec<_>, _>>()?;
                Some(configs_for_frm)
            }
            None => None,
        };
        // parse the connector_account_details into ConnectorAuthType
        let connector_account_details: hyperswitch_domain_models::router_data::ConnectorAuthType =
            item.connector_account_details
                .clone()
                .into_inner()
                .parse_value("ConnectorAuthType")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed while parsing value for ConnectorAuthType")?;
        // get the masked keys from the ConnectorAuthType and encode it to secret value
        let masked_connector_account_details = Secret::new(
            connector_account_details
                .get_masked_keys()
                .encode_to_value()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to encode ConnectorAuthType")?,
        );
        #[cfg(feature = "v2")]
        let response = Self {
            id: item.get_id(),
            connector_type: item.connector_type,
            connector_name: item.connector_name,
            connector_label: item.connector_label,
            connector_account_details: masked_connector_account_details,
            disabled: item.disabled,
            payment_methods_enabled,
            metadata: item.metadata,
            frm_configs,
            connector_webhook_details: item
                .connector_webhook_details
                .map(|webhook_details| {
                    serde_json::Value::parse_value(
                        webhook_details.expose(),
                        "MerchantConnectorWebhookDetails",
                    )
                    .attach_printable("Unable to deserialize connector_webhook_details")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
            profile_id: item.profile_id,
            applepay_verified_domains: item.applepay_verified_domains,
            pm_auth_config: item.pm_auth_config,
            status: item.status,
            additional_merchant_data: item
                .additional_merchant_data
                .map(|data| {
                    let data = data.into_inner();
                    serde_json::Value::parse_value::<router_types::AdditionalMerchantData>(
                        data.expose(),
                        "AdditionalMerchantData",
                    )
                    .attach_printable("Unable to deserialize additional_merchant_data")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?
                .map(api_models::admin::AdditionalMerchantData::foreign_from),
            connector_wallets_details: item
                .connector_wallets_details
                .map(|data| {
                    data.into_inner()
                        .expose()
                        .parse_value::<api_models::admin::ConnectorWalletDetails>(
                            "ConnectorWalletDetails",
                        )
                        .attach_printable("Unable to deserialize connector_wallets_details")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
        };
        #[cfg(feature = "v1")]
        let response = Self {
            connector_type: item.connector_type,
            connector_name: item.connector_name,
            connector_label: item.connector_label,
            merchant_connector_id: item.merchant_connector_id,
            connector_account_details: masked_connector_account_details,
            test_mode: item.test_mode,
            disabled: item.disabled,
            payment_methods_enabled,
            metadata: item.metadata,
            business_country: item.business_country,
            business_label: item.business_label,
            business_sub_label: item.business_sub_label,
            frm_configs,
            connector_webhook_details: item
                .connector_webhook_details
                .map(|webhook_details| {
                    serde_json::Value::parse_value(
                        webhook_details.expose(),
                        "MerchantConnectorWebhookDetails",
                    )
                    .attach_printable("Unable to deserialize connector_webhook_details")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
            profile_id: item.profile_id,
            applepay_verified_domains: item.applepay_verified_domains,
            pm_auth_config: item.pm_auth_config,
            status: item.status,
            additional_merchant_data: item
                .additional_merchant_data
                .map(|data| {
                    let data = data.into_inner();
                    serde_json::Value::parse_value::<router_types::AdditionalMerchantData>(
                        data.expose(),
                        "AdditionalMerchantData",
                    )
                    .attach_printable("Unable to deserialize additional_merchant_data")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?
                .map(api_models::admin::AdditionalMerchantData::foreign_from),
            connector_wallets_details: item
                .connector_wallets_details
                .map(|data| {
                    data.into_inner()
                        .expose()
                        .parse_value::<api_models::admin::ConnectorWalletDetails>(
                            "ConnectorWalletDetails",
                        )
                        .attach_printable("Unable to deserialize connector_wallets_details")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
        };
        Ok(response)
    }
}

#[cfg(feature = "v2")]
impl ForeignTryFrom<domain::MerchantConnectorAccount>
    for api_models::admin::MerchantConnectorResponse
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(item: domain::MerchantConnectorAccount) -> Result<Self, Self::Error> {
        let frm_configs = match item.frm_configs {
            Some(ref frm_value) => {
                let configs_for_frm : Vec<api_models::admin::FrmConfigs> = frm_value
                    .iter()
                    .map(|config| { config
                        .peek()
                        .clone()
                        .parse_value("FrmConfigs")
                        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                            field_name: "frm_configs".to_string(),
                            expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","payment_method_types": [{"payment_method_type": "credit","card_networks": ["Visa"],"flow": "pre","action": "cancel_txn"}]}]}]"#.to_string(),
                        })
                    })
                    .collect::<Result<Vec<_>, _>>()?;
                Some(configs_for_frm)
            }
            None => None,
        };

        // parse the connector_account_details into ConnectorAuthType
        let connector_account_details: hyperswitch_domain_models::router_data::ConnectorAuthType =
            item.connector_account_details
                .clone()
                .into_inner()
                .parse_value("ConnectorAuthType")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed while parsing value for ConnectorAuthType")?;
        // get the masked keys from the ConnectorAuthType and encode it to secret value
        let masked_connector_account_details = Secret::new(
            connector_account_details
                .get_masked_keys()
                .encode_to_value()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to encode ConnectorAuthType")?,
        );

        let feature_metadata = item.feature_metadata.as_ref().map(|metadata| {
            api_models::admin::MerchantConnectorAccountFeatureMetadata::foreign_from(metadata)
        });

        let response = Self {
            id: item.get_id(),
            connector_type: item.connector_type,
            connector_name: item.connector_name,
            connector_label: item.connector_label,
            connector_account_details: masked_connector_account_details,
            disabled: item.disabled,
            payment_methods_enabled: item.payment_methods_enabled,
            metadata: item.metadata,
            frm_configs,
            connector_webhook_details: item
                .connector_webhook_details
                .map(|webhook_details| {
                    serde_json::Value::parse_value(
                        webhook_details.expose(),
                        "MerchantConnectorWebhookDetails",
                    )
                    .attach_printable("Unable to deserialize connector_webhook_details")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
            profile_id: item.profile_id,
            applepay_verified_domains: item.applepay_verified_domains,
            pm_auth_config: item.pm_auth_config,
            status: item.status,
            additional_merchant_data: item
                .additional_merchant_data
                .map(|data| {
                    let data = data.into_inner();
                    serde_json::Value::parse_value::<router_types::AdditionalMerchantData>(
                        data.expose(),
                        "AdditionalMerchantData",
                    )
                    .attach_printable("Unable to deserialize additional_merchant_data")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?
                .map(api_models::admin::AdditionalMerchantData::foreign_from),
            connector_wallets_details: item
                .connector_wallets_details
                .map(|data| {
                    data.into_inner()
                        .expose()
                        .parse_value::<api_models::admin::ConnectorWalletDetails>(
                            "ConnectorWalletDetails",
                        )
                        .attach_printable("Unable to deserialize connector_wallets_details")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                })
                .transpose()?,
            feature_metadata,
        };
        Ok(response)
    }
}

#[cfg(feature = "v1")]
impl ForeignFrom<storage::PaymentAttempt> for payments::PaymentAttemptResponse {
    fn foreign_from(payment_attempt: storage::PaymentAttempt) -> Self {
        let connector_transaction_id = payment_attempt
            .get_connector_payment_id()
            .map(ToString::to_string);
        Self {
            attempt_id: payment_attempt.attempt_id,
            status: payment_attempt.status,
            amount: payment_attempt.net_amount.get_order_amount(),
            order_tax_amount: payment_attempt.net_amount.get_order_tax_amount(),
            currency: payment_attempt.currency,
            connector: payment_attempt.connector,
            error_message: payment_attempt.error_reason,
            payment_method: payment_attempt.payment_method,
            connector_transaction_id,
            capture_method: payment_attempt.capture_method,
            authentication_type: payment_attempt.authentication_type,
            created_at: payment_attempt.created_at,
            modified_at: payment_attempt.modified_at,
            cancellation_reason: payment_attempt.cancellation_reason,
            mandate_id: payment_attempt.mandate_id,
            error_code: payment_attempt.error_code,
            payment_token: payment_attempt.payment_token,
            connector_metadata: payment_attempt.connector_metadata,
            payment_experience: payment_attempt.payment_experience,
            payment_method_type: payment_attempt.payment_method_type,
            reference_id: payment_attempt.connector_response_reference_id,
            unified_code: payment_attempt.unified_code,
            unified_message: payment_attempt.unified_message,
            client_source: payment_attempt.client_source,
            client_version: payment_attempt.client_version,
        }
    }
}

impl ForeignFrom<storage::Capture> for payments::CaptureResponse {
    fn foreign_from(capture: storage::Capture) -> Self {
        let connector_capture_id = capture.get_optional_connector_transaction_id().cloned();
        Self {
            capture_id: capture.capture_id,
            status: capture.status,
            amount: capture.amount,
            currency: capture.currency,
            connector: capture.connector,
            authorized_attempt_id: capture.authorized_attempt_id,
            connector_capture_id,
            capture_sequence: capture.capture_sequence,
            error_message: capture.error_message,
            error_code: capture.error_code,
            error_reason: capture.error_reason,
            reference_id: capture.connector_response_reference_id,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentMethodType {
    fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self {
        match value {
            api_models::payouts::PayoutMethodData::Bank(bank) => Self::foreign_from(bank),
            api_models::payouts::PayoutMethodData::Card(_) => Self::Debit,
            api_models::payouts::PayoutMethodData::Wallet(wallet) => Self::foreign_from(wallet),
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::Bank> for api_enums::PaymentMethodType {
    fn foreign_from(value: &api_models::payouts::Bank) -> Self {
        match value {
            api_models::payouts::Bank::Ach(_) => Self::Ach,
            api_models::payouts::Bank::Bacs(_) => Self::Bacs,
            api_models::payouts::Bank::Sepa(_) => Self::SepaBankTransfer,
            api_models::payouts::Bank::Pix(_) => Self::Pix,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::Wallet> for api_enums::PaymentMethodType {
    fn foreign_from(value: &api_models::payouts::Wallet) -> Self {
        match value {
            api_models::payouts::Wallet::Paypal(_) => Self::Paypal,
            api_models::payouts::Wallet::Venmo(_) => Self::Venmo,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentMethod {
    fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self {
        match value {
            api_models::payouts::PayoutMethodData::Bank(_) => Self::BankTransfer,
            api_models::payouts::PayoutMethodData::Card(_) => Self::Card,
            api_models::payouts::PayoutMethodData::Wallet(_) => Self::Wallet,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_models::enums::PayoutType {
    fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self {
        match value {
            api_models::payouts::PayoutMethodData::Bank(_) => Self::Bank,
            api_models::payouts::PayoutMethodData::Card(_) => Self::Card,
            api_models::payouts::PayoutMethodData::Wallet(_) => Self::Wallet,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignFrom<api_models::enums::PayoutType> for api_enums::PaymentMethod {
    fn foreign_from(value: api_models::enums::PayoutType) -> Self {
        match value {
            api_models::enums::PayoutType::Bank => Self::BankTransfer,
            api_models::enums::PayoutType::Card => Self::Card,
            api_models::enums::PayoutType::Wallet => Self::Wallet,
        }
    }
}

#[cfg(feature = "payouts")]
impl ForeignTryFrom<api_enums::PaymentMethod> for api_models::enums::PayoutType {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn foreign_try_from(value: api_enums::PaymentMethod) -> Result<Self, Self::Error> {
        match value {
            api_enums::PaymentMethod::Card => Ok(Self::Card),
            api_enums::PaymentMethod::BankTransfer => Ok(Self::Bank),
            api_enums::PaymentMethod::Wallet => Ok(Self::Wallet),
            _ => Err(errors::ApiErrorResponse::InvalidRequestData {
                message: format!("PaymentMethod {value:?} is not supported for payouts"),
            })
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to convert PaymentMethod to PayoutType"),
        }
    }
}

#[cfg(feature = "v1")]
impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderPayload {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(headers: &HeaderMap) -> Result<Self, Self::Error> {
        let payment_confirm_source: Option<api_enums::PaymentSource> =
            get_header_value_by_key(X_PAYMENT_CONFIRM_SOURCE.into(), headers)?
                .map(|source| {
                    source
                        .to_owned()
                        .parse_enum("PaymentSource")
                        .change_context(errors::ApiErrorResponse::InvalidRequestData {
                            message: "Invalid data received in payment_confirm_source header"
                                .into(),
                        })
                        .attach_printable(
                            "Failed while paring PaymentConfirmSource header value to enum",
                        )
                })
                .transpose()?;
        when(
            payment_confirm_source.is_some_and(|payment_confirm_source| {
                payment_confirm_source.is_for_internal_use_only()
            }),
            || {
                Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Invalid data received in payment_confirm_source header".into(),
                }))
            },
        )?;
        let locale =
            get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers)?.map(|val| val.to_string());
        let x_hs_latency = get_header_value_by_key(X_HS_LATENCY.into(), headers)
            .map(|value| value == Some("true"))
            .unwrap_or(false);

        let client_source =
            get_header_value_by_key(X_CLIENT_SOURCE.into(), headers)?.map(|val| val.to_string());

        let client_version =
            get_header_value_by_key(X_CLIENT_VERSION.into(), headers)?.map(|val| val.to_string());

        let browser_name_str =
            get_header_value_by_key(BROWSER_NAME.into(), headers)?.map(|val| val.to_string());

        let browser_name: Option<api_enums::BrowserName> = browser_name_str.map(|browser_name| {
            browser_name
                .parse_enum("BrowserName")
                .unwrap_or(api_enums::BrowserName::Unknown)
        });

        let x_client_platform_str =
            get_header_value_by_key(X_CLIENT_PLATFORM.into(), headers)?.map(|val| val.to_string());

        let x_client_platform: Option<api_enums::ClientPlatform> =
            x_client_platform_str.map(|x_client_platform| {
                x_client_platform
                    .parse_enum("ClientPlatform")
                    .unwrap_or(api_enums::ClientPlatform::Unknown)
            });

        let x_merchant_domain =
            get_header_value_by_key(X_MERCHANT_DOMAIN.into(), headers)?.map(|val| val.to_string());

        let x_app_id =
            get_header_value_by_key(X_APP_ID.into(), headers)?.map(|val| val.to_string());

        let x_redirect_uri =
            get_header_value_by_key(X_REDIRECT_URI.into(), headers)?.map(|val| val.to_string());

        Ok(Self {
            payment_confirm_source,
            client_source,
            client_version,
            x_hs_latency: Some(x_hs_latency),
            browser_name,
            x_client_platform,
            x_merchant_domain,
            locale,
            x_app_id,
            x_redirect_uri,
        })
    }
}

#[cfg(feature = "v2")]
impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderPayload {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(headers: &HeaderMap) -> Result<Self, Self::Error> {
        use std::str::FromStr;

        use crate::headers::X_CLIENT_SECRET;

        let payment_confirm_source: Option<api_enums::PaymentSource> =
            get_header_value_by_key(X_PAYMENT_CONFIRM_SOURCE.into(), headers)?
                .map(|source| {
                    source
                        .to_owned()
                        .parse_enum("PaymentSource")
                        .change_context(errors::ApiErrorResponse::InvalidRequestData {
                            message: "Invalid data received in payment_confirm_source header"
                                .into(),
                        })
                        .attach_printable(
                            "Failed while paring PaymentConfirmSource header value to enum",
                        )
                })
                .transpose()?;
        when(
            payment_confirm_source.is_some_and(|payment_confirm_source| {
                payment_confirm_source.is_for_internal_use_only()
            }),
            || {
                Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Invalid data received in payment_confirm_source header".into(),
                }))
            },
        )?;
        let locale =
            get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers)?.map(|val| val.to_string());
        let x_hs_latency = get_header_value_by_key(X_HS_LATENCY.into(), headers)
            .map(|value| value == Some("true"))
            .unwrap_or(false);

        let client_source =
            get_header_value_by_key(X_CLIENT_SOURCE.into(), headers)?.map(|val| val.to_string());

        let client_version =
            get_header_value_by_key(X_CLIENT_VERSION.into(), headers)?.map(|val| val.to_string());

        let browser_name_str =
            get_header_value_by_key(BROWSER_NAME.into(), headers)?.map(|val| val.to_string());

        let browser_name: Option<api_enums::BrowserName> = browser_name_str.map(|browser_name| {
            browser_name
                .parse_enum("BrowserName")
                .unwrap_or(api_enums::BrowserName::Unknown)
        });

        let x_client_platform_str =
            get_header_value_by_key(X_CLIENT_PLATFORM.into(), headers)?.map(|val| val.to_string());

        let x_client_platform: Option<api_enums::ClientPlatform> =
            x_client_platform_str.map(|x_client_platform| {
                x_client_platform
                    .parse_enum("ClientPlatform")
                    .unwrap_or(api_enums::ClientPlatform::Unknown)
            });

        let x_merchant_domain =
            get_header_value_by_key(X_MERCHANT_DOMAIN.into(), headers)?.map(|val| val.to_string());

        let x_app_id =
            get_header_value_by_key(X_APP_ID.into(), headers)?.map(|val| val.to_string());

        let x_redirect_uri =
            get_header_value_by_key(X_REDIRECT_URI.into(), headers)?.map(|val| val.to_string());

        Ok(Self {
            payment_confirm_source,
            // client_source,
            // client_version,
            x_hs_latency: Some(x_hs_latency),
            browser_name,
            x_client_platform,
            x_merchant_domain,
            locale,
            x_app_id,
            x_redirect_uri,
        })
    }
}

#[cfg(feature = "v1")]
impl
    ForeignTryFrom<(
        Option<&storage::PaymentAttempt>,
        Option<&storage::PaymentIntent>,
        Option<&domain::Address>,
        Option<&domain::Address>,
        Option<&domain::Customer>,
    )> for payments::PaymentsRequest
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(
        value: (
            Option<&storage::PaymentAttempt>,
            Option<&storage::PaymentIntent>,
            Option<&domain::Address>,
            Option<&domain::Address>,
            Option<&domain::Customer>,
        ),
    ) -> Result<Self, Self::Error> {
        let (payment_attempt, payment_intent, shipping, billing, customer) = value;
        // Populating the dynamic fields directly, for the cases where we have customer details stored in
        // Payment Intent
        let customer_details_from_pi = payment_intent
            .and_then(|payment_intent| payment_intent.customer_details.clone())
            .map(|customer_details| {
                customer_details
                    .into_inner()
                    .peek()
                    .clone()
                    .parse_value::<CustomerData>("CustomerData")
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "customer_details",
                    })
                    .attach_printable("Failed to parse customer_details")
            })
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "customer_details",
            })?;

        let mut billing_address = billing
            .map(hyperswitch_domain_models::address::Address::from)
            .map(api_types::Address::from);

        // This change is to fix a merchant integration
        // If billing.email is not passed by the merchant, and if the customer email is present, then use the `customer.email` as the billing email
        if let Some(billing_address) = &mut billing_address {
            billing_address.email = billing_address.email.clone().or_else(|| {
                customer
                    .and_then(|cust| {
                        cust.email
                            .as_ref()
                            .map(|email| pii::Email::from(email.clone()))
                    })
                    .or(customer_details_from_pi.clone().and_then(|cd| cd.email))
            });
        } else {
            billing_address = Some(payments::Address {
                email: customer
                    .and_then(|cust| {
                        cust.email
                            .as_ref()
                            .map(|email| pii::Email::from(email.clone()))
                    })
                    .or(customer_details_from_pi.clone().and_then(|cd| cd.email)),
                ..Default::default()
            });
        }

        Ok(Self {
            currency: payment_attempt.map(|pa| pa.currency.unwrap_or_default()),
            shipping: shipping
                .map(hyperswitch_domain_models::address::Address::from)
                .map(api_types::Address::from),
            billing: billing_address,
            amount: payment_attempt
                .map(|pa| api_types::Amount::from(pa.net_amount.get_order_amount())),
            email: customer
                .and_then(|cust| cust.email.as_ref().map(|em| pii::Email::from(em.clone())))
                .or(customer_details_from_pi.clone().and_then(|cd| cd.email)),
            phone: customer
                .and_then(|cust| cust.phone.as_ref().map(|p| p.clone().into_inner()))
                .or(customer_details_from_pi.clone().and_then(|cd| cd.phone)),
            name: customer
                .and_then(|cust| cust.name.as_ref().map(|n| n.clone().into_inner()))
                .or(customer_details_from_pi.clone().and_then(|cd| cd.name)),
            ..Self::default()
        })
    }
}

impl ForeignFrom<(storage::PaymentLink, payments::PaymentLinkStatus)>
    for payments::RetrievePaymentLinkResponse
{
    fn foreign_from(
        (payment_link_config, status): (storage::PaymentLink, payments::PaymentLinkStatus),
    ) -> Self {
        Self {
            payment_link_id: payment_link_config.payment_link_id,
            merchant_id: payment_link_config.merchant_id,
            link_to_pay: payment_link_config.link_to_pay,
            amount: payment_link_config.amount,
            created_at: payment_link_config.created_at,
            expiry: payment_link_config.fulfilment_time,
            description: payment_link_config.description,
            currency: payment_link_config.currency,
            status,
            secure_link: payment_link_config.secure_link,
        }
    }
}

impl From<domain::Address> for payments::AddressDetails {
    fn from(addr: domain::Address) -> Self {
        Self {
            city: addr.city,
            country: addr.country,
            line1: addr.line1.map(Encryptable::into_inner),
            line2: addr.line2.map(Encryptable::into_inner),
            line3: addr.line3.map(Encryptable::into_inner),
            zip: addr.zip.map(Encryptable::into_inner),
            state: addr.state.map(Encryptable::into_inner),
            first_name: addr.first_name.map(Encryptable::into_inner),
            last_name: addr.last_name.map(Encryptable::into_inner),
            origin_zip: addr.origin_zip.map(Encryptable::into_inner),
        }
    }
}

impl ForeignFrom<ConnectorSelection> for routing_types::StaticRoutingAlgorithm {
    fn foreign_from(value: ConnectorSelection) -> Self {
        match value {
            ConnectorSelection::Priority(connectors) => Self::Priority(connectors),

            ConnectorSelection::VolumeSplit(splits) => Self::VolumeSplit(splits),
        }
    }
}

impl ForeignFrom<api_models::organization::OrganizationNew>
    for diesel_models::organization::OrganizationNew
{
    fn foreign_from(item: api_models::organization::OrganizationNew) -> Self {
        Self::new(item.org_id, item.org_type, item.org_name)
    }
}

impl ForeignFrom<api_models::organization::OrganizationCreateRequest>
    for diesel_models::organization::OrganizationNew
{
    fn foreign_from(item: api_models::organization::OrganizationCreateRequest) -> Self {
        // Create a new organization with a standard type by default
        let org_new = api_models::organization::OrganizationNew::new(
            common_enums::OrganizationType::Standard,
            None,
        );
        let api_models::organization::OrganizationCreateRequest {
            organization_name,
            organization_details,
            metadata,
        } = item;
        let mut org_new_db = Self::new(org_new.org_id, org_new.org_type, Some(organization_name));
        org_new_db.organization_details = organization_details;
        org_new_db.metadata = metadata;
        org_new_db
    }
}

impl ForeignFrom<gsm_api_types::GsmCreateRequest>
    for hyperswitch_domain_models::gsm::GatewayStatusMap
{
    fn foreign_from(value: gsm_api_types::GsmCreateRequest) -> Self {
        let inferred_feature_data = common_types::domain::GsmFeatureData::foreign_from(&value);
        Self {
            connector: value.connector.to_string(),
            flow: value.flow,
            sub_flow: value.sub_flow,
            code: value.code,
            message: value.message,
            status: value.status,
            router_error: value.router_error,
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            feature_data: value.feature_data.unwrap_or(inferred_feature_data),
            feature: value.feature.unwrap_or(api_enums::GsmFeature::Retry),
        }
    }
}

impl ForeignFrom<&gsm_api_types::GsmCreateRequest> for common_types::domain::GsmFeatureData {
    fn foreign_from(value: &gsm_api_types::GsmCreateRequest) -> Self {
        // Defaulting alternate_network_possible to false as it is provided only in the Retry feature
        // If the retry feature is not used, we assume alternate network as false
        let alternate_network_possible = false;

        match value.feature {
            Some(api_enums::GsmFeature::Retry) | None => {
                Self::Retry(common_types::domain::RetryFeatureData {
                    step_up_possible: value.step_up_possible,
                    clear_pan_possible: value.clear_pan_possible,
                    alternate_network_possible,
                    decision: value.decision,
                })
            }
        }
    }
}

impl
    ForeignFrom<(
        &gsm_api_types::GsmUpdateRequest,
        hyperswitch_domain_models::gsm::GatewayStatusMap,
    )> for (api_enums::GsmFeature, common_types::domain::GsmFeatureData)
{
    fn foreign_from(
        (gsm_update_request, gsm_db_record): (
            &gsm_api_types::GsmUpdateRequest,
            hyperswitch_domain_models::gsm::GatewayStatusMap,
        ),
    ) -> Self {
        let gsm_db_record_inferred_feature = match gsm_db_record.feature_data.get_decision() {
            api_enums::GsmDecision::Retry | api_enums::GsmDecision::DoDefault => {
                api_enums::GsmFeature::Retry
            }
        };

        let gsm_feature = gsm_update_request
            .feature
            .unwrap_or(gsm_db_record_inferred_feature);

        match gsm_feature {
            api_enums::GsmFeature::Retry => {
                let gsm_db_record_retry_feature_data =
                    gsm_db_record.feature_data.get_retry_feature_data();

                let retry_feature_data = common_types::domain::GsmFeatureData::Retry(
                    common_types::domain::RetryFeatureData {
                        step_up_possible: gsm_update_request
                            .step_up_possible
                            .or(gsm_db_record_retry_feature_data
                                .clone()
                                .map(|data| data.step_up_possible))
                            .unwrap_or_default(),
                        clear_pan_possible: gsm_update_request
                            .clear_pan_possible
                            .or(gsm_db_record_retry_feature_data
                                .clone()
                                .map(|data| data.clear_pan_possible))
                            .unwrap_or_default(),
                        alternate_network_possible: gsm_db_record_retry_feature_data
                            .map(|data| data.alternate_network_possible)
                            .unwrap_or_default(),
                        decision: gsm_update_request
                            .decision
                            .or(Some(gsm_db_record.feature_data.get_decision()))
                            .unwrap_or_default(),
                    },
                );
                (api_enums::GsmFeature::Retry, retry_feature_data)
            }
        }
    }
}

impl ForeignFrom<hyperswitch_domain_models::gsm::GatewayStatusMap> for gsm_api_types::GsmResponse {
    fn foreign_from(value: hyperswitch_domain_models::gsm::GatewayStatusMap) -> Self {
        Self {
            connector: value.connector.to_string(),
            flow: value.flow,
            sub_flow: value.sub_flow,
            code: value.code,
            message: value.message,
            decision: value.feature_data.get_decision(),
            status: value.status,
            router_error: value.router_error,
            step_up_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|data| data.is_step_up_possible())
                .unwrap_or(false),
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            clear_pan_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|data| data.is_clear_pan_possible())
                .unwrap_or(false),
            feature_data: Some(value.feature_data),
            feature: value.feature,
        }
    }
}

#[cfg(feature = "v2")]
impl ForeignFrom<&domain::Customer> for payments::CustomerDetailsResponse {
    fn foreign_from(_customer: &domain::Customer) -> Self {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl ForeignFrom<&domain::Customer> for payments::CustomerDetailsResponse {
    fn foreign_from(customer: &domain::Customer) -> Self {
        Self {
            id: Some(customer.customer_id.clone()),
            name: customer
                .name
                .as_ref()
                .map(|name| name.get_inner().to_owned()),
            email: customer.email.clone().map(Into::into),
            phone: customer
                .phone
                .as_ref()
                .map(|phone| phone.get_inner().to_owned()),
            phone_country_code: customer.phone_country_code.clone(),
        }
    }
}

#[cfg(feature = "olap")]
impl ForeignTryFrom<api_types::webhook_events::EventListConstraints>
    for api_types::webhook_events::EventListConstraintsInternal
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn foreign_try_from(
        item: api_types::webhook_events::EventListConstraints,
    ) -> Result<Self, Self::Error> {
        if item.object_id.is_some()
            && (item.created_after.is_some()
                || item.created_before.is_some()
                || item.limit.is_some()
                || item.offset.is_some()
                || item.event_classes.is_some()
                || item.event_types.is_some())
        {
            return Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                message:
                    "Either only `object_id` must be specified, or one or more of \
                          `created_after`, `created_before`, `limit`, `offset`, `event_classes` and `event_types` must be specified"
                        .to_string()
            }));
        }

        match item.object_id {
            Some(object_id) => Ok(Self::ObjectIdFilter { object_id }),
            None => Ok(Self::GenericFilter {
                created_after: item.created_after,
                created_before: item.created_before,
                limit: item.limit.map(i64::from),
                offset: item.offset.map(i64::from),
                event_classes: item.event_classes,
                event_types: item.event_types,
                is_delivered: item.is_delivered,
            }),
        }
    }
}

#[cfg(feature = "olap")]
impl TryFrom<domain::Event> for api_models::webhook_events::EventListItemResponse {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(item: domain::Event) -> Result<Self, Self::Error> {
        use crate::utils::OptionExt;

        // We only allow retrieving events with merchant_id, business_profile_id
        // and initial_attempt_id populated.
        // We cannot retrieve events with only some of these fields populated.
        let merchant_id = item
            .merchant_id
            .get_required_value("merchant_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)?;
        let profile_id = item
            .business_profile_id
            .get_required_value("business_profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)?;
        let initial_attempt_id = item
            .initial_attempt_id
            .get_required_value("initial_attempt_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)?;

        Ok(Self {
            event_id: item.event_id,
            merchant_id,
            profile_id,
            object_id: item.primary_object_id,
            event_type: item.event_type,
            event_class: item.event_class,
            is_delivery_successful: item.is_overall_delivery_successful,
            initial_attempt_id,
            created: item.created_at,
        })
    }
}

#[cfg(feature = "olap")]
impl TryFrom<domain::Event> for api_models::webhook_events::EventRetrieveResponse {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(item: domain::Event) -> Result<Self, Self::Error> {
        use crate::utils::OptionExt;

        // We only allow retrieving events with all required fields in `EventListItemResponse`, and
        // `request` and `response` populated.
        // We cannot retrieve events with only some of these fields populated.
        let event_information =
            api_models::webhook_events::EventListItemResponse::try_from(item.clone())?;

        let request = item
            .request
            .get_required_value("request")
            .change_context(errors::ApiErrorResponse::InternalServerError)?
            .peek()
            .parse_struct("OutgoingWebhookRequestContent")


pub struct SignifydPaymentsResponse {
    signifyd_id: i64,
    order_id: String,
    decision: Decision,
}


pub struct RefundStatusDetails {
    state: RefundStatus,
    reason_code: Option<String>,
    reason_description: Option<String>,
    last_updated_timestamp: String,
}


pub struct WebhookEvent {
    pub resource_type: WebhookResourceType,
    pub action: WebhookAction,
    pub links: WebhooksLink,
}


pub struct ChargebeePaymentsRequest {
    amount: MinorUnit,
    card: ChargebeeCard,
}


pub struct AverageAccumulator {
    pub total: u32,
    pub count: u32,
}


pub struct PayloadCardsRequestData {
    pub amount: StringMajorUnit,
    #[serde(flatten)]
    pub card: PayloadCard,
    #[serde(rename = "type")]
    pub transaction_types: TransactionTypes,
    // For manual capture, set status to "authorized", otherwise omit
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<responses::PayloadPaymentStatus>,
    #[serde(rename = "payment_method[type]")]
    pub payment_method_type: String,
    // Billing address fields are for AVS validation
    #[serde(flatten)]
    pub billing_address: BillingAddress,
    pub processing_id: Option<Secret<String>>,
    /// Allows one-time payment by customer without saving their payment method
    /// This is true by default
    #[serde(rename = "payment_method[keep_active]")]
    pub keep_active: bool,
}


File: crates/router/tests/connectors/coingate.rs

use masking::Secret;
use router::types::{self, api, storage::enums};

use crate::utils::{self, ConnectorActions};
use test_utils::connector_auth;

#[derive(Clone, Copy)]
struct CoingateTest;
impl ConnectorActions for CoingateTest {}
impl utils::Connector for CoingateTest {
    fn get_data(&self) -> api::ConnectorData {
        use router::connector::Coingate;
        api::ConnectorData {
            connector: Box::new(Coingate::new()),
            connector_name: types::Connector::Coingate,
            get_token: types::api::GetToken::Connector,
            merchant_connector_id: None,
        }
    }

    fn get_auth_token(&self) -> types::ConnectorAuthType {
        utils::to_connector_auth_type(
            connector_auth::ConnectorAuthentication::new()
                .coingate
                .expect("Missing connector authentication configuration")
                .into(),
        )
    }

    fn get_name(&self) -> String {
        "coingate".to_string()
    }
}

static CONNECTOR: CoingateTest = CoingateTest {};

fn get_default_payment_info() -> Option<utils::PaymentInfo> {
    None
}

fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    None
}

// Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_only_authorize_payment() {
    let response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized);
}

// Captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Partially captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(
            payment_method_details(),
            Some(types::PaymentsCaptureData {
                amount_to_capture: 50,
                ..utils::PaymentCaptureType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let authorize_response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}

// Voids a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_void_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_void_payment(
            payment_method_details(),
            Some(types::PaymentsCancelData {
                connector_transaction_id: String::from(""),
                cancellation_reason: Some("requested_by_customer".to_string()),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Void payment response");
    assert_eq!(response.status, enums::AttemptStatus::Voided);
}

// Refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Synchronizes a refund using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_manually_captured_refund() {
    let refund_response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_make_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_auto_captured_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Charged,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                capture_method: Some(enums::CaptureMethod::Automatic),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(response.status, enums::AttemptStatus::Charged,);
}

// Refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_auto_captured_payment() {
    let response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_succeeded_payment() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        refund_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
    CONNECTOR
        .make_payment_and_multiple_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await;
}

// Synchronizes a refund using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_refund() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Cards Negative scenarios
// Creates a payment with incorrect CVC.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: types::api::PaymentMethodData::Card(api::Card {
                    card_cvc: Secret::new("12345".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's security code is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry month.
#[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: api::PaymentMethodData::Card(api::Card {
                    card_exp_month: Secret::new("20".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration month is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry year.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: api::PaymentMethodData::Card(api::Card {
                    card_exp_year: Secret::new("2000".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration year is invalid.".to_string(),
    );
}

// Voids a payment using automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let void_response = CONNECTOR
        .void_payment(txn_id.unwrap(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        void_response.response.unwrap_err().message,
        "You cannot cancel this PaymentIntent because it has a status of succeeded."
    );
}

// Captures a payment using invalid connector payment id.
#[actix_web::test]
async fn should_fail_capture_for_invalid_payment() {
    let capture_response = CONNECTOR
        .capture_payment("123456789".to_string(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        capture_response.response.unwrap_err().message,
        String::from("No such payment_intent: '123456789'")
    );
}

// Refunds a payment with refund amount higher than payment amount.
#[actix_web::test]
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
    let response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 150,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Refund amount (₹1.50) is greater than charge amount (₹1.00)",
    );
}

// Connector dependent test cases goes here

// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests


impl State for PmFetched {}


pub struct TaxjarCard {
    number: cards::CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    cvc: Secret<String>,
    complete: bool,
}


pub struct PaymentsMandateReferenceRecord {
    pub connector_mandate_id: String,
    pub payment_method_type: Option<common_enums::PaymentMethodType>,
    pub original_payment_authorized_amount: Option<i64>,
    pub original_payment_authorized_currency: Option<common_enums::Currency>,
}


pub fn get_verification_days_left(
    state: &SessionState,
    user: &UserFromStorage,
) -> UserResult<Option<i64>> {
    #[cfg(feature = "email")]
    return user.get_verification_days_left(state);
    #[cfg(not(feature = "email"))]
    return Ok(None);
}


pub struct GlobalpayWebhookObjectId {
    pub id: String,
}


pub struct RuleMigrationResult {
    pub success: Vec<RuleMigrationResponse>,
    pub errors: Vec<RuleMigrationError>,
}


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


impl<const T: u8> Payment for DummyConnector<T> {}


/// Checks if retry with PAN is possible.
    pub fn is_clear_pan_possible(&self) -> bool {
        self.clear_pan_possible
    }


impl ConnectorSpecifications for UnifiedAuthenticationService {}

impl ConnectorSpecifications for for UnifiedAuthenticationService


impl api::PaymentVoid for Boku {}


pub async fn get_payout_method_data_from_temporary_locker(
        state: &routes::SessionState,
        lookup_key: &str,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<(Option<api::PayoutMethodData>, SupplementaryVaultData)> {
        let de_tokenize =
            get_tokenized_data(state, lookup_key, true, merchant_key_store.key.get_inner()).await?;
        let (payout_method, supp_data) =
            api::PayoutMethodData::from_values(de_tokenize.value1, de_tokenize.value2)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error parsing Payout Method from Values")?;

        Ok((Some(payout_method), supp_data))
    }


Documentation: api-reference/v2/profile/profile--deactivate-routing-algorithm.mdx
# Type: Doc File

---
openapi: patch /v2/profiles/{id}/deactivate-routing-algorithm
---


impl api::PaymentToken for Bluesnap {}


impl api::PaymentAuthorize for Phonepe {}

impl api::PaymentAuthorize for for Phonepe


impl api::ExternalVaultRetrieve for Vgs {}

impl api::ExternalVaultRetrieve for for Vgs


Documentation: api-reference/v1/payments/payments--create.mdx
# Type: Doc File

---
openapi: post /payments
---
<Tip> Use the dropdown on the top right corner of the code snippet to try out different payment scenarios. </Tip>


impl api::MandateSetup for Gpayments {}

impl api::MandateSetup for for Gpayments


pub fn convert_forex_value(amount: i64, from_currency: JsValue, to_currency: JsValue) -> JsResult {
    let forex_data = SEED_FOREX
        .get()
        .ok_or("Forex Data not seeded")
        .err_to_js()?;
    let from_currency: common_enums::Currency = serde_wasm_bindgen::from_value(from_currency)?;
    let to_currency: common_enums::Currency = serde_wasm_bindgen::from_value(to_currency)?;
    let converted_amount = convert_currency(forex_data, from_currency, to_currency, amount)
        .map_err(|_| "conversion not possible for provided values")
        .err_to_js()?;

    Ok(serde_wasm_bindgen::to_value(&converted_amount)?)
}


pub struct NoonPaymentsOrderResponse {
    status: NoonPaymentStatus,
    id: u64,
    error_code: u64,
    error_message: Option<String>,
    reference: Option<String>,
}


impl ConnectorSpecifications for Paytm {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&*PAYTM_CONNECTOR_INFO)
    }

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

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

impl ConnectorSpecifications for for Paytm


OpenAPI Block Path: components.schemas.ApplePayAddressParameters
{
  "type": "string",
  "enum": [
    "postalAddress",
    "phone",
    "email"
  ]
}