// File: crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs
// Module: hyperswitch_connectors
// Public structs: 18

use common_enums::enums;
use common_utils::types::MinorUnit;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{AccessToken, ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefreshTokenRouterData, RefundsResponseRouterData, ResponseRouterData},
    utils::{CardData as _, PaymentsAuthorizeRequestData, RouterData as OtherRouterData},
};

const CLIENT_CREDENTIALS: &str = "client_credentials";

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

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

pub mod auth_headers {
    pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
    pub const API_VERSION: &str = "Api-Version";
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsRequest {
    idempotency_key: String,
    amount: Amount,
    payment_method: PaymentMethod,
    automatic_capture: bool,
}
#[derive(Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    currency: enums::Currency,
    amount: MinorUnit,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum PaymentMethod {
    Card(PaymentMethodCard),
    PaymentMethodId(PaymentMethodId),
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentMethodCard {
    payment_method_source: PaymentMethodSource,
    card: MonerisCard,
    store_payment_method: StorePaymentMethod,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentMethodId {
    payment_method_source: PaymentMethodSource,
    payment_method_id: Secret<String>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentMethodSource {
    Card,
    PaymentMethodId,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisCard {
    card_number: cards::CardNumber,
    expiry_month: Secret<i64>,
    expiry_year: Secret<i64>,
    card_security_code: Secret<String>,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StorePaymentMethod {
    DoNotStore,
    CardholderInitiated,
    MerchantInitiated,
}

impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &MonerisRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if item.router_data.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Card 3DS".to_string(),
                connector: "Moneris",
            })?
        };
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(ref req_card) => {
                let idempotency_key = uuid::Uuid::new_v4().to_string();
                let amount = Amount {
                    currency: item.router_data.request.currency,
                    amount: item.amount,
                };
                let payment_method = PaymentMethod::Card(PaymentMethodCard {
                    payment_method_source: PaymentMethodSource::Card,
                    card: MonerisCard {
                        card_number: req_card.card_number.clone(),
                        expiry_month: Secret::new(
                            req_card
                                .card_exp_month
                                .peek()
                                .parse::<i64>()
                                .change_context(errors::ConnectorError::ParsingFailed)?,
                        ),
                        expiry_year: Secret::new(
                            req_card
                                .get_expiry_year_4_digit()
                                .peek()
                                .parse::<i64>()
                                .change_context(errors::ConnectorError::ParsingFailed)?,
                        ),
                        card_security_code: req_card.card_cvc.clone(),
                    },
                    store_payment_method: if item
                        .router_data
                        .request
                        .is_customer_initiated_mandate_payment()
                    {
                        StorePaymentMethod::CardholderInitiated
                    } else {
                        StorePaymentMethod::DoNotStore
                    },
                });
                let automatic_capture = item.router_data.request.is_auto_capture()?;

                Ok(Self {
                    idempotency_key,
                    amount,
                    payment_method,
                    automatic_capture,
                })
            }
            PaymentMethodData::MandatePayment => {
                let idempotency_key = uuid::Uuid::new_v4().to_string();
                let amount = Amount {
                    currency: item.router_data.request.currency,
                    amount: item.amount,
                };
                let automatic_capture = item.router_data.request.is_auto_capture()?;
                let payment_method = PaymentMethod::PaymentMethodId(PaymentMethodId {
                    payment_method_source: PaymentMethodSource::PaymentMethodId,
                    payment_method_id: item
                        .router_data
                        .request
                        .connector_mandate_id()
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "connector_mandate_id",
                        })?
                        .into(),
                });
                Ok(Self {
                    idempotency_key,
                    amount,
                    payment_method,
                    automatic_capture,
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

pub struct MonerisAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
    pub(super) merchant_id: Secret<String>,
}

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

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

impl TryFrom<&RefreshTokenRouterData> for MonerisAuthRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefreshTokenRouterData) -> Result<Self, Self::Error> {
        let auth = MonerisAuthType::try_from(&item.connector_auth_type)?;
        Ok(Self {
            client_id: auth.client_id.clone(),
            client_secret: auth.client_secret.clone(),
            grant_type: CLIENT_CREDENTIALS.to_string(),
        })
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MonerisAuthResponse {
    access_token: Secret<String>,
    token_type: String,
    expires_in: String,
}

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

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MonerisPaymentStatus {
    Succeeded,
    #[default]
    Processing,
    Canceled,
    Declined,
    DeclinedRetry,
    Authorized,
}

impl From<MonerisPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: MonerisPaymentStatus) -> Self {
        match item {
            MonerisPaymentStatus::Succeeded => Self::Charged,
            MonerisPaymentStatus::Authorized => Self::Authorized,
            MonerisPaymentStatus::Canceled => Self::Voided,
            MonerisPaymentStatus::Declined | MonerisPaymentStatus::DeclinedRetry => Self::Failure,
            MonerisPaymentStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsResponse {
    payment_status: MonerisPaymentStatus,
    payment_id: String,
    payment_method: MonerisPaymentMethodData,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentMethodData {
    payment_method_id: Secret<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, MonerisPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, MonerisPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.payment_status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payment_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(Some(MandateReference {
                    connector_mandate_id: Some(
                        item.response
                            .payment_method
                            .payment_method_id
                            .peek()
                            .to_string(),
                    ),
                    payment_method_id: None,
                    mandate_metadata: None,
                    connector_mandate_request_reference_id: None,
                })),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.payment_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsCaptureRequest {
    amount: Amount,
    idempotency_key: String,
}

impl TryFrom<&MonerisRouterData<&PaymentsCaptureRouterData>> for MonerisPaymentsCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &MonerisRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let amount = Amount {
            currency: item.router_data.request.currency,
            amount: item.amount,
        };
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        Ok(Self {
            amount,
            idempotency_key,
        })
    }
}

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

impl TryFrom<&PaymentsCancelRouterData> for MonerisCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        let reason = item.request.cancellation_reason.clone();
        Ok(Self {
            idempotency_key,
            reason,
        })
    }
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MonerisRefundRequest {
    pub refund_amount: Amount,
    pub idempotency_key: String,
    pub reason: Option<String>,
    pub payment_id: String,
}

impl<F> TryFrom<&MonerisRouterData<&RefundsRouterData<F>>> for MonerisRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &MonerisRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let refund_amount = Amount {
            currency: item.router_data.request.currency,
            amount: item.amount,
        };
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        let reason = item.router_data.request.reason.clone();
        let payment_id = item.router_data.request.connector_transaction_id.clone();
        Ok(Self {
            refund_amount,
            idempotency_key,
            reason,
            payment_id,
        })
    }
}

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
    Succeeded,
    #[default]
    Processing,
    Declined,
    DeclinedRetry,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Declined | RefundStatus::DeclinedRetry => Self::Failure,
            RefundStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    refund_id: String,
    refund_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> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.refund_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.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> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.refund_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.refund_status),
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisErrorResponse {
    pub status: u16,
    pub category: String,
    pub title: String,
    pub errors: Option<Vec<MonerisError>>,
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisError {
    pub reason_code: String,
    pub parameter_name: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct MonerisAuthErrorResponse {
    pub error: String,
    pub error_description: Option<String>,
}


// Function: mk_card_value2
// File: crates/router/src/core/payment_methods/transformers.rs
// Module: router

pub fn mk_card_value2(
    card_security_code: Option<String>,
    card_fingerprint: Option<String>,
    external_id: Option<String>,
    customer_id: Option<id_type::CustomerId>,
    payment_method_id: Option<String>,
) -> CustomResult<String, errors::VaultError>


// Function: get_filters
// File: crates/analytics/src/payment_intents/core.rs
// Module: analytics

pub fn get_filters(
    pool: &AnalyticsProvider,
    req: GetPaymentIntentFiltersRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<PaymentIntentFiltersResponse>


// File: crates/router/tests/connectors/bamboraapac.rs
// Module: router

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

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

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

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

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

static CONNECTOR: BamboraapacTest = BamboraapacTest {};

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


// Function: calculate_smart_retry_time
// File: crates/router/src/workflows/revenue_recovery.rs
// Module: router

pub fn calculate_smart_retry_time(
    state: &SessionState,
    payment_intent: &PaymentIntent,
    token_with_retry_info: &PaymentProcessorTokenWithRetryInfo,
) -> Result<Option<time::PrimitiveDateTime>, errors::ProcessTrackerError>


// Function: trigger_refund_outgoing_webhook
// File: crates/router/src/utils.rs
// Module: router

pub fn trigger_refund_outgoing_webhook(
    state: &SessionState,
    merchant_account: &domain::MerchantAccount,
    refund: &diesel_models::Refund,
    profile_id: id_type::ProfileId,
    key_store: &domain::MerchantKeyStore,
) -> RouterResult<()>


// Function: create_customer
// File: crates/router/src/core/payment_methods/tokenize/card_executor.rs
// Module: router

pub fn create_customer(&self) -> RouterResult<api::CustomerDetails>


// Struct: DisputeMetricsBucketValue
// File: crates/api_models/src/analytics/disputes.rs
// Module: api_models
// Implementations: 0

pub struct DisputeMetricsBucketValue


// Implementation: impl Strength
// File: crates/hyperswitch_constraint_graph/src/types.rs
// Module: hyperswitch_constraint_graph
// Methods: 1 total (1 public)

impl Strength


// Implementation: impl api::PaymentSync for for Flexiti
// File: crates/hyperswitch_connectors/src/connectors/flexiti.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSync for for Flexiti


// Struct: AddVaultRequest
// File: crates/router/src/types/payment_methods.rs
// Module: router
// Implementations: 0

pub struct AddVaultRequest<D>


// Implementation: impl api::PayoutRecipientAccount for for Stripe
// File: crates/hyperswitch_connectors/src/connectors/stripe.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PayoutRecipientAccount for for Stripe


// Implementation: impl SubscriptionUpdate
// File: crates/diesel_models/src/subscription.rs
// Module: diesel_models
// Methods: 1 total (1 public)

impl SubscriptionUpdate


// Function: update
// File: crates/api_models/src/routing.rs
// Module: api_models

pub fn update(&mut self, new: Self)


// Implementation: impl State for for CardDetailsAssigned
// File: crates/router/src/core/payment_methods/tokenize/card_executor.rs
// Module: router
// Methods: 0 total (0 public)

impl State for for CardDetailsAssigned


// Implementation: impl api::ConnectorAccessToken for for Peachpayments
// File: crates/hyperswitch_connectors/src/connectors/peachpayments.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorAccessToken for for Peachpayments


// Function: list_merchants_for_user_in_org
// File: crates/router/src/core/user.rs
// Module: router

pub fn list_merchants_for_user_in_org(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::UserMerchantAccountResponse>>


// Function: get_entity
// File: crates/router/src/services/email/types.rs
// Module: router

pub fn get_entity(&self) -> Option<&Entity>


// Implementation: impl api::subscriptions::GetSubscriptionPlansFlow for for Chargebee
// File: crates/hyperswitch_connectors/src/connectors/chargebee.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::subscriptions::GetSubscriptionPlansFlow for for Chargebee


// Function: get_encryption_management_client
// File: crates/external_services/src/managers/encryption_management.rs
// Module: external_services

// Documentation: Retrieves the appropriate encryption client based on the configuration.

pub fn get_encryption_management_client(
        &self,
    ) -> CustomResult<Arc<dyn EncryptionManagementInterface>, EncryptionError>


// Implementation: impl Drop for for SecretBytesMut
// File: crates/masking/src/bytes.rs
// Module: masking
// Methods: 1 total (0 public)

impl Drop for for SecretBytesMut


// Struct: OpayoCard
// File: crates/hyperswitch_connectors/src/connectors/opayo/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OpayoCard


// Function: default_service_405
// File: crates/router/src/core/errors/error_handlers.rs
// Module: router

pub fn default_service_405<E>(req: ServiceRequest) -> Result<ServiceResponse, E>


// Struct: RazorpayError
// File: crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RazorpayError


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

impl api::Payment for for Cashtocode


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

pub struct PayeezyAuthType


// Function: get_account_reference_id_using_payment_merchant_connector_account_id
// File: crates/hyperswitch_domain_models/src/merchant_connector_account.rs
// Module: hyperswitch_domain_models

pub fn get_account_reference_id_using_payment_merchant_connector_account_id(
        &self,
        payment_merchant_connector_account_id: id_type::MerchantConnectorAccountId,
    ) -> Option<String>


// File: crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs
// Module: hyperswitch_connectors
// Public structs: 7

use common_utils::types::StringMinorUnit;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{ExternalVaultInsertFlow, ExternalVaultRetrieveFlow},
    router_request_types::VaultRequestData,
    router_response_types::VaultResponseData,
    types::VaultRouterData,
    vault::PaymentMethodVaultingData,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::types::ResponseRouterData;

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

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

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct TokenexInsertRequest {
    data: cards::CardNumber, //Currently only card number is tokenized. Data can be stringified and can be tokenized
}

impl<F> TryFrom<&VaultRouterData<F>> for TokenexInsertRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &VaultRouterData<F>) -> Result<Self, Self::Error> {
        match item.request.payment_method_vaulting_data.clone() {
            Some(PaymentMethodVaultingData::Card(req_card)) => Ok(Self {
                data: req_card.card_number.clone(),
            }),
            _ => Err(errors::ConnectorError::NotImplemented(
                "Payment method apart from card".to_string(),
            )
            .into()),
        }
    }
}
pub struct TokenexAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) tokenex_id: Secret<String>,
}

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

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TokenexInsertResponse {
    token: Option<String>,
    first_six: Option<String>,
    last_four: Option<String>,
    success: bool,
    error: String,
    message: Option<String>,
}
impl
    TryFrom<
        ResponseRouterData<
            ExternalVaultInsertFlow,
            TokenexInsertResponse,
            VaultRequestData,
            VaultResponseData,
        >,
    > for RouterData<ExternalVaultInsertFlow, VaultRequestData, VaultResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            ExternalVaultInsertFlow,
            TokenexInsertResponse,
            VaultRequestData,
            VaultResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let resp = item.response;
        match resp.success && resp.error.is_empty() {
            true => {
                let token = resp
                    .token
                    .clone()
                    .ok_or(errors::ConnectorError::ResponseDeserializationFailed)
                    .attach_printable("Token is missing in tokenex response")?;
                Ok(Self {
                    status: common_enums::AttemptStatus::Started,
                    response: Ok(VaultResponseData::ExternalVaultInsertResponse {
                        connector_vault_id: token.clone(),
                        //fingerprint is not provided by tokenex, using token as fingerprint
                        fingerprint_id: token.clone(),
                    }),
                    ..item.data
                })
            }
            false => {
                let (code, message) = resp.error.split_once(':').unwrap_or(("", ""));

                let response = Err(ErrorResponse {
                    code: code.to_string(),
                    message: message.to_string(),
                    reason: resp.message,
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_decline_code: None,
                    network_advice_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                });

                Ok(Self {
                    response,
                    ..item.data
                })
            }
        }
    }
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TokenexRetrieveRequest {
    token: Secret<String>, //Currently only card number is tokenized. Data can be stringified and can be tokenized
    cache_cvv: bool,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TokenexRetrieveResponse {
    value: Option<cards::CardNumber>,
    success: bool,
    error: String,
    message: Option<String>,
}

impl<F> TryFrom<&VaultRouterData<F>> for TokenexRetrieveRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &VaultRouterData<F>) -> Result<Self, Self::Error> {
        let connector_vault_id = item.request.connector_vault_id.as_ref().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "connector_vault_id",
            },
        )?;
        Ok(Self {
            token: Secret::new(connector_vault_id.clone()),
            cache_cvv: false, //since cvv is not stored at tokenex
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            ExternalVaultRetrieveFlow,
            TokenexRetrieveResponse,
            VaultRequestData,
            VaultResponseData,
        >,
    > for RouterData<ExternalVaultRetrieveFlow, VaultRequestData, VaultResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            ExternalVaultRetrieveFlow,
            TokenexRetrieveResponse,
            VaultRequestData,
            VaultResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let resp = item.response;

        match resp.success && resp.error.is_empty() {
            true => {
                let data = resp
                    .value
                    .clone()
                    .ok_or(errors::ConnectorError::ResponseDeserializationFailed)
                    .attach_printable("Card number is missing in tokenex response")?;
                Ok(Self {
                    status: common_enums::AttemptStatus::Started,
                    response: Ok(VaultResponseData::ExternalVaultRetrieveResponse {
                        vault_data: PaymentMethodVaultingData::CardNumber(data),
                    }),
                    ..item.data
                })
            }
            false => {
                let (code, message) = resp.error.split_once(':').unwrap_or(("", ""));

                let response = Err(ErrorResponse {
                    code: code.to_string(),
                    message: message.to_string(),
                    reason: resp.message,
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_decline_code: None,
                    network_advice_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                });

                Ok(Self {
                    response,
                    ..item.data
                })
            }
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct TokenexErrorResponse {
    pub error: String,
    pub message: String,
}


// Struct: UnifiedAuthenticationServiceCard
// File: crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct UnifiedAuthenticationServiceCard


// Function: server
// File: crates/router/src/analytics.rs
// Module: router

pub fn server(state: AppState) -> Scope


// Struct: CardToken
// File: crates/hyperswitch_connectors/src/connectors/getnet/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct CardToken


// File: crates/hyperswitch_connectors/src/connectors/globalpay/response.rs
// Module: hyperswitch_connectors
// Public structs: 9

use common_enums::Currency;
use common_utils::types::StringMinorUnit;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::utils::deserialize_optional_currency;

#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalpayPaymentsResponse {
    pub status: GlobalpayPaymentStatus,
    pub payment_method: Option<PaymentMethod>,
    pub id: String,
    pub amount: Option<StringMinorUnit>,
    #[serde(deserialize_with = "deserialize_optional_currency")]
    pub currency: Option<Currency>,
    pub reference: Option<String>,
}

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

#[derive(Debug, Deserialize, Serialize)]
pub struct GlobalpayRefreshTokenErrorResponse {
    pub error_code: String,
    pub detailed_error_description: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentMethod {
    pub apm: Option<Apm>,
    pub card: Option<Card>,
    pub id: Option<Secret<String>>,
    pub message: Option<String>,
    pub result: Option<String>,
}

/// Data associated with the response of an APM transaction.
#[derive(Debug, Serialize, Deserialize)]
pub struct Apm {
    #[serde(alias = "provider_redirect_url")]
    pub redirect_url: Option<String>,
}

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

/// Indicates where a transaction is in its lifecycle.
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GlobalpayPaymentStatus {
    /// A Transaction has been successfully authorized and captured. The funding
    /// process will commence once the transaction remains in this status.
    Captured,
    /// A Transaction where the payment method provider declined the transfer of
    /// funds between the payer and the merchant.
    Declined,
    /// A Transaction where the funds have transferred between payer and merchant as
    /// expected.
    Funded,
    /// A Transaction has been successfully initiated. An update on its status is
    /// expected via a separate asynchronous notification to a webhook.
    Initiated,
    /// A Transaction has been sent to the payment method provider and are waiting
    /// for a result.
    Pending,
    /// A Transaction has been approved but a capture request is required to
    /// commence the movement of funds.
    Preauthorized,
    /// A Transaction where the funds were expected to transfer between payer and
    /// merchant but the transfer was rejected during the funding process. This rarely happens
    /// but when it does it is usually addressed by Global Payments operations.
    Rejected,
    /// A Transaction that had a status of PENDING, PREAUTHORIZED or CAPTURED has
    /// subsequently been reversed which voids/cancels a transaction before it is funded.
    Reversed,
}

#[derive(Debug, Deserialize)]
pub struct GlobalpayWebhookObjectId {
    pub id: String,
}

#[derive(Debug, Deserialize)]
pub struct GlobalpayWebhookObjectEventType {
    pub status: GlobalpayWebhookStatus,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum GlobalpayWebhookStatus {
    Declined,
    Captured,
    #[serde(other)]
    Unknown,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalpayPaymentMethodsResponse {
    #[serde(rename = "id")]
    pub payment_method_token_id: Option<Secret<String>>,
    pub card: Card,
}


// File: crates/hyperswitch_interfaces/src/authentication.rs
// Module: hyperswitch_interfaces
// Public structs: 1

//! Authentication interface

/// struct ExternalAuthenticationPayload
#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)]
pub struct ExternalAuthenticationPayload {
    /// trans_status
    pub trans_status: common_enums::TransactionStatus,
    /// authentication_value
    pub authentication_value: Option<masking::Secret<String>>,
    /// eci
    pub eci: Option<String>,
    /// Indicates whether the challenge was canceled by the user or system.
    pub challenge_cancel: Option<String>,
    /// Reason for the challenge code, if applicable.
    pub challenge_code_reason: Option<String>,
}


// Struct: SantanderDebtor
// File: crates/hyperswitch_connectors/src/connectors/santander/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct SantanderDebtor


// Function: is_ephemeral_auth
// File: crates/router/src/services/authentication.rs
// Module: router

pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
    headers: &HeaderMap,
    api_auth: ApiKeyAuth,
) -> RouterResult<Box<dyn AuthenticateAndFetch<AuthenticationData, A>>>


// Struct: GsmCreateRequest
// File: crates/api_models/src/gsm.rs
// Module: api_models
// Implementations: 0

pub struct GsmCreateRequest


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

pub struct CustomerPaymentMethodsListResponse


// Function: filter_based_on_profile_and_connector_type
// File: crates/hyperswitch_domain_models/src/merchant_connector_account.rs
// Module: hyperswitch_domain_models

pub fn filter_based_on_profile_and_connector_type(
            self,
            profile_id: &id_type::ProfileId,
            connector_type: common_enums::ConnectorType,
        ) -> Self


// Function: get_key
// File: crates/euclid/src/dssa/types.rs
// Module: euclid

pub fn get_key(&self) -> Option<dir::DirKey>


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

impl api::Refund for for Gigadat


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

pub struct BarclaycardConsumerAuthInformationValidateRequest


// Function: get_connector_customer_id
// File: crates/hyperswitch_domain_models/src/payments.rs
// Module: hyperswitch_domain_models

pub fn get_connector_customer_id(
        &self,
        customer: Option<&customer::Customer>,
        merchant_connector_account: &MerchantConnectorAccountTypeDetails,
    ) -> Option<String>


// Function: refund_response_wrapper
// File: crates/router/src/core/refunds.rs
// Module: router

pub fn refund_response_wrapper<F, Fut, T, Req>(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    request: Req,
    f: F,
) -> RouterResponse<refunds::RefundResponse>
where
    F: Fn(
        SessionState,
        domain::MerchantContext,
        Option<common_utils::id_type::ProfileId>,
        Req,
    ) -> Fut,
    Fut: futures::Future<Output = RouterResult<T>>,
    T: ForeignInto<refunds::RefundResponse>,


// Function: update_payment_method_metadata_and_last_used
// File: crates/router/src/core/payment_methods/cards.rs
// Module: router

pub fn update_payment_method_metadata_and_last_used(
    state: &routes::SessionState,
    key_store: &domain::MerchantKeyStore,
    db: &dyn db::StorageInterface,
    pm: domain::PaymentMethod,
    pm_metadata: Option<serde_json::Value>,
    storage_scheme: MerchantStorageScheme,
) -> errors::CustomResult<(), errors::VaultError>


// Function: retrieve_disputes_list
// File: crates/openapi/src/routes/disputes.rs
// Module: openapi

pub fn retrieve_disputes_list()


// File: crates/openapi/src/routes/three_ds_decision_rule.rs
// Module: openapi
// Public functions: 1

/// 3DS Decision - Execute
#[utoipa::path(
    post,
    path = "/three_ds_decision/execute",
    request_body = ThreeDsDecisionRuleExecuteRequest,
    responses(
        (status = 200, description = "3DS Decision Rule Executed Successfully", body = ThreeDsDecisionRuleExecuteResponse),
        (status = 400, description = "Bad Request")
    ),
    tag = "3DS Decision Rule",
    operation_id = "Execute 3DS Decision Rule",
    security(("api_key" = []))
)]
pub fn three_ds_decision_rule_execute() {}


// Struct: Iter
// File: crates/hyperswitch_constraint_graph/src/dense_map.rs
// Module: hyperswitch_constraint_graph
// Implementations: 2
// Traits: Iterator

pub struct Iter<'a, K, V>


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

impl api::Payment for for Elavon


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

impl api::RefundSync for for Deutschebank


// Struct: TrustpaymentsErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/trustpayments/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 1

pub struct TrustpaymentsErrorResponse


// Function: resolve_lineage_from_user_role
// File: crates/router/src/types/domain/user/decision_manager.rs
// Module: router

pub fn resolve_lineage_from_user_role(
        state: &SessionState,
        user_role: &UserRole,
        user_id: &str,
    ) -> UserResult<LineageContext>


// File: crates/openapi/src/routes/relay.rs
// Module: openapi
// Public functions: 2

/// Relay - Create
///
/// Creates a relay request.
#[utoipa::path(
    post,
    path = "/relay",
    request_body(
        content = RelayRequest,
        examples((
            "Create a relay request" = (
                value = json!({
                    "connector_resource_id": "7256228702616471803954",
                    "connector_id": "mca_5apGeP94tMts6rg3U3kR",
                    "type": "refund",
                    "data": {
                        "refund": {
                            "amount": 6540,
                            "currency": "USD"
                        }
                    }
                })
            )
        ))
    ),
    responses(
        (status = 200, description = "Relay request", body = RelayResponse),
        (status = 400, description = "Invalid data")
    ),
    params(
        ("X-Profile-Id" = String, Header, description = "Profile ID for authentication"),
        ("X-Idempotency-Key" = String, Header, description = "Idempotency Key for relay request")
    ),
    tag = "Relay",
    operation_id = "Relay Request",
    security(("api_key" = []))
)]

