File: crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs
 Public structs: 3

use std::collections::HashMap;

use api_models::payments::{MandateIds, MandateReferenceId};
use base64::Engine;
use common_enums::enums;
use common_utils::{
    consts::BASE64_ENGINE, errors::CustomResult, ext_traits::OptionExt, pii, types::MinorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    address,
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{Authorize, SetupMandate},
    router_request_types::{
        BrowserInformation, PaymentsAuthorizeData, ResponseId, SetupMandateRequestData,
    },
    router_response_types::{MandateReference, PaymentsResponseData, RedirectForm},
    types,
};
use hyperswitch_interfaces::{api, errors};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use super::{requests::*, response::*};
use crate::{
    types::ResponseRouterData,
    utils::{
        self, AddressData, ApplePay, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData,
        PaymentsSetupMandateRequestData, RouterData as RouterDataTrait,
    },
};

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

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

impl TryFrom<Option<&pii::SecretSerdeValue>> for WorldpayConnectorMetadataObject {
    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.cloned())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "metadata",
            })?;
        Ok(metadata)
    }
}

fn fetch_payment_instrument(
    payment_method: PaymentMethodData,
    billing_address: Option<&address::Address>,
    mandate_ids: Option<MandateIds>,
) -> CustomResult<PaymentInstrument, errors::ConnectorError> {
    match payment_method {
        PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment {
            raw_card_details: RawCardDetails {
                payment_type: PaymentType::Plain,
                expiry_date: ExpiryDate {
                    month: card.get_expiry_month_as_i8()?,
                    year: card.get_expiry_year_as_4_digit_i32()?,
                },
                card_number: card.card_number,
            },
            cvc: card.card_cvc,
            card_holder_name: billing_address.and_then(|address| address.get_optional_full_name()),
            billing_address: billing_address
                .and_then(|addr| addr.address.clone())
                .and_then(|address| {
                    match (address.line1, address.city, address.zip, address.country) {
                        (Some(address1), Some(city), Some(postal_code), Some(country_code)) => {
                            Some(BillingAddress {
                                address1,
                                address2: address.line2,
                                address3: address.line3,
                                city,
                                state: address.state,
                                postal_code,
                                country_code,
                            })
                        }
                        _ => None,
                    }
                }),
        })),
        PaymentMethodData::CardDetailsForNetworkTransactionId(raw_card_details) => {
            Ok(PaymentInstrument::RawCardForNTI(RawCardDetails {
                payment_type: PaymentType::Plain,
                expiry_date: ExpiryDate {
                    month: raw_card_details.get_expiry_month_as_i8()?,
                    year: raw_card_details.get_expiry_year_as_4_digit_i32()?,
                },
                card_number: raw_card_details.card_number,
            }))
        }
        PaymentMethodData::MandatePayment => mandate_ids
            .and_then(|mandate_ids| {
                mandate_ids
                    .mandate_reference_id
                    .and_then(|mandate_id| match mandate_id {
                        MandateReferenceId::ConnectorMandateId(connector_mandate_id) => {
                            connector_mandate_id.get_connector_mandate_id().map(|href| {
                                PaymentInstrument::CardToken(CardToken {
                                    payment_type: PaymentType::Token,
                                    href,
                                    cvc: None,
                                })
                            })
                        }
                        _ => None,
                    })
            })
            .ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "connector_mandate_id",
                }
                .into(),
            ),
        PaymentMethodData::Wallet(wallet) => match wallet {
            WalletData::GooglePay(data) => Ok(PaymentInstrument::Googlepay(WalletPayment {
                payment_type: PaymentType::Encrypted,
                wallet_token: Secret::new(
                    data.tokenization_data
                        .get_encrypted_google_pay_token()
                        .change_context(errors::ConnectorError::MissingRequiredField {
                            field_name: "gpay wallet_token",
                        })?,
                ),
                ..WalletPayment::default()
            })),
            WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment {
                payment_type: PaymentType::Encrypted,
                wallet_token: data.get_applepay_decoded_payment_data()?,
                ..WalletPayment::default()
            })),
            WalletData::AliPayQr(_)
            | WalletData::AliPayRedirect(_)
            | WalletData::AliPayHkRedirect(_)
            | WalletData::AmazonPay(_)
            | WalletData::AmazonPayRedirect(_)
            | WalletData::Paysera(_)
            | WalletData::Skrill(_)
            | WalletData::MomoRedirect(_)
            | WalletData::KakaoPayRedirect(_)
            | WalletData::GoPayRedirect(_)
            | WalletData::BluecodeRedirect {}
            | WalletData::GcashRedirect(_)
            | WalletData::ApplePayRedirect(_)
            | WalletData::ApplePayThirdPartySdk(_)
            | WalletData::DanaRedirect {}
            | WalletData::GooglePayRedirect(_)
            | WalletData::GooglePayThirdPartySdk(_)
            | WalletData::MbWayRedirect(_)
            | WalletData::MobilePayRedirect(_)
            | WalletData::PaypalRedirect(_)
            | WalletData::PaypalSdk(_)
            | WalletData::Paze(_)
            | WalletData::SamsungPay(_)
            | WalletData::TwintRedirect {}
            | WalletData::VippsRedirect {}
            | WalletData::TouchNGoRedirect(_)
            | WalletData::WeChatPayRedirect(_)
            | WalletData::CashappQr(_)
            | WalletData::SwishQr(_)
            | WalletData::WeChatPayQr(_)
            | WalletData::Mifinity(_)
            | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("worldpay"),
            )
            .into()),
        },
        PaymentMethodData::PayLater(_)
        | PaymentMethodData::BankRedirect(_)
        | PaymentMethodData::BankDebit(_)
        | PaymentMethodData::BankTransfer(_)
        | PaymentMethodData::Crypto(_)
        | PaymentMethodData::Reward
        | PaymentMethodData::RealTimePayment(_)
        | PaymentMethodData::MobilePayment(_)
        | PaymentMethodData::Upi(_)
        | PaymentMethodData::Voucher(_)
        | PaymentMethodData::CardRedirect(_)
        | PaymentMethodData::GiftCard(_)
        | PaymentMethodData::OpenBanking(_)
        | PaymentMethodData::CardToken(_)
        | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("worldpay"),
        )
        .into()),
    }
}