pub async fn relay() {}

/// Relay - Retrieve
///
/// Retrieves a relay details.
#[utoipa::path(
    get,
    path = "/relay/{relay_id}",
    params (("relay_id" = String, Path, description = "The unique identifier for the Relay"), ("X-Profile-Id" = String, Header, description = "Profile ID for authentication")),
    responses(
        (status = 200, description = "Relay Retrieved", body = RelayResponse),
        (status = 404, description = "Relay details was not found")
    ),
    tag = "Relay",
    operation_id = "Retrieve a Relay details",
    security(("api_key" = []), ("ephemeral_key" = []))
)]

pub async fn relay_retrieve() {}


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

pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone, D>(
    state: &SessionState,
    payment_data: &mut D,
    routing_data: &mut storage::RoutingData,
    connectors: Vec<api::ConnectorRoutingData>,
    mandate_type: Option<api::MandateTransactionType>,
    is_connector_agnostic_mit_enabled: Option<bool>,
    is_network_tokenization_enabled: bool,
) -> RouterResult<ConnectorCallType>
where
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,


// Implementation: impl PaymentToken for for Payone
// File: crates/hyperswitch_connectors/src/connectors/payone.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl PaymentToken for for Payone


// Function: get_paysafe_payment_status
// File: crates/hyperswitch_connectors/src/connectors/paysafe/transformers.rs
// Module: hyperswitch_connectors

pub fn get_paysafe_payment_status(
    status: PaysafePaymentStatus,
    capture_method: Option<common_enums::CaptureMethod>,
) -> common_enums::AttemptStatus


// Struct: Juspaythreedsserver
// File: crates/hyperswitch_connectors/src/connectors/juspaythreedsserver.rs
// Module: hyperswitch_connectors
// Implementations: 22
// Traits: api::Payment, api::PaymentSession, api::ConnectorAccessToken, api::MandateSetup, api::PaymentAuthorize, api::PaymentSync, api::PaymentCapture, api::PaymentVoid, api::Refund, api::RefundExecute, api::RefundSync, api::PaymentToken, api::UnifiedAuthenticationService, api::UasPreAuthentication, api::UasPostAuthentication, api::UasAuthenticationConfirmation, api::UasAuthentication, ConnectorCommon, ConnectorValidation, webhooks::IncomingWebhook, ConnectorSpecifications

pub struct Juspaythreedsserver

api::Payment api::PaymentSession api::ConnectorAccessToken api::MandateSetup api::PaymentAuthorize api::PaymentSync api::PaymentCapture api::PaymentVoid api::Refund api::RefundExecute api::RefundSync api::PaymentToken api::UnifiedAuthenticationService api::UasPreAuthentication api::UasPostAuthentication api::UasAuthenticationConfirmation api::UasAuthentication ConnectorCommon ConnectorValidation webhooks::IncomingWebhook ConnectorSpecifications