impl TryFrom<(enums::PaymentMethod, Option<enums::PaymentMethodType>)> for PaymentMethod {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        src: (enums::PaymentMethod, Option<enums::PaymentMethodType>),
    ) -> Result<Self, Self::Error> {
        match (src.0, src.1) {
            (enums::PaymentMethod::Card, _) => Ok(Self::Card),
            (enums::PaymentMethod::Wallet, pmt) => {
                let pm = pmt.ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "payment_method_type",
                })?;
                match pm {
                    enums::PaymentMethodType::ApplePay => Ok(Self::ApplePay),
                    enums::PaymentMethodType::GooglePay => Ok(Self::GooglePay),
                    _ => Err(errors::ConnectorError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("worldpay"),
                    )
                    .into()),
                }
            }
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("worldpay"),
            )
            .into()),
        }
    }
}

// Trait to abstract common functionality between Authorize and SetupMandate
trait WorldpayPaymentsRequestData {
    fn get_return_url(&self) -> Result<String, error_stack::Report<errors::ConnectorError>>;
    fn get_auth_type(&self) -> &enums::AuthenticationType;
    fn get_browser_info(&self) -> Option<&BrowserInformation>;
    fn get_payment_method_data(&self) -> &PaymentMethodData;
    fn get_setup_future_usage(&self) -> Option<enums::FutureUsage>;
    fn get_off_session(&self) -> Option<bool>;
    fn get_mandate_id(&self) -> Option<MandateIds>;
    fn get_currency(&self) -> enums::Currency;
    fn get_optional_billing_address(&self) -> Option<&address::Address>;
    fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue>;
    fn get_payment_method(&self) -> enums::PaymentMethod;
    fn get_payment_method_type(&self) -> Option<enums::PaymentMethodType>;
    fn get_connector_request_reference_id(&self) -> String;
    fn get_is_mandate_payment(&self) -> bool;
    fn get_settlement_info(&self, _amount: i64) -> Option<AutoSettlement> {
        None
    }
}

impl WorldpayPaymentsRequestData
    for RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
{
    fn get_return_url(&self) -> Result<String, error_stack::Report<errors::ConnectorError>> {
        self.request.get_complete_authorize_url()
    }

    fn get_auth_type(&self) -> &enums::AuthenticationType {
        &self.auth_type
    }

    fn get_browser_info(&self) -> Option<&BrowserInformation> {
        self.request.browser_info.as_ref()
    }

    fn get_payment_method_data(&self) -> &PaymentMethodData {
        &self.request.payment_method_data
    }

    fn get_setup_future_usage(&self) -> Option<enums::FutureUsage> {
        self.request.setup_future_usage
    }

    fn get_off_session(&self) -> Option<bool> {
        self.request.off_session
    }

    fn get_mandate_id(&self) -> Option<MandateIds> {
        self.request.mandate_id.clone()
    }

    fn get_currency(&self) -> enums::Currency {
        self.request.currency
    }

    fn get_optional_billing_address(&self) -> Option<&address::Address> {
        self.get_optional_billing()
    }

    fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue> {
        self.connector_meta_data.as_ref()
    }

    fn get_payment_method(&self) -> enums::PaymentMethod {
        self.payment_method
    }

    fn get_payment_method_type(&self) -> Option<enums::PaymentMethodType> {
        self.request.payment_method_type
    }

    fn get_connector_request_reference_id(&self) -> String {
        self.connector_request_reference_id.clone()
    }

    fn get_is_mandate_payment(&self) -> bool {
        true
    }
}