// Function: delete_evidence
// File: crates/router/src/core/disputes.rs
// Module: router

pub fn delete_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    delete_evidence_request: dispute_models::DeleteEvidenceRequest,
) -> RouterResponse<serde_json::Value>


// Function: execute_decision_rule
// File: crates/router/src/routes/three_ds_decision_rule.rs
// Module: router

pub fn execute_decision_rule(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    payload: web::Json<api_models::three_ds_decision_rule::ThreeDsDecisionRuleExecuteRequest>,
) -> impl Responder


// Function: get_proxy_record
// File: crates/router/src/core/proxy/utils.rs
// Module: router

pub fn get_proxy_record(
        &self,
        state: &SessionState,
        key_store: &domain::MerchantKeyStore,
        storage_scheme: common_enums::enums::MerchantStorageScheme,
    ) -> RouterResult<ProxyRecord>


// Struct: SubscriptionWithHandler
// File: crates/router/src/core/subscription.rs
// Module: router
// Implementations: 1

pub struct SubscriptionWithHandler<'a>


// Struct: MonerisErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct MonerisErrorResponse


// Struct: Wise
// File: crates/hyperswitch_connectors/src/connectors/wise.rs
// Module: hyperswitch_connectors
// Implementations: 25
// Traits: ConnectorCommon, api::Payment, api::PaymentAuthorize, api::PaymentSync, api::PaymentVoid, api::PaymentCapture, api::MandateSetup, api::ConnectorAccessToken, api::PaymentToken, api::ConnectorValidation, api::PaymentSession, api::Payouts, api::PayoutCancel, api::PayoutCreate, api::PayoutEligibility, api::PayoutQuote, api::PayoutRecipient, api::PayoutFulfill, api::PayoutSync, Refund, RefundExecute, RefundSync, IncomingWebhook, ConnectorSpecifications