impl WorldpayPaymentsRequestData
    for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    fn get_return_url(&self) -> Result<String, error_stack::Report<errors::ConnectorError>> {
        self.request.get_complete_authorize_url()
    }

    fn get_auth_type(&self) -> &enums::AuthenticationType {
        &self.auth_type
    }

    fn get_browser_info(&self) -> Option<&BrowserInformation> {
        self.request.browser_info.as_ref()
    }

    fn get_payment_method_data(&self) -> &PaymentMethodData {
        &self.request.payment_method_data
    }

    fn get_setup_future_usage(&self) -> Option<enums::FutureUsage> {
        self.request.setup_future_usage
    }

    fn get_off_session(&self) -> Option<bool> {
        self.request.off_session
    }

    fn get_mandate_id(&self) -> Option<MandateIds> {
        self.request.mandate_id.clone()
    }

    fn get_currency(&self) -> enums::Currency {
        self.request.currency
    }

    fn get_optional_billing_address(&self) -> Option<&address::Address> {
        self.get_optional_billing()
    }

    fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue> {
        self.connector_meta_data.as_ref()
    }

    fn get_payment_method(&self) -> enums::PaymentMethod {
        self.payment_method
    }

    fn get_payment_method_type(&self) -> Option<enums::PaymentMethodType> {
        self.request.payment_method_type
    }

    fn get_connector_request_reference_id(&self) -> String {
        self.connector_request_reference_id.clone()
    }

    fn get_is_mandate_payment(&self) -> bool {
        self.request.is_mandate_payment()
    }

    fn get_settlement_info(&self, amount: i64) -> Option<AutoSettlement> {
        match (self.request.capture_method.unwrap_or_default(), amount) {
            (_, 0) => None,
            (enums::CaptureMethod::Automatic, _)
            | (enums::CaptureMethod::SequentialAutomatic, _) => Some(AutoSettlement { auto: true }),
            (enums::CaptureMethod::Manual, _) | (enums::CaptureMethod::ManualMultiple, _) => {
                Some(AutoSettlement { auto: false })
            }
            _ => None,
        }
    }
}

// Dangling helper function to create ThreeDS request
fn create_three_ds_request<T: WorldpayPaymentsRequestData>(
    router_data: &T,
    is_mandate_payment: bool,
) -> Result<Option<ThreeDSRequest>, error_stack::Report<errors::ConnectorError>> {
    match (
        router_data.get_auth_type(),
        router_data.get_payment_method_data(),
    ) {
        // 3DS for NTI flow
        (_, PaymentMethodData::CardDetailsForNetworkTransactionId(_)) => Ok(None),
        // 3DS for regular payments
        (enums::AuthenticationType::ThreeDs, _) => {
            let browser_info = router_data.get_browser_info().ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "browser_info",
                },
            )?;

            let accept_header = browser_info
                .accept_header
                .clone()
                .get_required_value("accept_header")
                .change_context(errors::ConnectorError::MissingRequiredField {
                    field_name: "accept_header",
                })?;

            let user_agent_header = browser_info
                .user_agent
                .clone()
                .get_required_value("user_agent")
                .change_context(errors::ConnectorError::MissingRequiredField {
                    field_name: "user_agent",
                })?;

            Ok(Some(ThreeDSRequest {
                three_ds_type: THREE_DS_TYPE.to_string(),
                mode: THREE_DS_MODE.to_string(),
                device_data: ThreeDSRequestDeviceData {
                    accept_header,
                    user_agent_header,
                    browser_language: browser_info.language.clone(),
                    browser_screen_width: browser_info.screen_width,
                    browser_screen_height: browser_info.screen_height,
                    browser_color_depth: browser_info.color_depth.map(|depth| depth.to_string()),
                    time_zone: browser_info.time_zone.map(|tz| tz.to_string()),
                    browser_java_enabled: browser_info.java_enabled,
                    browser_javascript_enabled: browser_info.java_script_enabled,
                    channel: Some(ThreeDSRequestChannel::Browser),
                },
                challenge: ThreeDSRequestChallenge {
                    return_url: router_data.get_return_url()?,
                    preference: if is_mandate_payment {
                        Some(ThreeDsPreference::ChallengeMandated)
                    } else {
                        None
                    },
                },
            }))
        }
        // Non 3DS
        _ => Ok(None),
    }
}

// Dangling helper function to determine token and agreement settings
fn get_token_and_agreement(
    payment_method_data: &PaymentMethodData,
    setup_future_usage: Option<enums::FutureUsage>,
    off_session: Option<bool>,
    mandate_ids: Option<MandateIds>,
) -> (Option<TokenCreation>, Option<CustomerAgreement>) {
    match (payment_method_data, setup_future_usage, off_session) {
        // CIT
        (PaymentMethodData::Card(_), Some(enums::FutureUsage::OffSession), _) => (
            Some(TokenCreation {
                token_type: TokenCreationType::Worldpay,
            }),
            Some(CustomerAgreement {
                agreement_type: CustomerAgreementType::Subscription,
                stored_card_usage: Some(StoredCardUsageType::First),
                scheme_reference: None,
            }),
        ),
        // MIT
        (PaymentMethodData::Card(_), _, Some(true)) => (
            None,
            Some(CustomerAgreement {
                agreement_type: CustomerAgreementType::Subscription,
                stored_card_usage: Some(StoredCardUsageType::Subsequent),
                scheme_reference: None,
            }),
        ),
        // NTI with raw card data
        (PaymentMethodData::CardDetailsForNetworkTransactionId(_), _, _) => (
            None,
            mandate_ids.and_then(|mandate_ids| {
                mandate_ids
                    .mandate_reference_id
                    .and_then(|mandate_id| match mandate_id {
                        MandateReferenceId::NetworkMandateId(network_transaction_id) => {
                            Some(CustomerAgreement {
                                agreement_type: CustomerAgreementType::Unscheduled,
                                scheme_reference: Some(network_transaction_id.into()),
                                stored_card_usage: None,
                            })
                        }
                        _ => None,
                    })
            }),
        ),
        _ => (None, None),
    }
}