pub struct Wise

ConnectorCommon api::Payment api::PaymentAuthorize api::PaymentSync api::PaymentVoid api::PaymentCapture api::MandateSetup api::ConnectorAccessToken api::PaymentToken api::ConnectorValidation api::PaymentSession api::Payouts api::PayoutCancel api::PayoutCreate api::PayoutEligibility api::PayoutQuote api::PayoutRecipient api::PayoutFulfill api::PayoutSync Refund RefundExecute RefundSync IncomingWebhook ConnectorSpecifications


// File: crates/hyperswitch_interfaces/src/events.rs
// Module: hyperswitch_interfaces

//! Events interface

pub mod connector_api_logs;
pub mod routing_api_logs;


// Struct: DisputeId
// File: crates/router/src/types/api/disputes.rs
// Module: router
// Implementations: 0

pub struct DisputeId


// Struct: WebhookEvents
// File: crates/router/src/routes/app.rs
// Module: router
// Implementations: 1

pub struct WebhookEvents


// Struct: OpennodeAuthType
// File: crates/hyperswitch_connectors/src/connectors/opennode/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OpennodeAuthType


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

pub struct RefundResponse


// Implementation: impl ConnectorSpecifications for for Payload
// File: crates/hyperswitch_connectors/src/connectors/payload.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl ConnectorSpecifications for for Payload


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

use api_models::payments::DeviceChannel;
use base64::Engine;
use common_utils::{consts::BASE64_ENGINE, date_time, types::MinorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, ErrorResponse},
    router_flow_types::authentication::{
        Authentication, PreAuthentication, PreAuthenticationVersionCall,
    },
    router_request_types::{
        authentication::{
            AuthNFlowType, ChallengeParams, ConnectorAuthenticationRequestData, MessageCategory,
            PreAuthNRequestData,
        },
        BrowserInformation,
    },
    router_response_types::AuthenticationResponseData,
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::{ExposeInterface, Secret};
use serde::Deserialize;
use serde_json::to_string;

use super::gpayments_types::{
    self, AuthStatus, BrowserInfoCollected, GpaymentsAuthenticationSuccessResponse,
    GpaymentsPreAuthVersionCallResponse,
};
use crate::{
    types::{
        ConnectorAuthenticationRouterData, PreAuthNRouterData, PreAuthNVersionCallRouterData,
        ResponseRouterData,
    },
    utils::{get_card_details, to_connector_meta_from_secret, CardData as _},
};

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

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

// Auth Struct
pub struct GpaymentsAuthType {
    /// base64 encoded certificate
    pub certificate: Secret<String>,
    /// base64 encoded private_key
    pub private_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for GpaymentsAuthType {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type.to_owned() {
            ConnectorAuthType::CertificateAuth {
                certificate,
                private_key,
            } => Ok(Self {
                certificate,
                private_key,
            }),
            _ => Err(ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

impl TryFrom<&GpaymentsRouterData<&PreAuthNVersionCallRouterData>>
    for gpayments_types::GpaymentsPreAuthVersionCallRequest
{
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(
        value: &GpaymentsRouterData<&PreAuthNVersionCallRouterData>,
    ) -> Result<Self, Self::Error> {
        let router_data = value.router_data;
        let metadata = GpaymentsMetaData::try_from(&router_data.connector_meta_data)?;
        Ok(Self {
            acct_number: router_data.request.card.card_number.clone(),
            merchant_id: metadata.merchant_id,
        })
    }
}

#[derive(Deserialize, PartialEq)]
pub struct GpaymentsMetaData {
    pub endpoint_prefix: String,
    pub merchant_id: common_utils::id_type::MerchantId,
}

impl TryFrom<&Option<common_utils::pii::SecretSerdeValue>> for GpaymentsMetaData {
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(
        meta_data: &Option<common_utils::pii::SecretSerdeValue>,
    ) -> Result<Self, Self::Error> {
        let metadata: Self = to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(ConnectorError::InvalidConnectorConfig { config: "metadata" })?;
        Ok(metadata)
    }
}

impl
    TryFrom<
        ResponseRouterData<
            PreAuthenticationVersionCall,
            GpaymentsPreAuthVersionCallResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    > for PreAuthNVersionCallRouterData
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            PreAuthenticationVersionCall,
            GpaymentsPreAuthVersionCallResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let version_response = item.response;
        let response = Ok(AuthenticationResponseData::PreAuthVersionCallResponse {
            maximum_supported_3ds_version: version_response
                .supported_message_versions
                .and_then(|supported_version| supported_version.iter().max().cloned()) // if no version is returned for the card number, then
                .unwrap_or(common_utils::types::SemanticVersion::new(0, 0, 0)),
        });
        Ok(Self {
            response,
            ..item.data.clone()
        })
    }
}

impl TryFrom<&GpaymentsRouterData<&PreAuthNRouterData>>
    for gpayments_types::GpaymentsPreAuthenticationRequest
{
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(value: &GpaymentsRouterData<&PreAuthNRouterData>) -> Result<Self, Self::Error> {
        let router_data = value.router_data;
        let metadata = GpaymentsMetaData::try_from(&router_data.connector_meta_data)?;
        Ok(Self {
            acct_number: router_data.request.card.card_number.clone(),
            card_scheme: None,
            challenge_window_size: Some(gpayments_types::ChallengeWindowSize::FullScreen),
            event_callback_url: "https://webhook.site/55e3db24-7c4e-4432-9941-d806f68d210b"
                .to_string(),
            merchant_id: metadata.merchant_id,
            skip_auto_browser_info_collect: Some(true),
            // should auto generate this id.
            three_ds_requestor_trans_id: uuid::Uuid::new_v4().hyphenated().to_string(),
        })
    }
}

impl TryFrom<&GpaymentsRouterData<&ConnectorAuthenticationRouterData>>
    for gpayments_types::GpaymentsAuthenticationRequest
{
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(
        item: &GpaymentsRouterData<&ConnectorAuthenticationRouterData>,
    ) -> Result<Self, Self::Error> {
        let request = &item.router_data.request;
        let browser_details = match request.browser_details.clone() {
            Some(details) => Ok::<Option<BrowserInformation>, Self::Error>(Some(details)),
            None => {
                if request.device_channel == DeviceChannel::Browser {
                    Err(ConnectorError::MissingRequiredField {
                        field_name: "browser_info",
                    })?
                } else {
                    Ok(None)
                }
            }
        }?;
        let card_details = get_card_details(request.payment_method_data.clone(), "gpayments")?;

        let metadata = GpaymentsMetaData::try_from(&item.router_data.connector_meta_data)?;

        Ok(Self {
            acct_number: card_details.card_number.clone(),
            authentication_ind: "01".into(),
            card_expiry_date: card_details.get_expiry_date_as_yymm()?.expose(),
            merchant_id: metadata.merchant_id,
            message_category: match item.router_data.request.message_category.clone() {
                MessageCategory::Payment => "01".into(),
                MessageCategory::NonPayment => "02".into(),
            },
            notification_url: request
                .return_url
                .clone()
                .ok_or(ConnectorError::RequestEncodingFailed)
                .attach_printable("missing return_url")?,
            three_ds_comp_ind: request.threeds_method_comp_ind.clone(),
            purchase_amount: item.amount.to_string(),
            purchase_date: date_time::DateTime::<date_time::YYYYMMDDHHmmss>::from(date_time::now())
                .to_string(),
            three_ds_server_trans_id: request
                .pre_authentication_data
                .threeds_server_transaction_id
                .clone(),
            browser_info_collected: BrowserInfoCollected {
                browser_javascript_enabled: browser_details
                    .as_ref()
                    .and_then(|details| details.java_script_enabled),
                browser_accept_header: browser_details
                    .as_ref()
                    .and_then(|details| details.accept_header.clone()),
                browser_ip: browser_details
                    .clone()
                    .and_then(|details| details.ip_address.map(|ip| Secret::new(ip.to_string()))),
                browser_java_enabled: browser_details
                    .as_ref()
                    .and_then(|details| details.java_enabled),
                browser_language: browser_details
                    .as_ref()
                    .and_then(|details| details.language.clone()),
                browser_color_depth: browser_details
                    .as_ref()
                    .and_then(|details| details.color_depth.map(|a| a.to_string())),
                browser_screen_height: browser_details
                    .as_ref()
                    .and_then(|details| details.screen_height.map(|a| a.to_string())),
                browser_screen_width: browser_details
                    .as_ref()
                    .and_then(|details| details.screen_width.map(|a| a.to_string())),
                browser_tz: browser_details
                    .as_ref()
                    .and_then(|details| details.time_zone.map(|a| a.to_string())),
                browser_user_agent: browser_details
                    .as_ref()
                    .and_then(|details| details.user_agent.clone().map(|a| a.to_string())),
            },
        })
    }
}
impl
    TryFrom<
        ResponseRouterData<
            Authentication,
            GpaymentsAuthenticationSuccessResponse,
            ConnectorAuthenticationRequestData,
            AuthenticationResponseData,
        >,
    > for ConnectorAuthenticationRouterData
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authentication,
            GpaymentsAuthenticationSuccessResponse,
            ConnectorAuthenticationRequestData,
            AuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response_auth = item.response;
        let creq = serde_json::json!({
            "threeDSServerTransID": response_auth.three_ds_server_trans_id,
            "acsTransID": response_auth.acs_trans_id,
            "messageVersion": response_auth.message_version,
            "messageType": "CReq",
            "challengeWindowSize": "01",
        });
        let creq_str = to_string(&creq)
            .change_context(ConnectorError::ResponseDeserializationFailed)
            .attach_printable("error while constructing creq_str")?;
        let creq_base64 = Engine::encode(&BASE64_ENGINE, creq_str)
            .trim_end_matches('=')
            .to_owned();
        let response: Result<AuthenticationResponseData, ErrorResponse> =
            Ok(AuthenticationResponseData::AuthNResponse {
                trans_status: response_auth.trans_status.clone().into(),
                authn_flow_type: if response_auth.trans_status == AuthStatus::C {
                    AuthNFlowType::Challenge(Box::new(ChallengeParams {
                        acs_url: response_auth.acs_url,
                        challenge_request: Some(creq_base64),
                        acs_reference_number: Some(response_auth.acs_reference_number.clone()),
                        acs_trans_id: Some(response_auth.acs_trans_id.clone()),
                        three_dsserver_trans_id: Some(response_auth.three_ds_server_trans_id),
                        acs_signed_content: None,
                        challenge_request_key: None,
                    }))
                } else {
                    AuthNFlowType::Frictionless
                },
                authentication_value: response_auth.authentication_value,
                ds_trans_id: Some(response_auth.ds_trans_id),
                connector_metadata: None,
                eci: None,
                challenge_code: None,
                challenge_cancel: None,
                challenge_code_reason: None,
                message_extension: None,
            });
        Ok(Self {
            response,
            ..item.data.clone()
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            PreAuthentication,
            gpayments_types::GpaymentsPreAuthenticationResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    > for PreAuthNRouterData
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            PreAuthentication,
            gpayments_types::GpaymentsPreAuthenticationResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let threeds_method_response = item.response;
        let three_ds_method_data = threeds_method_response
            .three_ds_method_url
            .as_ref()
            .map(|_| {
                let three_ds_method_data_json = serde_json::json!({
                    "threeDSServerTransID": threeds_method_response.three_ds_server_trans_id,
                    "threeDSMethodNotificationURL": "https://webhook.site/bd06863d-82c2-42ea-b35b-5ffd5ecece71"
                });
                to_string(&three_ds_method_data_json)
                    .change_context(ConnectorError::ResponseDeserializationFailed)
                    .attach_printable("error while constructing three_ds_method_data_str")
                    .map(|three_ds_method_data_string| {
                        Engine::encode(&BASE64_ENGINE, three_ds_method_data_string)
                    })
            })
            .transpose()?;
        let connector_metadata = Some(serde_json::json!(
            gpayments_types::GpaymentsConnectorMetaData {
                authentication_url: threeds_method_response.auth_url,
                three_ds_requestor_trans_id: None,
            }
        ));
        let response: Result<AuthenticationResponseData, ErrorResponse> = Ok(
            AuthenticationResponseData::PreAuthThreeDsMethodCallResponse {
                threeds_server_transaction_id: threeds_method_response
                    .three_ds_server_trans_id
                    .clone(),
                three_ds_method_data,
                three_ds_method_url: threeds_method_response.three_ds_method_url,
                connector_metadata,
            },
        );
        Ok(Self {
            response,
            ..item.data.clone()
        })
    }
}


// Struct: BluesnapConnectorMetaData
// File: crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct BluesnapConnectorMetaData


// Implementation: impl api::PaymentSync for for Adyenplatform
// File: crates/hyperswitch_connectors/src/connectors/adyenplatform.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSync for for Adyenplatform


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

impl api::Refund for for Prophetpay


// Function: new
// File: crates/hyperswitch_connectors/src/connectors/cryptopay.rs
// Module: hyperswitch_connectors

pub fn new() -> &'static Self


// Function: get_payment_method_collect_data
// File: crates/diesel_models/src/generic_link.rs
// Module: diesel_models

pub fn get_payment_method_collect_data(&self) -> Result<&PaymentMethodCollectLinkData, String>


// File: crates/hyperswitch_connectors/src/connectors/dwolla/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 20

use common_enums::{enums, AttemptStatus};
use common_utils::{errors::CustomResult, types::StringMajorUnit};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    payment_method_data::{BankDebitData, PaymentMethodData},
    router_data::{AccessToken, ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::refunds::RSync,
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RefundsResponseData},
    types,
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self, CustomerData, RouterData as _},
};