// Implementation for WorldpayPaymentsRequest using abstracted request
impl<T: WorldpayPaymentsRequestData> TryFrom<(&WorldpayRouterData<&T>, &Secret<String>)>
    for WorldpayPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(req: (&WorldpayRouterData<&T>, &Secret<String>)) -> Result<Self, Self::Error> {
        let (item, entity_id) = req;
        let worldpay_connector_metadata_object: WorldpayConnectorMetadataObject =
            WorldpayConnectorMetadataObject::try_from(item.router_data.get_connector_meta_data())?;

        let merchant_name = worldpay_connector_metadata_object.merchant_name.ok_or(
            errors::ConnectorError::InvalidConnectorConfig {
                config: "metadata.merchant_name",
            },
        )?;

        let is_mandate_payment = item.router_data.get_is_mandate_payment();
        let three_ds = create_three_ds_request(item.router_data, is_mandate_payment)?;

        let (token_creation, customer_agreement) = get_token_and_agreement(
            item.router_data.get_payment_method_data(),
            item.router_data.get_setup_future_usage(),
            item.router_data.get_off_session(),
            item.router_data.get_mandate_id(),
        );

        Ok(Self {
            instruction: Instruction {
                settlement: item.router_data.get_settlement_info(item.amount),
                method: PaymentMethod::try_from((
                    item.router_data.get_payment_method(),
                    item.router_data.get_payment_method_type(),
                ))?,
                payment_instrument: fetch_payment_instrument(
                    item.router_data.get_payment_method_data().clone(),
                    item.router_data.get_optional_billing_address(),
                    item.router_data.get_mandate_id(),
                )?,
                narrative: InstructionNarrative {
                    line1: merchant_name.expose(),
                },
                value: PaymentValue {
                    amount: item.amount,
                    currency: item.router_data.get_currency(),
                },
                debt_repayment: None,
                three_ds,
                token_creation,
                customer_agreement,
            },
            merchant: Merchant {
                entity: entity_id.clone(),
                ..Default::default()
            },
            transaction_reference: item.router_data.get_connector_request_reference_id(),
            customer: None,
        })
    }
}

pub struct WorldpayAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) entity_id: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for WorldpayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            // TODO: Remove this later, kept purely for backwards compatibility
            ConnectorAuthType::BodyKey { api_key, key1 } => {
                let auth_key = format!("{}:{}", key1.peek(), api_key.peek());
                let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key));
                Ok(Self {
                    api_key: Secret::new(auth_header),
                    entity_id: Secret::new("default".to_string()),
                })
            }
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => {
                let auth_key = format!("{}:{}", key1.peek(), api_key.peek());
                let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key));
                Ok(Self {
                    api_key: Secret::new(auth_header),
                    entity_id: api_secret.clone(),
                })
            }
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
        }
    }
}

impl From<PaymentOutcome> for enums::AttemptStatus {
    fn from(item: PaymentOutcome) -> Self {
        match item {
            PaymentOutcome::Authorized => Self::Authorized,
            PaymentOutcome::SentForSettlement => Self::Charged,
            PaymentOutcome::ThreeDsDeviceDataRequired => Self::DeviceDataCollectionPending,
            PaymentOutcome::ThreeDsAuthenticationFailed => Self::AuthenticationFailed,
            PaymentOutcome::ThreeDsChallenged => Self::AuthenticationPending,
            PaymentOutcome::SentForCancellation => Self::VoidInitiated,
            PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => {
                Self::AutoRefunded
            }
            PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure,
            PaymentOutcome::ThreeDsUnavailable => Self::AuthenticationFailed,
        }
    }
}

impl From<PaymentOutcome> for enums::RefundStatus {
    fn from(item: PaymentOutcome) -> Self {
        match item {
            PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => Self::Success,
            PaymentOutcome::Refused
            | PaymentOutcome::FraudHighRisk
            | PaymentOutcome::Authorized
            | PaymentOutcome::SentForSettlement
            | PaymentOutcome::ThreeDsDeviceDataRequired
            | PaymentOutcome::ThreeDsAuthenticationFailed
            | PaymentOutcome::ThreeDsChallenged
            | PaymentOutcome::SentForCancellation
            | PaymentOutcome::ThreeDsUnavailable => Self::Failure,
        }
    }
}

impl From<&EventType> for enums::AttemptStatus {
    fn from(value: &EventType) -> Self {
        match value {
            EventType::SentForAuthorization => Self::Authorizing,
            EventType::SentForSettlement => Self::Charged,
            EventType::Settled => Self::Charged,
            EventType::Authorized => Self::Authorized,
            EventType::Refused
            | EventType::SettlementFailed
            | EventType::Expired
            | EventType::Cancelled
            | EventType::Error => Self::Failure,
            EventType::SentForRefund
            | EventType::RefundFailed
            | EventType::Refunded
            | EventType::Unknown => Self::Pending,
        }
    }
}

impl From<EventType> for enums::RefundStatus {
    fn from(value: EventType) -> Self {
        match value {
            EventType::Refunded | EventType::SentForRefund => Self::Success,
            EventType::RefundFailed => Self::Failure,
            EventType::Authorized
            | EventType::Cancelled
            | EventType::Settled
            | EventType::Refused
            | EventType::Error
            | EventType::SentForSettlement
            | EventType::SentForAuthorization
            | EventType::SettlementFailed
            | EventType::Expired
            | EventType::Unknown => Self::Pending,
        }
    }
}

impl<F, T>
    ForeignTryFrom<(
        ResponseRouterData<F, WorldpayPaymentsResponse, T, PaymentsResponseData>,
        Option<String>,
        i64,
    )> for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        item: (
            ResponseRouterData<F, WorldpayPaymentsResponse, T, PaymentsResponseData>,
            Option<String>,
            i64,
        ),
    ) -> Result<Self, Self::Error> {
        let (router_data, optional_correlation_id, amount) = item;
        let (description, redirection_data, mandate_reference, network_txn_id, error) = router_data
            .response
            .other_fields
            .as_ref()
            .map(|other_fields| match other_fields {
                WorldpayPaymentResponseFields::AuthorizedResponse(res) => (
                    res.description.clone(),
                    None,
                    res.token.as_ref().map(|mandate_token| MandateReference {
                        connector_mandate_id: Some(mandate_token.href.clone().expose()),
                        payment_method_id: Some(mandate_token.token_id.clone()),
                        mandate_metadata: None,
                        connector_mandate_request_reference_id: None,
                    }),
                    res.scheme_reference.clone(),
                    None,
                ),
                WorldpayPaymentResponseFields::DDCResponse(res) => (
                    None,
                    Some(RedirectForm::WorldpayDDCForm {
                        endpoint: res.device_data_collection.url.clone(),
                        method: common_utils::request::Method::Post,
                        collection_id: Some("SessionId".to_string()),
                        form_fields: HashMap::from([
                            (
                                "Bin".to_string(),
                                res.device_data_collection.bin.clone().expose(),
                            ),
                            (
                                "JWT".to_string(),
                                res.device_data_collection.jwt.clone().expose(),
                            ),
                        ]),
                    }),
                    None,
                    None,
                    None,
                ),
                WorldpayPaymentResponseFields::ThreeDsChallenged(res) => (
                    None,
                    Some(RedirectForm::Form {
                        endpoint: res.challenge.url.to_string(),
                        method: common_utils::request::Method::Post,
                        form_fields: HashMap::from([(
                            "JWT".to_string(),
                            res.challenge.jwt.clone().expose(),
                        )]),
                    }),
                    None,
                    None,
                    None,
                ),
                WorldpayPaymentResponseFields::RefusedResponse(res) => (
                    None,
                    None,
                    None,
                    None,
                    Some((
                        res.refusal_code.clone(),
                        res.refusal_description.clone(),
                        res.advice
                            .as_ref()
                            .and_then(|advice_code| advice_code.code.clone()),
                    )),
                ),
                WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None, None),
            })
            .unwrap_or((None, None, None, None, None));
        let worldpay_status = router_data.response.outcome.clone();
        let optional_error_message = match worldpay_status {
            PaymentOutcome::ThreeDsAuthenticationFailed => {
                Some("3DS authentication failed from issuer".to_string())
            }
            PaymentOutcome::ThreeDsUnavailable => {
                Some("3DS authentication unavailable from issuer".to_string())
            }
            PaymentOutcome::FraudHighRisk => Some("Transaction marked as high risk".to_string()),
            _ => None,
        };
        let status = if amount == 0 && worldpay_status == PaymentOutcome::Authorized {
            enums::AttemptStatus::Charged
        } else {
            enums::AttemptStatus::from(worldpay_status.clone())
        };
        let response = match (optional_error_message, error) {
            (None, None) => Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::foreign_try_from((
                    router_data.response,
                    optional_correlation_id.clone(),
                ))?,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(mandate_reference),
                connector_metadata: None,
                network_txn_id: network_txn_id.map(|id| id.expose()),
                connector_response_reference_id: optional_correlation_id.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            (Some(reason), _) => Err(ErrorResponse {
                code: worldpay_status.to_string(),
                message: reason.clone(),
                reason: Some(reason),
                status_code: router_data.http_code,
                attempt_status: Some(status),
                connector_transaction_id: optional_correlation_id,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            (_, Some((code, message, advice_code))) => Err(ErrorResponse {
                code: code.clone(),
                message: message.clone(),
                reason: Some(message.clone()),
                status_code: router_data.http_code,
                attempt_status: Some(status),
                connector_transaction_id: optional_correlation_id,
                network_advice_code: advice_code,
                // Access Worldpay returns a raw response code in the refusalCode field (if enabled) containing the unmodified response code received either directly from the card scheme for Worldpay-acquired transactions, or from third party acquirers.
                // You can use raw response codes to inform your retry logic. A rawCode is only returned if specifically requested.
                network_decline_code: Some(code),
                network_error_message: Some(message),
                connector_metadata: None,
            }),
        };
        Ok(Self {
            status,
            description,
            response,
            ..router_data.data
        })
    }
}