pub struct DwollaAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
}

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

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct DwollaAccessTokenRequest {
    pub grant_type: String,
}

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

pub fn extract_token_from_body(body: &[u8]) -> CustomResult<String, errors::ConnectorError> {
    let parsed: serde_json::Value = serde_json::from_slice(body)
        .map_err(|_| report!(errors::ConnectorError::ResponseDeserializationFailed))?;

    parsed
        .get("_links")
        .and_then(|links| links.get("about"))
        .and_then(|about| about.get("href"))
        .and_then(|href| href.as_str())
        .and_then(|url| url.rsplit('/').next())
        .map(|id| id.to_string())
        .ok_or_else(|| report!(errors::ConnectorError::ResponseHandlingFailed))
}

fn map_topic_to_status(topic: &str) -> DwollaPaymentStatus {
    match topic {
        "customer_transfer_created" | "customer_bank_transfer_created" => {
            DwollaPaymentStatus::Pending
        }
        "customer_transfer_completed" | "customer_bank_transfer_completed" => {
            DwollaPaymentStatus::Succeeded
        }
        "customer_transfer_failed" | "customer_bank_transfer_failed" => DwollaPaymentStatus::Failed,
        _ => DwollaPaymentStatus::Pending,
    }
}

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

#[derive(Debug)]
pub struct DwollaRouterData<'a, T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
    pub base_url: &'a str,
}

impl<'a, T> TryFrom<(StringMajorUnit, T, &'a str)> for DwollaRouterData<'a, T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (amount, router_data, base_url): (StringMajorUnit, T, &'a str),
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data,
            base_url,
        })
    }
}

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

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct DwollaCustomerResponse {}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct DwollaFundingSourceResponse {}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DwollaFundingSourceRequest {
    routing_number: Secret<String>,
    account_number: Secret<String>,
    #[serde(rename = "type")]
    account_type: common_enums::BankType,
    name: Secret<String>,
}

#[derive(Debug, Serialize, PartialEq, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DwollaPaymentsRequest {
    #[serde(rename = "_links")]
    links: DwollaPaymentLinks,
    amount: DwollaAmount,
    correlation_id: String,
}

#[derive(Default, Debug, Serialize, PartialEq, Deserialize, Clone)]
pub struct DwollaPaymentLinks {
    source: DwollaRequestLink,
    destination: DwollaRequestLink,
}

#[derive(Default, Debug, Serialize, PartialEq, Deserialize, Clone)]
pub struct DwollaRequestLink {
    href: String,
}

#[derive(Debug, Serialize, PartialEq, Deserialize, Clone)]
pub struct DwollaAmount {
    pub currency: common_enums::Currency,
    pub value: StringMajorUnit,
}

#[derive(Debug, Serialize, PartialEq, Deserialize, Clone)]
#[serde(untagged)]
pub enum DwollaPSyncResponse {
    Payment(DwollaPaymentSyncResponse),
    Webhook(DwollaWebhookDetails),
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct DwollaPaymentSyncResponse {
    pub id: String,
    pub status: DwollaPaymentStatus,
    pub amount: DwollaAmount,
}

#[derive(Debug, Serialize, PartialEq, Deserialize, Clone)]
#[serde(untagged)]
pub enum DwollaRSyncResponse {
    Payment(DwollaRefundSyncResponse),
    Webhook(DwollaWebhookDetails),
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct DwollaRefundSyncResponse {
    id: String,
    status: DwollaPaymentStatus,
    amount: DwollaAmount,
}

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

#[derive(Debug, Serialize, PartialEq, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DwollaRefundsRequest {
    #[serde(rename = "_links")]
    links: DwollaPaymentLinks,
    amount: DwollaAmount,
    correlation_id: String,
}

impl TryFrom<&types::ConnectorCustomerRouterData> for DwollaCustomerRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::ConnectorCustomerRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            first_name: item.get_billing_first_name()?,
            last_name: item.get_billing_last_name()?,
            email: item
                .request
                .get_email()
                .or_else(|_| item.get_billing_email())?,
        })
    }
}

impl TryFrom<&types::TokenizationRouterData> for DwollaFundingSourceRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
        match item.request.payment_method_data.clone() {
            PaymentMethodData::BankDebit(BankDebitData::AchBankDebit {
                ref routing_number,
                ref account_number,
                ref bank_type,
                ref bank_account_holder_name,
                ..
            }) => {
                let account_type =
                    (*bank_type).ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                        field_name: "bank_type",
                    })?;

                let name = bank_account_holder_name.clone().ok_or_else(|| {
                    errors::ConnectorError::MissingRequiredField {
                        field_name: "bank_account_holder_name",
                    }
                })?;

                let request = Self {
                    routing_number: routing_number.clone(),
                    account_number: account_number.clone(),
                    account_type,
                    name,
                };
                Ok(request)
            }
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("dwolla"),
            ))?,
        }
    }
}

impl<'a> TryFrom<&DwollaRouterData<'a, &PaymentsAuthorizeRouterData>> for DwollaPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &DwollaRouterData<'a, &PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let source_funding = match item.router_data.get_payment_method_token().ok() {
            Some(PaymentMethodToken::Token(pm_token)) => pm_token,
            _ => {
                return Err(report!(errors::ConnectorError::MissingRequiredField {
                    field_name: "payment_method_token",
                }))
            }
        };

        let metadata = utils::to_connector_meta_from_secret::<DwollaMetaData>(
            item.router_data.connector_meta_data.clone(),
        )
        .change_context(errors::ConnectorError::InvalidConnectorConfig { config: "metadata" })?;

        let source_url = format!(
            "{}/funding-sources/{}",
            item.base_url,
            source_funding.expose()
        );

        let destination_url = format!(
            "{}/funding-sources/{}",
            item.base_url,
            metadata.merchant_funding_source.expose()
        );

        let request = Self {
            links: DwollaPaymentLinks {
                source: DwollaRequestLink { href: source_url },
                destination: DwollaRequestLink {
                    href: destination_url,
                },
            },
            amount: DwollaAmount {
                currency: item.router_data.request.currency,
                value: item.amount.to_owned(),
            },
            correlation_id: format!(
                "payment_{}",
                item.router_data.connector_request_reference_id
            ),
        };

        Ok(request)
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, DwollaPSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, DwollaPSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let connector_metadata =
            item.data
                .payment_method_token
                .as_ref()
                .and_then(|token| match token {
                    PaymentMethodToken::Token(t) => {
                        Some(serde_json::json!({ "payment_token": t.clone().expose() }))
                    }
                    _ => None,
                });
        match item.response {
            DwollaPSyncResponse::Payment(payment_response) => {
                let payment_id = payment_response.id.clone();
                let status = payment_response.status;
                Ok(Self {
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(payment_id.clone()),
                        redirection_data: Box::new(None),
                        mandate_reference: Box::new(None),
                        connector_metadata,
                        network_txn_id: None,
                        connector_response_reference_id: Some(payment_id.clone()),
                        incremental_authorization_allowed: None,
                        charges: None,
                    }),
                    status: AttemptStatus::from(status),
                    ..item.data
                })
            }
            DwollaPSyncResponse::Webhook(webhook_response) => {
                let payment_id = webhook_response.resource_id.clone();

                Ok(Self {
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(payment_id.clone()),
                        redirection_data: Box::new(None),
                        mandate_reference: Box::new(None),
                        connector_metadata,
                        network_txn_id: None,
                        connector_response_reference_id: Some(payment_id.clone()),
                        incremental_authorization_allowed: None,
                        charges: None,
                    }),
                    status: AttemptStatus::from(map_topic_to_status(
                        webhook_response.topic.as_str(),
                    )),
                    ..item.data
                })
            }
        }
    }
}