impl TryFrom<(&types::PaymentsCaptureRouterData, MinorUnit)> for WorldpayPartialRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(req: (&types::PaymentsCaptureRouterData, MinorUnit)) -> Result<Self, Self::Error> {
        let (item, amount) = req;
        Ok(Self {
            reference: item.payment_id.clone().replace("_", "-"),
            value: PaymentValue {
                amount: amount.get_amount_as_i64(),
                currency: item.request.currency,
            },
        })
    }
}

impl<F> TryFrom<(&types::RefundsRouterData<F>, MinorUnit)> for WorldpayPartialRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(req: (&types::RefundsRouterData<F>, MinorUnit)) -> Result<Self, Self::Error> {
        let (item, amount) = req;
        Ok(Self {
            reference: item.request.refund_id.clone().replace("_", "-"),
            value: PaymentValue {
                amount: amount.get_amount_as_i64(),
                currency: item.request.currency,
            },
        })
    }
}

impl TryFrom<WorldpayWebhookEventType> for WorldpayEventResponse {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(event: WorldpayWebhookEventType) -> Result<Self, Self::Error> {
        Ok(Self {
            last_event: event.event_details.event_type,
            links: None,
        })
    }
}

impl ForeignTryFrom<(WorldpayPaymentsResponse, Option<String>)> for ResponseIdStr {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        item: (WorldpayPaymentsResponse, Option<String>),
    ) -> Result<Self, Self::Error> {
        get_resource_id(item.0, item.1, |id| Self { id })
    }
}

impl ForeignTryFrom<(WorldpayPaymentsResponse, Option<String>)> for ResponseId {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        item: (WorldpayPaymentsResponse, Option<String>),
    ) -> Result<Self, Self::Error> {
        get_resource_id(item.0, item.1, Self::ConnectorTransactionId)
    }
}

impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for WorldpayCompleteAuthorizationRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
        let params = item
            .request
            .redirect_response
            .as_ref()
            .and_then(|redirect_response| redirect_response.params.as_ref())
            .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?;

        let parsed_request = serde_urlencoded::from_str::<Self>(params.peek())
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        match item.status {
            enums::AttemptStatus::DeviceDataCollectionPending => Ok(parsed_request),
            enums::AttemptStatus::AuthenticationPending => {
                if parsed_request.collection_reference.is_some() {
                    return Err(errors::ConnectorError::InvalidDataFormat {
                        field_name:
                            "collection_reference not allowed in AuthenticationPending state",
                    }
                    .into());
                }
                Ok(parsed_request)
            }
            _ => Err(
                errors::ConnectorError::RequestEncodingFailedWithReason(format!(
                    "Invalid payment status for complete authorize: {:?}",
                    item.status
                ))
                .into(),
            ),
        }
    }
}


impl FilteredPaymentMethodsEnabled {
    fn get_required_fields(
        self,
        input: RequiredFieldsInput,
    ) -> RequiredFieldsForEnabledPaymentMethodTypes {
        let required_fields_config = input.required_fields_config;

        let required_fields_info = self
            .0
            .into_iter()
            .map(|payment_methods_enabled| {
                let required_fields =
                    required_fields_config.get_required_fields(&payment_methods_enabled);

                let required_fields = required_fields
                    .map(|required_fields| {
                        let common_required_fields = required_fields
                            .common
                            .iter()
                            .flatten()
                            .map(ToOwned::to_owned);

                        // Collect mandate required fields because this is for zero auth mandates only
                        let mandate_required_fields = required_fields
                            .mandate
                            .iter()
                            .flatten()
                            .map(ToOwned::to_owned);

                        // Combine both common and mandate required fields
                        common_required_fields
                            .chain(mandate_required_fields)
                            .collect::<Vec<_>>()
                    })
                    .unwrap_or_default();

                RequiredFieldsForEnabledPaymentMethod {
                    required_fields,
                    payment_method_type: payment_methods_enabled.payment_method,
                    payment_method_subtype: payment_methods_enabled
                        .payment_methods_enabled
                        .payment_method_subtype,
                }
            })
            .collect();

        RequiredFieldsForEnabledPaymentMethodTypes(required_fields_info)
    }
}


pub fn get_hashed(&self) -> UserResult<Vec<Secret<String>>> {
        self.0
            .iter()
            .cloned()
            .map(password::generate_password_hash)
            .collect::<Result<Vec<_>, _>>()
    }


pub fn qr_code_next_steps_check(
    payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::QrCodeInformation>> {
    let qr_code_steps: Option<Result<api_models::payments::QrCodeInformation, _>> = payment_attempt
        .connector_metadata
        .map(|metadata| metadata.parse_value("QrCodeInformation"));

    let qr_code_instructions = qr_code_steps.transpose().ok().flatten();
    Ok(qr_code_instructions)
}


File: crates/events/src/actix.rs

use router_env::tracing_actix_web::RequestId;

use crate::EventInfo;

impl EventInfo for RequestId {
    type Data = String;

    fn data(&self) -> error_stack::Result<String, crate::EventsError> {
        Ok(self.as_hyphenated().to_string())
    }

    fn key(&self) -> String {
        "request_id".to_string()
    }
}


pub struct BankOfAmericaRefundResponse {
    id: String,
    status: BankofamericaRefundStatus,
    error_information: Option<BankOfAmericaErrorInformation>,
}


pub struct AffirmRsyncResponse {
    pub amount: MinorUnit,
    pub amount_refunded: MinorUnit,
    pub authorization_expiration: String,
    pub checkout_id: String,
    pub created: String,
    pub currency: Currency,
    pub events: Vec<TransactionEvent>,
    pub id: String,
    pub order_id: String,
    pub status: AffirmRefundStatus,
    pub refund_expires: Option<String>,
    pub remaining_capturable_amount: Option<MinorUnit>,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub merchant_transaction_id: Option<String>,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
}


impl Refund {
    pub async fn update(self, conn: &PgPooledConn, refund: RefundUpdate) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::refund_id
                .eq(self.refund_id.to_owned())
                .and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
            RefundUpdateInternal::from(refund),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    // This is required to be changed for KV.
    pub async fn find_by_merchant_id_refund_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        refund_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::refund_id.eq(refund_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_refund_id_connector(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_refund_id: &str,
        connector: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_refund_id.eq(connector_refund_id.to_owned()))
                .and(dsl::connector.eq(connector.to_owned())),
        )
        .await
    }

    pub async fn find_by_internal_reference_id_merchant_id(
        conn: &PgPooledConn,
        internal_reference_id: &str,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::internal_reference_id.eq(internal_reference_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_transaction_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_transaction_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn find_by_payment_id_merchant_id(
        conn: &PgPooledConn,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_id.eq(payment_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }
}


File: crates/router_env/tests/logger.rs

#![allow(clippy::unwrap_used)]

mod test_module;

use ::config::ConfigError;
use router_env::TelemetryGuard;

use self::test_module::fn_with_colon;

fn logger() -> error_stack::Result<&'static TelemetryGuard, ConfigError> {
    use std::sync::OnceLock;

    static INSTANCE: OnceLock<TelemetryGuard> = OnceLock::new();
    Ok(INSTANCE.get_or_init(|| {
        let config = router_env::Config::new().unwrap();

        router_env::setup(&config.log, "router_env_test", []).unwrap()
    }))
}

#[tokio::test]
async fn basic() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    logger()?;

    fn_with_colon(13).await;

    Ok(())
}


pub struct SelfLink {
    #[serde(rename = "self")]
    pub self_link: SelfLinkInner,
}


File: crates/api_models/src/user_role/role.rs
 Public structs: 15

use common_enums::{
    EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource, RoleScope,
};

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateRoleRequest {
    pub role_name: String,
    pub groups: Vec<PermissionGroup>,
    pub role_scope: RoleScope,
    pub entity_type: Option<EntityType>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateRoleV2Request {
    pub role_name: String,
    pub role_scope: RoleScope,
    pub entity_type: Option<EntityType>,
    pub parent_groups: Vec<ParentGroupInfoRequest>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct UpdateRoleRequest {
    pub groups: Option<Vec<PermissionGroup>>,
    pub role_name: Option<String>,
}

#[derive(Debug, serde::Serialize)]
pub struct RoleInfoWithGroupsResponse {
    pub role_id: String,
    pub groups: Vec<PermissionGroup>,
    pub role_name: String,
    pub role_scope: RoleScope,
    pub entity_type: EntityType,
}

#[derive(Debug, serde::Serialize)]
pub struct RoleInfoWithParents {
    pub role_id: String,
    pub parent_groups: Vec<ParentGroupInfo>,
    pub role_name: String,
    pub role_scope: RoleScope,
}

#[derive(Debug, serde::Serialize)]
pub struct ParentGroupInfo {
    pub name: ParentGroup,
    pub description: String,
    pub scopes: Vec<PermissionScope>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ParentGroupInfoRequest {
    pub name: ParentGroup,
    pub scopes: Vec<PermissionScope>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListRolesQueryParams {
    pub entity_type: Option<EntityType>,
    pub groups: Option<bool>,
}

#[derive(Debug, serde::Serialize)]
pub struct RoleInfoResponseNew {
    pub role_id: String,
    pub role_name: String,
    pub entity_type: EntityType,
    pub groups: Vec<PermissionGroup>,
    pub scope: RoleScope,
}

#[derive(Debug, serde::Serialize)]
pub struct RoleInfoResponseWithParentsGroup {
    pub role_id: String,
    pub role_name: String,
    pub entity_type: EntityType,
    pub parent_groups: Vec<ParentGroupInfo>,
    pub role_scope: RoleScope,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct GetRoleRequest {
    pub role_id: String,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListRolesAtEntityLevelRequest {
    pub entity_type: EntityType,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct GetParentGroupsInfoQueryParams {
    pub entity_type: Option<EntityType>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum RoleCheckType {
    Invite,
    Update,
}

#[derive(Debug, serde::Serialize, Clone)]
pub struct MinimalRoleInfo {
    pub role_id: String,
    pub role_name: String,
}

#[derive(Debug, serde::Serialize)]
pub struct GroupsAndResources {
    pub groups: Vec<PermissionGroup>,
    pub resources: Vec<Resource>,
}

#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum ListRolesResponse {
    WithGroups(Vec<RoleInfoResponseNew>),
    WithParentGroups(Vec<RoleInfoResponseWithParentsGroup>),
}


pub fn get_payment_billing(&self) -> Option<&Address> {
        self.billing.as_ref()
    }


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

use masking::Secret;
use router::types::{self, domain, storage::enums};
use test_utils::connector_auth;

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

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

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

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

static CONNECTOR: HelcimTest = HelcimTest {};

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: domain::PaymentMethodData::Card(domain::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: domain::PaymentMethodData::Card(domain::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: domain::PaymentMethodData::Card(domain::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 api::PaymentCapture for Bitpay {}


pub fn arbitrary_comparison(input: &str) -> ParseResult<&str, ast::Comparison> {
    error::context(
        "condition",
        combinator::map(
            sequence::tuple((
                skip_ws(string_str),
                skip_ws(comparison_type),
                skip_ws(string_str),
            )),
            |tup: (String, ast::ComparisonType, String)| ast::Comparison {
                lhs: "metadata".to_string(),
                comparison: tup.1,
                value: ast::ValueType::MetadataVariant(ast::MetadataValue {
                    key: tup.0,
                    value: tup.2,
                }),
                metadata: std::collections::HashMap::new(),
            },
        ),
    )(input)
}


/// Get the encrypted Google Pay payment data, returning an error if it does not exist
    pub fn get_encrypted_google_pay_payment_data_mandatory(
        &self,
    ) -> Result<&GpayEcryptedTokenizationData, errors::ValidationError> {
        match self {
            Self::Encrypted(encrypted_data) => Ok(encrypted_data),
            Self::Decrypted(_) => Err(errors::ValidationError::InvalidValue {
                message: "Encrypted Google Pay payment data is mandatory".to_string(),
            }
            .into()),
        }
    }


pub struct GenericTokenizationRequest {
    /// Customer ID for which the tokenization is requested
    #[schema(value_type = String, example = "12345_cus_01926c58bc6e77c09e809964e72af8c8")]
    pub customer_id: id_type::GlobalCustomerId,
    /// Request for tokenization which contains the data to be tokenized
    #[schema(value_type = Object,example = json!({ "city": "NY", "unit": "245" }))]
    pub token_request: masking::Secret<serde_json::Value>,
}


pub struct ConnectorTokenDetails {
    /// The unique identifier of the connector account through which the token was generated
    #[schema(value_type = String, example = "mca_")]
    pub connector_id: id_type::MerchantConnectorAccountId,

    #[schema(value_type = TokenizationType)]
    pub token_type: common_enums::TokenizationType,

    /// The status of connector token if it is active or inactive
    #[schema(value_type = ConnectorTokenStatus)]
    pub status: common_enums::ConnectorTokenStatus,

    /// The reference id of the connector token
    /// This is the reference that was passed to connector when creating the token
    pub connector_token_request_reference_id: Option<String>,

    pub original_payment_authorized_amount: Option<MinorUnit>,

    /// The currency of the original payment authorized amount
    #[schema(value_type = Currency)]
    pub original_payment_authorized_currency: Option<common_enums::Currency>,

    /// Metadata associated with the connector token
    pub metadata: Option<pii::SecretSerdeValue>,

    /// The value of the connector token. This token can be used to make merchant initiated payments ( MIT ), directly with the connector.
    pub token: masking::Secret<String>,
}


pub async fn list_profile(
    state: SessionState,
    merchant_id: id_type::MerchantId,
    profile_id_list: Option<Vec<id_type::ProfileId>>,
) -> RouterResponse<Vec<api_models::admin::ProfileResponse>> {
    let db = state.store.as_ref();
    let key_store = db
        .get_merchant_key_store_by_merchant_id(
            &(&state).into(),
            &merchant_id,
            &db.get_master_key().to_vec().into(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
    let profiles = db
        .list_profile_by_merchant_id(&(&state).into(), &key_store, &merchant_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
        .clone();
    let profiles = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, profiles);
    let mut business_profiles = Vec::new();
    for profile in profiles {
        let business_profile = api_models::admin::ProfileResponse::foreign_try_from(profile)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse business profile details")?;
        business_profiles.push(business_profile);
    }

    Ok(service_api::ApplicationResponse::Json(business_profiles))
}


pub fn new(txn_id: masking::Secret<String>) -> Self {
        Self(txn_id)
    }