impl<'a, F> TryFrom<&DwollaRouterData<'a, &RefundsRouterData<F>>> for DwollaRefundsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &DwollaRouterData<'a, &RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let destination_funding = item
            .router_data
            .request
            .connector_metadata
            .as_ref()
            .and_then(|meta| {
                meta.get("payment_token")
                    .and_then(|token| token.as_str().map(|s| s.to_string()))
            })
            .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                field_name: "payment_token",
            })?;

        let metadata = utils::to_connector_meta_from_secret::<DwollaMetaData>(
            item.router_data.connector_meta_data.clone(),
        )
        .change_context(errors::ConnectorError::InvalidConnectorConfig { config: "metadata" })?;

        let source_url = format!(
            "{}/funding-sources/{}",
            item.base_url,
            metadata.merchant_funding_source.expose()
        );

        let destination_url = format!("{}/funding-sources/{}", item.base_url, destination_funding);

        let request = Self {
            links: DwollaPaymentLinks {
                source: DwollaRequestLink { href: source_url },
                destination: DwollaRequestLink {
                    href: destination_url,
                },
            },
            amount: DwollaAmount {
                currency: item.router_data.request.currency,
                value: item.amount.to_owned(),
            },
            correlation_id: format!("refund_{}", item.router_data.connector_request_reference_id),
        };

        Ok(request)
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, DwollaRSyncResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, DwollaRSyncResponse>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            DwollaRSyncResponse::Payment(refund_response) => {
                let refund_id = refund_response.id.clone();
                let status = refund_response.status;
                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_id,
                        refund_status: enums::RefundStatus::from(status),
                    }),
                    ..item.data
                })
            }
            DwollaRSyncResponse::Webhook(webhook_response) => {
                let refund_id = webhook_response.resource_id.clone();
                let status = map_topic_to_status(webhook_response.topic.as_str());
                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_id,
                        refund_status: enums::RefundStatus::from(status),
                    }),
                    ..item.data
                })
            }
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DwollaPaymentStatus {
    Succeeded,
    Failed,
    Pending,
    #[default]
    Processing,
    Processed,
}

impl From<DwollaPaymentStatus> for AttemptStatus {
    fn from(item: DwollaPaymentStatus) -> Self {
        match item {
            DwollaPaymentStatus::Succeeded => Self::Charged,
            DwollaPaymentStatus::Processed => Self::Charged,
            DwollaPaymentStatus::Failed => Self::Failure,
            DwollaPaymentStatus::Processing => Self::Pending,
            DwollaPaymentStatus::Pending => Self::Pending,
        }
    }
}

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

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct DwollaErrorResponse {
    pub code: String,
    pub message: String,
    pub _embedded: Option<Vec<DwollaErrorDetails>>,
    pub reason: Option<String>,
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct DwollaErrorDetails {
    pub errors: Vec<DwollaErrorDetail>,
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct DwollaErrorDetail {
    pub code: Option<String>,
    pub message: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DwollaWebhookDetails {
    pub id: String,
    pub resource_id: String,
    pub topic: String,
    pub correlation_id: Option<String>,
}

impl From<&str> for DwollaWebhookEventType {
    fn from(topic: &str) -> Self {
        match topic {
            "customer_created" => Self::CustomerCreated,
            "customer_verified" => Self::CustomerVerified,
            "customer_funding_source_added" => Self::CustomerFundingSourceAdded,
            "customer_funding_source_removed" => Self::CustomerFundingSourceRemoved,
            "customer_funding_source_verified" => Self::CustomerFundingSourceVerified,
            "customer_funding_source_unverified" => Self::CustomerFundingSourceUnverified,
            "customer_microdeposits_added" => Self::CustomerMicrodepositsAdded,
            "customer_microdeposits_failed" => Self::CustomerMicrodepositsFailed,
            "customer_microdeposits_completed" => Self::CustomerMicrodepositsCompleted,
            "customer_microdeposits_maxattempts" => Self::CustomerMicrodepositsMaxAttempts,
            "customer_bank_transfer_creation_failed" => Self::CustomerBankTransferCreationFailed,
            "customer_bank_transfer_created" => Self::CustomerBankTransferCreated,
            "customer_transfer_created" => Self::CustomerTransferCreated,
            "customer_bank_transfer_failed" => Self::CustomerBankTransferFailed,
            "customer_bank_transfer_completed" => Self::CustomerBankTransferCompleted,
            "customer_transfer_completed" => Self::CustomerTransferCompleted,
            "customer_transfer_failed" => Self::CustomerTransferFailed,
            "transfer_created" => Self::TransferCreated,
            "transfer_pending" => Self::TransferPending,
            "transfer_completed" => Self::TransferCompleted,
            "transfer_failed" => Self::TransferFailed,
            _ => Self::Unknown,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum DwollaWebhookEventType {
    CustomerCreated,
    CustomerVerified,
    CustomerFundingSourceAdded,
    CustomerFundingSourceRemoved,
    CustomerFundingSourceUnverified,
    CustomerFundingSourceVerified,
    CustomerMicrodepositsAdded,
    CustomerMicrodepositsFailed,
    CustomerMicrodepositsCompleted,
    CustomerMicrodepositsMaxAttempts,
    CustomerTransferCreated,
    CustomerBankTransferCreationFailed,
    CustomerBankTransferCreated,
    CustomerBankTransferCompleted,
    CustomerBankTransferFailed,
    CustomerTransferCompleted,
    CustomerTransferFailed,
    TransferCreated,
    TransferPending,
    TransferCompleted,
    TransferFailed,
    #[serde(other)]
    Unknown,
}

impl TryFrom<DwollaWebhookDetails> for api_models::webhooks::IncomingWebhookEvent {
    type Error = errors::ConnectorError;
    fn try_from(details: DwollaWebhookDetails) -> Result<Self, Self::Error> {
        let correlation_id = match details.correlation_id.as_deref() {
            Some(cid) => cid,
            None => {
                return Ok(Self::EventNotSupported);
            }
        };
        let event_type = DwollaWebhookEventType::from(details.topic.as_str());
        let is_refund = correlation_id.starts_with("refund_");
        Ok(match (event_type, is_refund) {
            (DwollaWebhookEventType::CustomerTransferCompleted, true)
            | (DwollaWebhookEventType::CustomerBankTransferCompleted, true) => Self::RefundSuccess,
            (DwollaWebhookEventType::CustomerTransferFailed, true)
            | (DwollaWebhookEventType::CustomerBankTransferFailed, true) => Self::RefundFailure,

            (DwollaWebhookEventType::CustomerTransferCreated, false)
            | (DwollaWebhookEventType::CustomerBankTransferCreated, false) => {
                Self::PaymentIntentProcessing
            }
            (DwollaWebhookEventType::CustomerTransferCompleted, false)
            | (DwollaWebhookEventType::CustomerBankTransferCompleted, false) => {
                Self::PaymentIntentSuccess
            }
            (DwollaWebhookEventType::CustomerTransferFailed, false)
            | (DwollaWebhookEventType::CustomerBankTransferFailed, false) => {
                Self::PaymentIntentFailure
            }
            _ => Self::EventNotSupported,
        })
    }
}