pub async fn perform_debit_routing<F, Req, D>(
    operation: &BoxedOperation<'_, F, Req, D>,
    state: &SessionState,
    business_profile: &domain::Profile,
    payment_data: &mut D,
    connector: Option<ConnectorCallType>,
) -> (
    Option<ConnectorCallType>,
    Option<open_router::DebitRoutingOutput>,
)
where
    F: Send + Clone,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
{
    let mut debit_routing_output = None;

    if should_execute_debit_routing(state, business_profile, operation, payment_data).await {
        let debit_routing_config = state.conf.debit_routing_config.clone();
        let debit_routing_supported_connectors = debit_routing_config.supported_connectors.clone();

        // If the business profile does not have a country set, we cannot perform debit routing,
        // because the merchant_business_country will be treated as the acquirer_country,
        // which is used to determine whether a transaction is local or global in the open router.
        // For now, since debit routing is only implemented for USD, we can safely assume the
        // acquirer_country is US if not provided by the merchant.

        let acquirer_country = business_profile
            .merchant_business_country
            .unwrap_or_default();

        if let Some(call_connector_type) = connector.clone() {
            debit_routing_output = match call_connector_type {
                ConnectorCallType::PreDetermined(connector_data) => {
                    logger::info!("Performing debit routing for PreDetermined connector");
                    handle_pre_determined_connector(
                        state,
                        debit_routing_supported_connectors,
                        &connector_data,
                        payment_data,
                        acquirer_country,
                    )
                    .await
                }
                ConnectorCallType::Retryable(connector_data) => {
                    logger::info!("Performing debit routing for Retryable connector");
                    handle_retryable_connector(
                        state,
                        debit_routing_supported_connectors,
                        connector_data,
                        payment_data,
                        acquirer_country,
                    )
                    .await
                }
                ConnectorCallType::SessionMultiple(_) => {
                    logger::info!(
                        "SessionMultiple connector type is not supported for debit routing"
                    );
                    None
                }
                #[cfg(feature = "v2")]
                ConnectorCallType::Skip => {
                    logger::info!("Skip connector type is not supported for debit routing");
                    None
                }
            };
        }
    }

    if let Some(debit_routing_output) = debit_routing_output {
        (
            Some(debit_routing_output.debit_routing_connector_call_type),
            Some(debit_routing_output.debit_routing_output),
        )
    } else {
        // If debit_routing_output is None, return the static routing output (connector)
        logger::info!("Debit routing is not performed, returning static routing output");
        (connector, None)
    }
}


pub struct FortePaymentsSyncResponse {
    pub transaction_id: String,
    pub organization_id: Secret<String>,
    pub location_id: Secret<String>,
    pub original_transaction_id: Option<String>,
    pub status: FortePaymentStatus,
    pub action: ForteAction,
    pub authorization_code: String,
    pub authorization_amount: Option<FloatMajorUnit>,
    pub billing_address: Option<BillingAddress>,
    pub entered_by: String,
    pub received_date: String,
    pub origination_date: Option<String>,
    pub card: Option<CardResponse>,
    pub attempt_number: i64,
    pub response: ResponseStatus,
    pub links: ForteLink,
    pub biller_name: Option<String>,
}


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

use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData};
use masking::Secret;
use router::types::{self, api, storage::enums};
use test_utils::connector_auth;

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

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

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

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

static CONNECTOR: JpmorganTest = JpmorganTest {};

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: PaymentMethodData::Card(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: PaymentMethodData::Card(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: PaymentMethodData::Card(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


            PaymentMethodType::Gcash => Self::Wallet,
            PaymentMethodType::Mifinity => Self::Wallet,
            PaymentMethodType::Ideal => Self::BankRedirect,
            PaymentMethodType::Klarna => Self::PayLater,
            PaymentMethodType::KakaoPay => Self::Wallet,
            PaymentMethodType::Knet => Self::CardRedirect,
            PaymentMethodType::LocalBankRedirect => Self::BankRedirect,
            PaymentMethodType::MbWay => Self::Wallet,
            PaymentMethodType::MobilePay => Self::Wallet,
            PaymentMethodType::Momo => Self::Wallet,
            PaymentMethodType::MomoAtm => Self::CardRedirect,
            PaymentMethodType::Multibanco => Self::BankTransfer,
            PaymentMethodType::MandiriVa => Self::BankTransfer,
            PaymentMethodType::Interac => Self::BankRedirect,
            PaymentMethodType::InstantBankTransfer => Self::BankTransfer,
            PaymentMethodType::InstantBankTransferFinland => Self::BankTransfer,
            PaymentMethodType::InstantBankTransferPoland => Self::BankTransfer,
            PaymentMethodType::Indomaret => Self::Voucher,
            PaymentMethodType::OnlineBankingCzechRepublic => Self::BankRedirect,
            PaymentMethodType::OnlineBankingFinland => Self::BankRedirect,
            PaymentMethodType::OnlineBankingFpx => Self::BankRedirect,
            PaymentMethodType::OnlineBankingThailand => Self::BankRedirect,
            PaymentMethodType::OnlineBankingPoland => Self::BankRedirect,
            PaymentMethodType::OnlineBankingSlovakia => Self::BankRedirect,
            PaymentMethodType::Paze => Self::Wallet,
            PaymentMethodType::PermataBankTransfer => Self::BankTransfer,
            PaymentMethodType::Pix => Self::BankTransfer,
            PaymentMethodType::Pse => Self::BankTransfer,
            PaymentMethodType::LocalBankTransfer => Self::BankTransfer,
            PaymentMethodType::PayBright => Self::PayLater,
            PaymentMethodType::Paypal => Self::Wallet,
            PaymentMethodType::PaySafeCard => Self::GiftCard,
            PaymentMethodType::Przelewy24 => Self::BankRedirect,
            PaymentMethodType::PromptPay => Self::RealTimePayment,
            PaymentMethodType::SamsungPay => Self::Wallet,
            PaymentMethodType::Sepa => Self::BankDebit,
            PaymentMethodType::SepaBankTransfer => Self::BankTransfer,
            PaymentMethodType::Sofort => Self::BankRedirect,
            PaymentMethodType::Swish => Self::BankRedirect,
            PaymentMethodType::Trustly => Self::BankRedirect,
            PaymentMethodType::Twint => Self::Wallet,
            PaymentMethodType::UpiCollect => Self::Upi,
            PaymentMethodType::UpiIntent => Self::Upi,
            PaymentMethodType::Vipps => Self::Wallet,
            PaymentMethodType::Venmo => Self::Wallet,
            PaymentMethodType::VietQr => Self::RealTimePayment,
            PaymentMethodType::Walley => Self::PayLater,
            PaymentMethodType::WeChatPay => Self::Wallet,
            PaymentMethodType::TouchNGo => Self::Wallet,
            PaymentMethodType::Atome => Self::PayLater,
            PaymentMethodType::Boleto => Self::Voucher,
            PaymentMethodType::Efecty => Self::Voucher,
            PaymentMethodType::PagoEfectivo => Self::Voucher,
            PaymentMethodType::RedCompra => Self::Voucher,
            PaymentMethodType::RedPagos => Self::Voucher,
            PaymentMethodType::Cashapp => Self::Wallet,
            PaymentMethodType::BhnCardNetwork => Self::GiftCard,
            PaymentMethodType::Givex => Self::GiftCard,
            PaymentMethodType::Oxxo => Self::Voucher,
            PaymentMethodType::OpenBankingUk => Self::BankRedirect,
            PaymentMethodType::SevenEleven => Self::Voucher,
            PaymentMethodType::Lawson => Self::Voucher,
            PaymentMethodType::MiniStop => Self::Voucher,
            PaymentMethodType::FamilyMart => Self::Voucher,
            PaymentMethodType::Seicomart => Self::Voucher,
            PaymentMethodType::PayEasy => Self::Voucher,
            PaymentMethodType::OpenBankingPIS => Self::OpenBanking,
            PaymentMethodType::DirectCarrierBilling => Self::MobilePayment,
            PaymentMethodType::RevolutPay => Self::Wallet,
            PaymentMethodType::IndonesianBankTransfer => Self::BankTransfer,
        }
    }
}

#[derive(Debug)]
pub struct NumericCountryCodeParseError;
#[allow(dead_code)]
mod custom_serde {
    use super::*;

    pub mod alpha2_country_code {
        use std::fmt;

        use serde::de::Visitor;

        use super::*;

        // `serde::Serialize` implementation needs the function to accept `&Country`
        #[allow(clippy::trivially_copy_pass_by_ref)]
        pub fn serialize<S>(code: &Country, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            code.to_alpha2().serialize(serializer)
        }

        struct FieldVisitor;

        impl Visitor<'_> for FieldVisitor {
            type Value = CountryAlpha2;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                formatter.write_str("CountryAlpha2 as a string")
            }
        }

        pub fn deserialize<'a, D>(deserializer: D) -> Result<Country, D::Error>
        where
            D: serde::Deserializer<'a>,
        {
            CountryAlpha2::deserialize(deserializer).map(Country::from_alpha2)
        }
    }

    pub mod alpha3_country_code {
        use std::fmt;

        use serde::de::Visitor;

        use super::*;

        // `serde::Serialize` implementation needs the function to accept `&Country`
        #[allow(clippy::trivially_copy_pass_by_ref)]
        pub fn serialize<S>(code: &Country, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            code.to_alpha3().serialize(serializer)
        }

        struct FieldVisitor;

        impl Visitor<'_> for FieldVisitor {
            type Value = CountryAlpha3;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                formatter.write_str("CountryAlpha3 as a string")
            }
        }

        pub fn deserialize<'a, D>(deserializer: D) -> Result<Country, D::Error>
        where
            D: serde::Deserializer<'a>,
        {
            CountryAlpha3::deserialize(deserializer).map(Country::from_alpha3)
        }
    }

    pub mod numeric_country_code {
        use std::fmt;

        use serde::de::Visitor;

        use super::*;

        // `serde::Serialize` implementation needs the function to accept `&Country`
        #[allow(clippy::trivially_copy_pass_by_ref)]
        pub fn serialize<S>(code: &Country, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            code.to_numeric().serialize(serializer)
        }

        struct FieldVisitor;

        impl Visitor<'_> for FieldVisitor {
            type Value = u32;

            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
                formatter.write_str("Numeric country code passed as an u32")
            }
        }

        pub fn deserialize<'a, D>(deserializer: D) -> Result<Country, D::Error>
        where
            D: serde::Deserializer<'a>,
        {
            u32::deserialize(deserializer)
                .and_then(|value| Country::from_numeric(value).map_err(serde::de::Error::custom))
        }
    }
}

impl From<Option<bool>> for super::External3dsAuthenticationRequest {
    fn from(value: Option<bool>) -> Self {
        match value {
            Some(true) => Self::Enable,
            _ => Self::Skip,
        }
    }
}

/// Get the boolean value of the `External3dsAuthenticationRequest`.
impl super::External3dsAuthenticationRequest {
    pub fn as_bool(&self) -> bool {
        match self {
            Self::Enable => true,
            Self::Skip => false,
        }
    }
}

impl super::EnablePaymentLinkRequest {
    pub fn as_bool(&self) -> bool {
        match self {
            Self::Enable => true,
            Self::Skip => false,
        }
    }
}

impl From<Option<bool>> for super::EnablePaymentLinkRequest {
    fn from(value: Option<bool>) -> Self {
        match value {
            Some(true) => Self::Enable,
            _ => Self::Skip,
        }
    }
}

impl From<Option<bool>> for super::MitExemptionRequest {
    fn from(value: Option<bool>) -> Self {
        match value {
            Some(true) => Self::Apply,
            _ => Self::Skip,
        }
    }
}

impl super::MitExemptionRequest {
    pub fn as_bool(&self) -> bool {
        match self {
            Self::Apply => true,
            Self::Skip => false,
        }
    }
}

impl From<Option<bool>> for super::PresenceOfCustomerDuringPayment {
    fn from(value: Option<bool>) -> Self {
        match value {
            Some(false) => Self::Absent,
            _ => Self::Present,
        }
    }
}

impl super::PresenceOfCustomerDuringPayment {
    pub fn as_bool(&self) -> bool {
        match self {
            Self::Present => true,
            Self::Absent => false,
        }
    }
}

impl From<AttemptStatus> for IntentStatus {
    fn from(s: AttemptStatus) -> Self {
        match s {
            AttemptStatus::Charged | AttemptStatus::AutoRefunded => Self::Succeeded,

            AttemptStatus::ConfirmationAwaited => Self::RequiresConfirmation,
            AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod,

            AttemptStatus::Authorized => Self::RequiresCapture,
            AttemptStatus::AuthenticationPending | AttemptStatus::DeviceDataCollectionPending => {
                Self::RequiresCustomerAction
            }
            AttemptStatus::Unresolved => Self::RequiresMerchantAction,
            AttemptStatus::IntegrityFailure => Self::Conflicted,
            AttemptStatus::PartialCharged => Self::PartiallyCaptured,
            AttemptStatus::PartialChargedAndChargeable => Self::PartiallyCapturedAndCapturable,
            AttemptStatus::Started
            | AttemptStatus::AuthenticationSuccessful
            | AttemptStatus::Authorizing
            | AttemptStatus::CodInitiated
            | AttemptStatus::VoidInitiated
            | AttemptStatus::CaptureInitiated
            | AttemptStatus::Pending => Self::Processing,

            AttemptStatus::AuthenticationFailed
            | AttemptStatus::AuthorizationFailed
            | AttemptStatus::VoidFailed
            | AttemptStatus::RouterDeclined
            | AttemptStatus::CaptureFailed
            | AttemptStatus::Failure => Self::Failed,
            AttemptStatus::Voided => Self::Cancelled,
            AttemptStatus::VoidedPostCharge => Self::CancelledPostCapture,
            AttemptStatus::Expired => Self::Expired,
            AttemptStatus::PartiallyAuthorized => Self::PartiallyAuthorizedAndRequiresCapture,
        }
    }
}

impl From<IntentStatus> for Option<EventType> {
    fn from(value: IntentStatus) -> Self {
        match value {
            IntentStatus::Succeeded => Some(EventType::PaymentSucceeded),
            IntentStatus::Failed => Some(EventType::PaymentFailed),
            IntentStatus::Processing => Some(EventType::PaymentProcessing),
            IntentStatus::RequiresMerchantAction
            | IntentStatus::RequiresCustomerAction
            | IntentStatus::Conflicted => Some(EventType::ActionRequired),
            IntentStatus::Cancelled => Some(EventType::PaymentCancelled),
            IntentStatus::CancelledPostCapture => Some(EventType::PaymentCancelledPostCapture),
            IntentStatus::Expired => Some(EventType::PaymentExpired),
            IntentStatus::PartiallyCaptured | IntentStatus::PartiallyCapturedAndCapturable => {
                Some(EventType::PaymentCaptured)
            }
            IntentStatus::RequiresCapture => Some(EventType::PaymentAuthorized),
            IntentStatus::RequiresPaymentMethod | IntentStatus::RequiresConfirmation => None,
            IntentStatus::PartiallyAuthorizedAndRequiresCapture => {
                Some(EventType::PaymentPartiallyAuthorized)
            }
        }
    }
}

impl From<RefundStatus> for Option<EventType> {
    fn from(value: RefundStatus) -> Self {
        match value {
            RefundStatus::Success => Some(EventType::RefundSucceeded),
            RefundStatus::Failure => Some(EventType::RefundFailed),
            RefundStatus::ManualReview
            | RefundStatus::Pending
            | RefundStatus::TransactionFailure => None,
        }
    }
}

#[cfg(feature = "payouts")]
impl From<PayoutStatus> for Option<EventType> {
    fn from(value: PayoutStatus) -> Self {
        match value {
            PayoutStatus::Success => Some(EventType::PayoutSuccess),
            PayoutStatus::Failed => Some(EventType::PayoutFailed),
            PayoutStatus::Cancelled => Some(EventType::PayoutCancelled),
            PayoutStatus::Initiated => Some(EventType::PayoutInitiated),
            PayoutStatus::Expired => Some(EventType::PayoutExpired),
            PayoutStatus::Reversed => Some(EventType::PayoutReversed),
            PayoutStatus::Ineligible
            | PayoutStatus::Pending
            | PayoutStatus::RequiresCreation
            | PayoutStatus::RequiresFulfillment
            | PayoutStatus::RequiresPayoutMethodData
            | PayoutStatus::RequiresVendorAccountCreation
            | PayoutStatus::RequiresConfirmation => None,
        }
    }
}

impl From<DisputeStatus> for EventType {
    fn from(value: DisputeStatus) -> Self {
        match value {
            DisputeStatus::DisputeOpened => Self::DisputeOpened,
            DisputeStatus::DisputeExpired => Self::DisputeExpired,
            DisputeStatus::DisputeAccepted => Self::DisputeAccepted,
            DisputeStatus::DisputeCancelled => Self::DisputeCancelled,
            DisputeStatus::DisputeChallenged => Self::DisputeChallenged,
            DisputeStatus::DisputeWon => Self::DisputeWon,
            DisputeStatus::DisputeLost => Self::DisputeLost,
        }
    }
}

impl From<MandateStatus> for Option<EventType> {
    fn from(value: MandateStatus) -> Self {
        match value {
            MandateStatus::Active => Some(EventType::MandateActive),
            MandateStatus::Revoked => Some(EventType::MandateRevoked),
            MandateStatus::Inactive | MandateStatus::Pending => None,
        }
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]

    use super::*;

    #[derive(serde::Serialize)]
    struct Alpha2Request {
        #[serde(with = "custom_serde::alpha2_country_code")]
        pub country: Country,
    }

    #[derive(serde::Serialize)]
    struct Alpha3Request {
        #[serde(with = "custom_serde::alpha3_country_code")]
        pub country: Country,
    }

    #[derive(serde::Serialize)]
    struct NumericRequest {
        #[serde(with = "custom_serde::numeric_country_code")]
        pub country: Country,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    struct HyperswitchRequestAlpha2 {
        #[serde(with = "custom_serde::alpha2_country_code")]
        pub country: Country,
    }

    #[derive(serde::Serialize, serde::Deserialize)]
    struct HyperswitchRequestAlpha3 {
        #[serde(with = "custom_serde::alpha3_country_code")]
        pub country: Country,
    }

    #[derive(serde::Serialize, serde::Deserialize, Debug)]
    struct HyperswitchRequestNumeric {
        #[serde(with = "custom_serde::numeric_country_code")]
        pub country: Country,
    }

    #[test]
    fn test_serialize_alpha2() {
        let x_request = Alpha2Request {
            country: Country::India,
        };
        let serialized_country = serde_json::to_string(&x_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"IN"}"#);

        let x_request = Alpha2Request {
            country: Country::MacedoniaTheFormerYugoslavRepublic,
        };
        let serialized_country = serde_json::to_string(&x_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"MK"}"#);

        let x_request = Alpha2Request {
            country: Country::FrenchSouthernTerritories,
        };
        let serialized_country = serde_json::to_string(&x_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"TF"}"#);
    }

    #[test]
    fn test_serialize_alpha3() {
        let y_request = Alpha3Request {
            country: Country::India,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"IND"}"#);

        let y_request = Alpha3Request {
            country: Country::HeardIslandAndMcDonaldIslands,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"HMD"}"#);

        let y_request = Alpha3Request {
            country: Country::Argentina,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":"ARG"}"#);
    }

    #[test]
    fn test_serialize_numeric() {
        let y_request = NumericRequest {
            country: Country::India,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":356}"#);

        let y_request = NumericRequest {
            country: Country::Bermuda,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":60}"#);

        let y_request = NumericRequest {
            country: Country::GuineaBissau,
        };
        let serialized_country = serde_json::to_string(&y_request).unwrap();
        assert_eq!(serialized_country, r#"{"country":624}"#);
    }

    #[test]
    fn test_deserialize_alpha2() {
        let request_str = r#"{"country":"IN"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha2>(request_str).unwrap();
        assert_eq!(request.country, Country::India);

        let request_str = r#"{"country":"GR"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha2>(request_str).unwrap();
        assert_eq!(request.country, Country::Greece);

        let request_str = r#"{"country":"IQ"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha2>(request_str).unwrap();
        assert_eq!(request.country, Country::Iraq);
    }

    #[test]
    fn test_deserialize_alpha3() {
        let request_str = r#"{"country":"IND"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha3>(request_str).unwrap();
        assert_eq!(request.country, Country::India);

        let request_str = r#"{"country":"LVA"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha3>(request_str).unwrap();
        assert_eq!(request.country, Country::Latvia);

        let request_str = r#"{"country":"PNG"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha3>(request_str).unwrap();
        assert_eq!(request.country, Country::PapuaNewGuinea);
    }

    #[test]
    fn test_deserialize_numeric() {
        let request_str = r#"{"country":356}"#;
        let request = serde_json::from_str::<HyperswitchRequestNumeric>(request_str).unwrap();
        assert_eq!(request.country, Country::India);

        let request_str = r#"{"country":239}"#;
        let request = serde_json::from_str::<HyperswitchRequestNumeric>(request_str).unwrap();
        assert_eq!(
            request.country,
            Country::SouthGeorgiaAndTheSouthSandwichIslands
        );

        let request_str = r#"{"country":826}"#;
        let request = serde_json::from_str::<HyperswitchRequestNumeric>(request_str).unwrap();
        assert_eq!(
            request.country,
            Country::UnitedKingdomOfGreatBritainAndNorthernIreland
        );
    }

    #[test]
    fn test_deserialize_and_serialize() {
        // Deserialize the country as alpha2 code
        // Serialize the country as alpha3 code
        let request_str = r#"{"country":"IN"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha2>(request_str).unwrap();
        let alpha3_request = Alpha3Request {
            country: request.country,
        };
        let response = serde_json::to_string::<Alpha3Request>(&alpha3_request).unwrap();
        assert_eq!(response, r#"{"country":"IND"}"#)
    }

    #[test]
    fn test_serialize_and_deserialize() {
        let request_str = r#"{"country":"AX"}"#;
        let request = serde_json::from_str::<HyperswitchRequestAlpha2>(request_str).unwrap();
        let alpha3_request = Alpha3Request {
            country: request.country,
        };
        let response = serde_json::to_string::<Alpha3Request>(&alpha3_request).unwrap();
        assert_eq!(response, r#"{"country":"ALA"}"#);

        let result = serde_json::from_str::<HyperswitchRequestAlpha3>(response.as_str()).unwrap();
        assert_eq!(result.country, Country::AlandIslands);
    }

    #[test]
    fn test_deserialize_invalid_country_code() {
        let request_str = r#"{"country": 123456}"#;
        let result: Result<HyperswitchRequestNumeric, _> =
            serde_json::from_str::<HyperswitchRequestNumeric>(request_str);
        assert!(result.is_err());
    }
}


impl PaymentIntentMetricsBucketIdentifier {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        status: Option<IntentStatus>,
        currency: Option<Currency>,
        profile_id: Option<String>,
        connector: Option<String>,
        auth_type: Option<AuthenticationType>,
        payment_method: Option<String>,
        payment_method_type: Option<String>,
        card_network: Option<String>,
        merchant_id: Option<String>,
        card_last_4: Option<String>,
        card_issuer: Option<String>,
        error_reason: Option<String>,
        normalized_time_range: TimeRange,
    ) -> Self {
        Self {
            status,
            currency,
            profile_id,
            connector,
            auth_type,
            payment_method,
            payment_method_type,
            card_network,
            merchant_id,
            card_last_4,
            card_issuer,
            error_reason,
            time_bucket: normalized_time_range,
            start_time: normalized_time_range.start_time,
        }
    }
}


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


pub struct CoBadgedCardNetworks(pub Vec<CoBadgedCardNetworksInfo>);


/// Get the IP address
    pub fn get_ip_address(&self) -> Option<String> {
        self.online
            .as_ref()
            .and_then(|data| data.ip_address.as_ref().map(|ip| ip.peek().to_owned()))
    }


pub trait RefundDistributionAccumulator {
    type DistributionOutput;

    fn add_distribution_bucket(&mut self, distribution: &RefundDistributionRow);

    fn collect(self) -> Self::DistributionOutput;
}


File: crates/router/src/routes/disputes.rs
 Public functions: 13

use actix_multipart::Multipart;
use actix_web::{web, HttpRequest, HttpResponse};
use api_models::disputes as dispute_models;
use router_env::{instrument, tracing, Flow};

use crate::{core::api_locking, services::authorization::permissions::Permission};
pub mod utils;

use super::app::AppState;
use crate::{
    core::disputes,
    services::{api, authentication as auth},
    types::{api::disputes as dispute_types, domain},
};

#[cfg(feature = "v1")]
/// Disputes - Retrieve Dispute
#[utoipa::path(
    get,
    path = "/disputes/{dispute_id}",
    params(
        ("dispute_id" = String, Path, description = "The identifier for dispute")
    ),
    responses(
        (status = 200, description = "The dispute was retrieved successfully", body = DisputeResponse),
        (status = 404, description = "Dispute does not exist in our records")
    ),
    tag = "Disputes",
    operation_id = "Retrieve a Dispute",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesRetrieve))]
pub async fn retrieve_dispute(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
    json_payload: web::Query<dispute_models::DisputeRetrieveBody>,
) -> HttpResponse {
    let flow = Flow::DisputesRetrieve;
    let payload = dispute_models::DisputeRetrieveRequest {
        dispute_id: path.into_inner(),
        force_sync: json_payload.force_sync,
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::retrieve_dispute(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::DisputesRetrieve))]
pub async fn fetch_disputes(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
    json_payload: web::Query<dispute_types::DisputeFetchQueryData>,
) -> HttpResponse {
    let flow = Flow::DisputesList;
    let connector_id = path.into_inner();
    let payload = json_payload.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::connector_sync_disputes(state, merchant_context, connector_id.clone(), req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Disputes - List Disputes
#[utoipa::path(
    get,
    path = "/disputes/list",
    params(
        ("limit" = Option<i64>, Query, description = "The maximum number of Dispute Objects to include in the response"),
        ("dispute_status" = Option<DisputeStatus>, Query, description = "The status of dispute"),
        ("dispute_stage" = Option<DisputeStage>, Query, description = "The stage of dispute"),
        ("reason" = Option<String>, Query, description = "The reason for dispute"),
        ("connector" = Option<String>, Query, description = "The connector linked to dispute"),
        ("received_time" = Option<PrimitiveDateTime>, Query, description = "The time at which dispute is received"),
        ("received_time.lt" = Option<PrimitiveDateTime>, Query, description = "Time less than the dispute received time"),
        ("received_time.gt" = Option<PrimitiveDateTime>, Query, description = "Time greater than the dispute received time"),
        ("received_time.lte" = Option<PrimitiveDateTime>, Query, description = "Time less than or equals to the dispute received time"),
        ("received_time.gte" = Option<PrimitiveDateTime>, Query, description = "Time greater than or equals to the dispute received time"),
    ),
    responses(
        (status = 200, description = "The dispute list was retrieved successfully", body = Vec<DisputeResponse>),
        (status = 401, description = "Unauthorized request")
    ),
    tag = "Disputes",
    operation_id = "List Disputes",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesList))]
pub async fn retrieve_disputes_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    query: web::Query<dispute_models::DisputeListGetConstraints>,
) -> HttpResponse {
    let flow = Flow::DisputesList;
    let payload = query.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::retrieve_disputes_list(state, merchant_context, None, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - List Disputes for The Given Business Profiles
#[utoipa::path(
    get,
    path = "/disputes/profile/list",
    params(
        ("limit" = Option<i64>, Query, description = "The maximum number of Dispute Objects to include in the response"),
        ("dispute_status" = Option<DisputeStatus>, Query, description = "The status of dispute"),
        ("dispute_stage" = Option<DisputeStage>, Query, description = "The stage of dispute"),
        ("reason" = Option<String>, Query, description = "The reason for dispute"),
        ("connector" = Option<String>, Query, description = "The connector linked to dispute"),
        ("received_time" = Option<PrimitiveDateTime>, Query, description = "The time at which dispute is received"),
        ("received_time.lt" = Option<PrimitiveDateTime>, Query, description = "Time less than the dispute received time"),
        ("received_time.gt" = Option<PrimitiveDateTime>, Query, description = "Time greater than the dispute received time"),
        ("received_time.lte" = Option<PrimitiveDateTime>, Query, description = "Time less than or equals to the dispute received time"),
        ("received_time.gte" = Option<PrimitiveDateTime>, Query, description = "Time greater than or equals to the dispute received time"),
    ),
    responses(
        (status = 200, description = "The dispute list was retrieved successfully", body = Vec<DisputeResponse>),
        (status = 401, description = "Unauthorized request")
    ),
    tag = "Disputes",
    operation_id = "List Disputes for The given Business Profiles",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesList))]
pub async fn retrieve_disputes_list_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    payload: web::Query<dispute_models::DisputeListGetConstraints>,
) -> HttpResponse {
    let flow = Flow::DisputesList;
    let payload = payload.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::retrieve_disputes_list(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - Disputes Filters
#[utoipa::path(
    get,
    path = "/disputes/filter",
    responses(
        (status = 200, description = "List of filters", body = DisputeListFilters),
    ),
    tag = "Disputes",
    operation_id = "List all filters for disputes",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesFilters))]
pub async fn get_disputes_filters(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
    let flow = Flow::DisputesFilters;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        (),
        |state, auth: auth::AuthenticationData, _, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::get_filters_for_disputes(state, merchant_context, None)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - Disputes Filters Profile
#[utoipa::path(
    get,
    path = "/disputes/profile/filter",
    responses(
        (status = 200, description = "List of filters", body = DisputeListFilters),
    ),
    tag = "Disputes",
    operation_id = "List all filters for disputes",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesFilters))]
pub async fn get_disputes_filters_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
) -> HttpResponse {
    let flow = Flow::DisputesFilters;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        (),
        |state, auth: auth::AuthenticationData, _, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::get_filters_for_disputes(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - Accept Dispute
#[utoipa::path(
    get,
    path = "/disputes/accept/{dispute_id}",
    params(
        ("dispute_id" = String, Path, description = "The identifier for dispute")
    ),
    responses(
        (status = 200, description = "The dispute was accepted successfully", body = DisputeResponse),
        (status = 404, description = "Dispute does not exist in our records")
    ),
    tag = "Disputes",
    operation_id = "Accept a Dispute",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesRetrieve))]
pub async fn accept_dispute(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let flow = Flow::DisputesRetrieve;
    let dispute_id = dispute_types::DisputeId {
        dispute_id: path.into_inner(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        dispute_id,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::accept_dispute(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - Submit Dispute Evidence
#[utoipa::path(
    post,
    path = "/disputes/evidence",
    request_body=AcceptDisputeRequestData,
    responses(
        (status = 200, description = "The dispute evidence submitted successfully", body = AcceptDisputeResponse),
        (status = 404, description = "Dispute does not exist in our records")
    ),
    tag = "Disputes",
    operation_id = "Submit Dispute Evidence",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DisputesEvidenceSubmit))]
pub async fn submit_dispute_evidence(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<dispute_models::SubmitEvidenceRequest>,
) -> HttpResponse {
    let flow = Flow::DisputesEvidenceSubmit;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::submit_evidence(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
#[cfg(feature = "v1")]
/// Disputes - Attach Evidence to Dispute
///
/// To attach an evidence file to dispute
#[utoipa::path(
    put,
    path = "/disputes/evidence",
    request_body=MultipartRequestWithFile,
    responses(
        (status = 200, description = "Evidence attached to dispute", body = CreateFileResponse),
        (status = 400, description = "Bad Request")
    ),
    tag = "Disputes",
    operation_id = "Attach Evidence to Dispute",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::AttachDisputeEvidence))]
pub async fn attach_dispute_evidence(
    state: web::Data<AppState>,
    req: HttpRequest,
    payload: Multipart,
) -> HttpResponse {
    let flow = Flow::AttachDisputeEvidence;
    //Get attach_evidence_request from the multipart request
    let attach_evidence_request_result = utils::get_attach_evidence_request(payload).await;
    let attach_evidence_request = match attach_evidence_request_result {
        Ok(valid_request) => valid_request,
        Err(err) => return api::log_and_return_error_response(err),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        attach_evidence_request,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::attach_evidence(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
/// Disputes - Retrieve Dispute
#[utoipa::path(
    get,
    path = "/disputes/evidence/{dispute_id}",
    params(
        ("dispute_id" = String, Path, description = "The identifier for dispute")
    ),
    responses(
        (status = 200, description = "The dispute evidence was retrieved successfully", body = DisputeResponse),
        (status = 404, description = "Dispute does not exist in our records")
    ),
    tag = "Disputes",
    operation_id = "Retrieve a Dispute Evidence",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::RetrieveDisputeEvidence))]
pub async fn retrieve_dispute_evidence(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let flow = Flow::RetrieveDisputeEvidence;
    let dispute_id = dispute_types::DisputeId {
        dispute_id: path.into_inner(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        dispute_id,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::retrieve_dispute_evidence(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Disputes - Delete Evidence attached to a Dispute
///
/// To delete an evidence file attached to a dispute
#[utoipa::path(
    put,
    path = "/disputes/evidence",
    request_body=DeleteEvidenceRequest,
    responses(
        (status = 200, description = "Evidence deleted from a dispute"),
        (status = 400, description = "Bad Request")
    ),
    tag = "Disputes",
    operation_id = "Delete Evidence attached to a Dispute",
    security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::DeleteDisputeEvidence))]
pub async fn delete_dispute_evidence(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<dispute_models::DeleteEvidenceRequest>,
) -> HttpResponse {
    let flow = Flow::DeleteDisputeEvidence;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::delete_evidence(state, merchant_context, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::DisputesAggregate))]
pub async fn get_disputes_aggregate(
    state: web::Data<AppState>,
    req: HttpRequest,
    query_param: web::Query<common_utils::types::TimeRange>,
) -> HttpResponse {
    let flow = Flow::DisputesAggregate;
    let query_param = query_param.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        query_param,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::get_aggregates_for_disputes(state, merchant_context, None, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::DisputesAggregate))]
pub async fn get_disputes_aggregate_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    query_param: web::Query<common_utils::types::TimeRange>,
) -> HttpResponse {
    let flow = Flow::DisputesAggregate;
    let query_param = query_param.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        query_param,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::get_aggregates_for_disputes(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentCapture for Silverflow {}

impl api::PaymentCapture for for Silverflow


pub struct ErrorResponse {
    errors: Vec<ErrorDetails>,
}


File: crates/diesel_models/src/process_tracker.rs
 Public functions: 2
 Public structs: 3

pub use common_enums::{enums::ProcessTrackerRunner, ApiVersion};
use common_utils::ext_traits::Encode;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{enums as storage_enums, errors, schema::process_tracker, StorageResult};

#[derive(
    Clone,
    Debug,
    Eq,
    PartialEq,
    Deserialize,
    Identifiable,
    Queryable,
    Selectable,
    Serialize,
    router_derive::DebugAsDisplay,
)]
#[diesel(table_name = process_tracker, check_for_backend(diesel::pg::Pg))]
pub struct ProcessTracker {
    pub id: String,
    pub name: Option<String>,
    #[diesel(deserialize_as = super::DieselArray<String>)]
    pub tag: Vec<String>,
    pub runner: Option<String>,
    pub retry_count: i32,
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pub schedule_time: Option<PrimitiveDateTime>,
    pub rule: String,
    pub tracking_data: serde_json::Value,
    pub business_status: String,
    pub status: storage_enums::ProcessTrackerStatus,
    #[diesel(deserialize_as = super::DieselArray<String>)]
    pub event: Vec<String>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub updated_at: PrimitiveDateTime,
    pub version: ApiVersion,
}

impl ProcessTracker {
    #[inline(always)]
    pub fn is_valid_business_status(&self, valid_statuses: &[&str]) -> bool {
        valid_statuses.iter().any(|&x| x == self.business_status)
    }
}

#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
#[diesel(table_name = process_tracker)]
pub struct ProcessTrackerNew {
    pub id: String,
    pub name: Option<String>,
    pub tag: Vec<String>,
    pub runner: Option<String>,
    pub retry_count: i32,
    pub schedule_time: Option<PrimitiveDateTime>,
    pub rule: String,
    pub tracking_data: serde_json::Value,
    pub business_status: String,
    pub status: storage_enums::ProcessTrackerStatus,
    pub event: Vec<String>,
    pub created_at: PrimitiveDateTime,
    pub updated_at: PrimitiveDateTime,
    pub version: ApiVersion,
}

impl ProcessTrackerNew {
    #[allow(clippy::too_many_arguments)]
    pub fn new<T>(
        process_tracker_id: impl Into<String>,
        task: impl Into<String>,
        runner: ProcessTrackerRunner,
        tag: impl IntoIterator<Item = impl Into<String>>,
        tracking_data: T,
        retry_count: Option<i32>,
        schedule_time: PrimitiveDateTime,
        api_version: ApiVersion,
    ) -> StorageResult<Self>
    where
        T: Serialize + std::fmt::Debug,
    {
        let current_time = common_utils::date_time::now();
        Ok(Self {
            id: process_tracker_id.into(),
            name: Some(task.into()),
            tag: tag.into_iter().map(Into::into).collect(),
            runner: Some(runner.to_string()),
            retry_count: retry_count.unwrap_or(0),
            schedule_time: Some(schedule_time),
            rule: String::new(),
            tracking_data: tracking_data
                .encode_to_value()
                .change_context(errors::DatabaseError::Others)
                .attach_printable("Failed to serialize process tracker tracking data")?,
            business_status: String::from(business_status::PENDING),
            status: storage_enums::ProcessTrackerStatus::New,
            event: vec![],
            created_at: current_time,
            updated_at: current_time,
            version: api_version,
        })
    }
}

#[derive(Debug)]
pub enum ProcessTrackerUpdate {
    Update {
        name: Option<String>,
        retry_count: Option<i32>,
        schedule_time: Option<PrimitiveDateTime>,
        tracking_data: Option<serde_json::Value>,
        business_status: Option<String>,
        status: Option<storage_enums::ProcessTrackerStatus>,
        updated_at: Option<PrimitiveDateTime>,
    },
    StatusUpdate {
        status: storage_enums::ProcessTrackerStatus,
        business_status: Option<String>,
    },
    StatusRetryUpdate {
        status: storage_enums::ProcessTrackerStatus,
        retry_count: i32,
        schedule_time: PrimitiveDateTime,
    },
}

#[derive(Debug, Clone, AsChangeset, router_derive::DebugAsDisplay)]
#[diesel(table_name = process_tracker)]
pub struct ProcessTrackerUpdateInternal {
    name: Option<String>,
    retry_count: Option<i32>,
    schedule_time: Option<PrimitiveDateTime>,
    tracking_data: Option<serde_json::Value>,
    business_status: Option<String>,
    status: Option<storage_enums::ProcessTrackerStatus>,
    updated_at: Option<PrimitiveDateTime>,
}

impl Default for ProcessTrackerUpdateInternal {
    fn default() -> Self {
        Self {
            name: Option::default(),
            retry_count: Option::default(),
            schedule_time: Option::default(),
            tracking_data: Option::default(),
            business_status: Option::default(),
            status: Option::default(),
            updated_at: Some(common_utils::date_time::now()),
        }
    }
}

impl From<ProcessTrackerUpdate> for ProcessTrackerUpdateInternal {
    fn from(process_tracker_update: ProcessTrackerUpdate) -> Self {
        match process_tracker_update {
            ProcessTrackerUpdate::Update {
                name,
                retry_count,
                schedule_time,
                tracking_data,
                business_status,
                status,
                updated_at,
            } => Self {
                name,
                retry_count,
                schedule_time,
                tracking_data,
                business_status,
                status,
                updated_at,
            },
            ProcessTrackerUpdate::StatusUpdate {
                status,
                business_status,
            } => Self {
                status: Some(status),
                business_status,
                ..Default::default()
            },
            ProcessTrackerUpdate::StatusRetryUpdate {
                status,
                retry_count,
                schedule_time,
            } => Self {
                status: Some(status),
                retry_count: Some(retry_count),
                schedule_time: Some(schedule_time),
                ..Default::default()
            },
        }
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]
    use common_utils::ext_traits::StringExt;

    use super::ProcessTrackerRunner;

    #[test]
    fn test_enum_to_string() {
        let string_format = "PAYMENTS_SYNC_WORKFLOW".to_string();
        let enum_format: ProcessTrackerRunner =
            string_format.parse_enum("ProcessTrackerRunner").unwrap();
        assert_eq!(enum_format, ProcessTrackerRunner::PaymentsSyncWorkflow);
    }
}

pub mod business_status {
    /// Indicates that an irrecoverable error occurred during the workflow execution.
    pub const GLOBAL_FAILURE: &str = "GLOBAL_FAILURE";

    /// Task successfully completed by consumer.
    /// A task that reaches this status should not be retried (rescheduled for execution) later.
    pub const COMPLETED_BY_PT: &str = "COMPLETED_BY_PT";

    /// An error occurred during the workflow execution which prevents further execution and
    /// retries.
    /// A task that reaches this status should not be retried (rescheduled for execution) later.
    pub const FAILURE: &str = "FAILURE";

    /// The resource associated with the task was removed, due to which further retries can/should
    /// not be done.
    pub const REVOKED: &str = "Revoked";

    /// The task was executed for the maximum possible number of times without a successful outcome.
    /// A task that reaches this status should not be retried (rescheduled for execution) later.
    pub const RETRIES_EXCEEDED: &str = "RETRIES_EXCEEDED";

    /// The outgoing webhook was successfully delivered in the initial attempt.
    /// Further retries of the task are not required.
    pub const INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL: &str = "INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL";

    /// Indicates that an error occurred during the workflow execution.
    /// This status is typically set by the workflow error handler.
    /// A task that reaches this status should not be retried (rescheduled for execution) later.
    pub const GLOBAL_ERROR: &str = "GLOBAL_ERROR";

    /// The resource associated with the task has been significantly modified since the task was
    /// created, due to which further retries of the current task are not required.
    /// A task that reaches this status should not be retried (rescheduled for execution) later.
    pub const RESOURCE_STATUS_MISMATCH: &str = "RESOURCE_STATUS_MISMATCH";

    /// Business status set for newly created tasks.
    pub const PENDING: &str = "Pending";

    /// For the PCR Workflow
    ///
    /// This status indicates the completion of a execute task
    pub const EXECUTE_WORKFLOW_COMPLETE: &str = "COMPLETED_EXECUTE_TASK";

    /// This status indicates the failure of a execute task
    pub const EXECUTE_WORKFLOW_FAILURE: &str = "FAILED_EXECUTE_TASK";

    /// This status indicates that the execute task was completed to trigger the psync task
    pub const EXECUTE_WORKFLOW_COMPLETE_FOR_PSYNC: &str = "COMPLETED_EXECUTE_TASK_TO_TRIGGER_PSYNC";

    /// This status indicates that the execute task was completed to trigger the review task
    pub const EXECUTE_WORKFLOW_COMPLETE_FOR_REVIEW: &str =
        "COMPLETED_EXECUTE_TASK_TO_TRIGGER_REVIEW";

    /// This status indicates that the requeue was triggered for execute task
    pub const EXECUTE_WORKFLOW_REQUEUE: &str = "TRIGGER_REQUEUE_FOR_EXECUTE_WORKFLOW";

    /// This status indicates the completion of a psync task
    pub const PSYNC_WORKFLOW_COMPLETE: &str = "COMPLETED_PSYNC_TASK";

    /// This status indicates that the psync task was completed to trigger the review task
    pub const PSYNC_WORKFLOW_COMPLETE_FOR_REVIEW: &str = "COMPLETED_PSYNC_TASK_TO_TRIGGER_REVIEW";

    /// This status indicates that the requeue was triggered for psync task
    pub const PSYNC_WORKFLOW_REQUEUE: &str = "TRIGGER_REQUEUE_FOR_PSYNC_WORKFLOW";

    /// This status indicates the completion of a review task
    pub const REVIEW_WORKFLOW_COMPLETE: &str = "COMPLETED_REVIEW_TASK";

    /// For the CALCULATE_WORKFLOW
    ///
    /// This status indicates an invoice is queued
    pub const CALCULATE_WORKFLOW_QUEUED: &str = "CALCULATE_WORKFLOW_QUEUED";

    /// This status indicates an invoice has been declined due to hard decline
    pub const CALCULATE_WORKFLOW_FINISH: &str = "FAILED_DUE_TO_HARD_DECLINE_ERROR";

    /// This status indicates that the invoice is scheduled with the best available token
    pub const CALCULATE_WORKFLOW_SCHEDULED: &str = "CALCULATE_WORKFLOW_SCHEDULED";

    /// This status indicates the invoice is in payment sync state
    pub const CALCULATE_WORKFLOW_PROCESSING: &str = "CALCULATE_WORKFLOW_PROCESSING";

    /// This status indicates the workflow has completed successfully when the invoice is paid
    pub const CALCULATE_WORKFLOW_COMPLETE: &str = "CALCULATE_WORKFLOW_COMPLETE";
}


impl api::Refund for Mifinity {}


impl api::MandateSetup for Bankofamerica {}

impl api::MandateSetup for for Bankofamerica


impl behaviour::Conversion for PaymentIntent {
    type DstType = DieselPaymentIntent;
    type NewDstType = DieselPaymentIntentNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(DieselPaymentIntent {
            payment_id: self.payment_id,
            merchant_id: self.merchant_id,
            status: self.status,
            amount: self.amount,
            currency: self.currency,
            amount_captured: self.amount_captured,
            customer_id: self.customer_id,
            description: self.description,
            return_url: None, // deprecated
            metadata: self.metadata,
            connector_id: self.connector_id,
            shipping_address_id: self.shipping_address_id,
            billing_address_id: self.billing_address_id,
            statement_descriptor_name: self.statement_descriptor_name,
            statement_descriptor_suffix: self.statement_descriptor_suffix,
            created_at: self.created_at,
            modified_at: self.modified_at,
            last_synced: self.last_synced,
            setup_future_usage: self.setup_future_usage,
            off_session: self.off_session,
            client_secret: self.client_secret,
            active_attempt_id: self.active_attempt.get_id(),
            business_country: self.business_country,
            business_label: self.business_label,
            order_details: self.order_details,
            allowed_payment_method_types: self.allowed_payment_method_types,
            connector_metadata: self.connector_metadata,
            feature_metadata: self.feature_metadata,
            attempt_count: self.attempt_count,
            profile_id: self.profile_id,
            merchant_decision: self.merchant_decision,
            payment_link_id: self.payment_link_id,
            payment_confirm_source: self.payment_confirm_source,
            updated_by: self.updated_by,
            surcharge_applicable: self.surcharge_applicable,
            request_incremental_authorization: self.request_incremental_authorization,
            incremental_authorization_allowed: self.incremental_authorization_allowed,
            authorization_count: self.authorization_count,
            fingerprint_id: self.fingerprint_id,
            session_expiry: self.session_expiry,
            request_external_three_ds_authentication: self.request_external_three_ds_authentication,
            charges: None,
            split_payments: self.split_payments,
            frm_metadata: self.frm_metadata,
            customer_details: self.customer_details.map(Encryption::from),
            billing_details: self.billing_details.map(Encryption::from),
            merchant_order_reference_id: self.merchant_order_reference_id,
            shipping_details: self.shipping_details.map(Encryption::from),
            is_payment_processor_token_flow: self.is_payment_processor_token_flow,
            organization_id: self.organization_id,
            shipping_cost: self.shipping_cost,
            tax_details: self.tax_details,
            skip_external_tax_calculation: self.skip_external_tax_calculation,
            request_extended_authorization: self.request_extended_authorization,
            psd2_sca_exemption_type: self.psd2_sca_exemption_type,
            platform_merchant_id: None,
            processor_merchant_id: Some(self.processor_merchant_id),
            created_by: self.created_by.map(|cb| cb.to_string()),
            force_3ds_challenge: self.force_3ds_challenge,
            force_3ds_challenge_trigger: self.force_3ds_challenge_trigger,
            is_iframe_redirection_enabled: self.is_iframe_redirection_enabled,
            extended_return_url: self.return_url,
            is_payment_id_from_merchant: self.is_payment_id_from_merchant,
            payment_channel: self.payment_channel,
            tax_status: self.tax_status,
            discount_amount: self.discount_amount,
            order_date: self.order_date,
            shipping_amount_tax: self.shipping_amount_tax,
            duty_amount: self.duty_amount,
            enable_partial_authorization: self.enable_partial_authorization,
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        storage_model: Self::DstType,
        key: &Secret<Vec<u8>>,
        key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        async {
            let decrypted_data = crypto_operation(
                state,
                type_name!(Self::DstType),
                CryptoOperation::BatchDecrypt(super::EncryptedPaymentIntent::to_encryptable(
                    super::EncryptedPaymentIntent {
                        billing_details: storage_model.billing_details,
                        shipping_details: storage_model.shipping_details,
                        customer_details: storage_model.customer_details,
                    },
                )),
                key_manager_identifier,
                key.peek(),
            )
            .await
            .and_then(|val| val.try_into_batchoperation())?;

            let data = super::EncryptedPaymentIntent::from_encryptable(decrypted_data)
                .change_context(common_utils::errors::CryptoError::DecodingFailed)
                .attach_printable("Invalid batch operation data")?;

            Ok::<Self, error_stack::Report<common_utils::errors::CryptoError>>(Self {
                payment_id: storage_model.payment_id,
                merchant_id: storage_model.merchant_id.clone(),
                status: storage_model.status,
                amount: storage_model.amount,
                currency: storage_model.currency,
                amount_captured: storage_model.amount_captured,
                customer_id: storage_model.customer_id,
                description: storage_model.description,
                return_url: storage_model
                    .extended_return_url
                    .or(storage_model.return_url), // fallback to legacy
                metadata: storage_model.metadata,
                connector_id: storage_model.connector_id,
                shipping_address_id: storage_model.shipping_address_id,
                billing_address_id: storage_model.billing_address_id,
                statement_descriptor_name: storage_model.statement_descriptor_name,
                statement_descriptor_suffix: storage_model.statement_descriptor_suffix,
                created_at: storage_model.created_at,
                modified_at: storage_model.modified_at,
                last_synced: storage_model.last_synced,
                setup_future_usage: storage_model.setup_future_usage,
                off_session: storage_model.off_session,
                client_secret: storage_model.client_secret,
                active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id),
                business_country: storage_model.business_country,
                business_label: storage_model.business_label,
                order_details: storage_model.order_details,
                allowed_payment_method_types: storage_model.allowed_payment_method_types,
                connector_metadata: storage_model.connector_metadata,
                feature_metadata: storage_model.feature_metadata,
                attempt_count: storage_model.attempt_count,
                profile_id: storage_model.profile_id,
                merchant_decision: storage_model.merchant_decision,
                payment_link_id: storage_model.payment_link_id,
                payment_confirm_source: storage_model.payment_confirm_source,
                updated_by: storage_model.updated_by,
                surcharge_applicable: storage_model.surcharge_applicable,
                request_incremental_authorization: storage_model.request_incremental_authorization,
                incremental_authorization_allowed: storage_model.incremental_authorization_allowed,
                authorization_count: storage_model.authorization_count,
                fingerprint_id: storage_model.fingerprint_id,
                session_expiry: storage_model.session_expiry,
                request_external_three_ds_authentication: storage_model
                    .request_external_three_ds_authentication,
                split_payments: storage_model.split_payments,
                frm_metadata: storage_model.frm_metadata,
                shipping_cost: storage_model.shipping_cost,
                tax_details: storage_model.tax_details,
                customer_details: data.customer_details,
                billing_details: data.billing_details,
                merchant_order_reference_id: storage_model.merchant_order_reference_id,
                shipping_details: data.shipping_details,
                is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow,
                organization_id: storage_model.organization_id,
                skip_external_tax_calculation: storage_model.skip_external_tax_calculation,
                request_extended_authorization: storage_model.request_extended_authorization,
                psd2_sca_exemption_type: storage_model.psd2_sca_exemption_type,
                processor_merchant_id: storage_model
                    .processor_merchant_id
                    .unwrap_or(storage_model.merchant_id),
                created_by: storage_model
                    .created_by
                    .and_then(|created_by| created_by.parse::<CreatedBy>().ok()),
                force_3ds_challenge: storage_model.force_3ds_challenge,
                force_3ds_challenge_trigger: storage_model.force_3ds_challenge_trigger,
                is_iframe_redirection_enabled: storage_model.is_iframe_redirection_enabled,
                is_payment_id_from_merchant: storage_model.is_payment_id_from_merchant,
                payment_channel: storage_model.payment_channel,
                tax_status: storage_model.tax_status,
                discount_amount: storage_model.discount_amount,
                shipping_amount_tax: storage_model.shipping_amount_tax,
                duty_amount: storage_model.duty_amount,
                order_date: storage_model.order_date,
                enable_partial_authorization: storage_model.enable_partial_authorization,
            })
        }
        .await
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting payment intent".to_string(),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(DieselPaymentIntentNew {
            payment_id: self.payment_id,
            merchant_id: self.merchant_id,
            status: self.status,
            amount: self.amount,
            currency: self.currency,
            amount_captured: self.amount_captured,
            customer_id: self.customer_id,
            description: self.description,
            return_url: None, // deprecated
            metadata: self.metadata,
            connector_id: self.connector_id,
            shipping_address_id: self.shipping_address_id,
            billing_address_id: self.billing_address_id,
            statement_descriptor_name: self.statement_descriptor_name,
            statement_descriptor_suffix: self.statement_descriptor_suffix,
            created_at: self.created_at,
            modified_at: self.modified_at,
            last_synced: self.last_synced,
            setup_future_usage: self.setup_future_usage,
            off_session: self.off_session,
            client_secret: self.client_secret,
            active_attempt_id: self.active_attempt.get_id(),
            business_country: self.business_country,
            business_label: self.business_label,
            order_details: self.order_details,
            allowed_payment_method_types: self.allowed_payment_method_types,
            connector_metadata: self.connector_metadata,
            feature_metadata: self.feature_metadata,
            attempt_count: self.attempt_count,
            profile_id: self.profile_id,
            merchant_decision: self.merchant_decision,
            payment_link_id: self.payment_link_id,
            payment_confirm_source: self.payment_confirm_source,
            updated_by: self.updated_by,
            surcharge_applicable: self.surcharge_applicable,
            request_incremental_authorization: self.request_incremental_authorization,
            incremental_authorization_allowed: self.incremental_authorization_allowed,
            authorization_count: self.authorization_count,
            fingerprint_id: self.fingerprint_id,
            session_expiry: self.session_expiry,
            request_external_three_ds_authentication: self.request_external_three_ds_authentication,
            charges: None,
            split_payments: self.split_payments,
            frm_metadata: self.frm_metadata,
            customer_details: self.customer_details.map(Encryption::from),
            billing_details: self.billing_details.map(Encryption::from),
            merchant_order_reference_id: self.merchant_order_reference_id,
            shipping_details: self.shipping_details.map(Encryption::from),
            is_payment_processor_token_flow: self.is_payment_processor_token_flow,
            organization_id: self.organization_id,
            shipping_cost: self.shipping_cost,
            tax_details: self.tax_details,
            skip_external_tax_calculation: self.skip_external_tax_calculation,
            request_extended_authorization: self.request_extended_authorization,
            psd2_sca_exemption_type: self.psd2_sca_exemption_type,
            platform_merchant_id: None,
            processor_merchant_id: Some(self.processor_merchant_id),
            created_by: self.created_by.map(|cb| cb.to_string()),
            force_3ds_challenge: self.force_3ds_challenge,
            force_3ds_challenge_trigger: self.force_3ds_challenge_trigger,
            is_iframe_redirection_enabled: self.is_iframe_redirection_enabled,
            extended_return_url: self.return_url,
            is_payment_id_from_merchant: self.is_payment_id_from_merchant,
            payment_channel: self.payment_channel,
            tax_status: self.tax_status,
            discount_amount: self.discount_amount,
            order_date: self.order_date,
            shipping_amount_tax: self.shipping_amount_tax,
            duty_amount: self.duty_amount,
            enable_partial_authorization: self.enable_partial_authorization,
        })
    }
}

impl behaviour::Conversion for for PaymentIntent


File: crates/external_services/src/email/ses.rs
 Public functions: 3
 Public structs: 2

use std::time::{Duration, SystemTime};

use aws_sdk_sesv2::{
    config::Region,
    operation::send_email::SendEmailError,
    types::{Body, Content, Destination, EmailContent, Message},
    Client,
};
use aws_sdk_sts::config::Credentials;
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
use common_utils::{errors::CustomResult, pii};
use error_stack::{report, ResultExt};
use hyper::Uri;
use masking::PeekInterface;
use router_env::logger;

use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, IntermediateString};

/// Client for AWS SES operation
#[derive(Debug, Clone)]
pub struct AwsSes {
    sender: String,
    ses_config: SESConfig,
    settings: EmailSettings,
}

/// Struct that contains the AWS ses specific configs required to construct an SES email client
#[derive(Debug, Clone, Default, serde::Deserialize)]
pub struct SESConfig {
    /// The arn of email role
    pub email_role_arn: String,

    /// The name of sts_session role
    pub sts_role_session_name: String,
}

impl SESConfig {
    /// Validation for the SES client specific configs
    pub fn validate(&self) -> Result<(), &'static str> {
        use common_utils::{ext_traits::ConfigExt, fp_utils::when};

        when(self.email_role_arn.is_default_or_empty(), || {
            Err("email.aws_ses.email_role_arn must not be empty")
        })?;

        when(self.sts_role_session_name.is_default_or_empty(), || {
            Err("email.aws_ses.sts_role_session_name must not be empty")
        })
    }
}

/// Errors that could occur during SES operations.
#[derive(Debug, thiserror::Error)]
pub enum AwsSesError {
    /// An error occurred in the SDK while sending email.
    #[error("Failed to Send Email {0:?}")]
    SendingFailure(Box<aws_sdk_sesv2::error::SdkError<SendEmailError>>),

    /// Configuration variable is missing to construct the email client
    #[error("Missing configuration variable {0}")]
    MissingConfigurationVariable(&'static str),

    /// Failed to assume the given STS role
    #[error("Failed to STS assume role: Role ARN: {role_arn}, Session name: {session_name}, Region: {region}")]
    AssumeRoleFailure {
        /// Aws region
        region: String,

        /// arn of email role
        role_arn: String,

        /// The name of sts_session role
        session_name: String,
    },

    /// Temporary credentials are missing
    #[error("Assumed role does not contain credentials for role user: {0:?}")]
    TemporaryCredentialsMissing(String),

    /// The proxy Connector cannot be built
    #[error("The proxy build cannot be built")]
    BuildingProxyConnectorFailed,
}

impl AwsSes {
    /// Constructs a new AwsSes client
    pub async fn create(
        conf: &EmailSettings,
        ses_config: &SESConfig,
        proxy_url: Option<impl AsRef<str>>,
    ) -> Self {
        // Build the client initially which will help us know if the email configuration is correct
        Self::create_client(conf, ses_config, proxy_url)
            .await
            .map_err(|error| logger::error!(?error, "Failed to initialize SES Client"))
            .ok();

        Self {
            sender: conf.sender_email.clone(),
            ses_config: ses_config.clone(),
            settings: conf.clone(),
        }
    }

    /// A helper function to create ses client
    pub async fn create_client(
        conf: &EmailSettings,
        ses_config: &SESConfig,
        proxy_url: Option<impl AsRef<str>>,
    ) -> CustomResult<Client, AwsSesError> {
        let sts_config = Self::get_shared_config(conf.aws_region.to_owned(), proxy_url.as_ref())?
            .load()
            .await;

        let role = aws_sdk_sts::Client::new(&sts_config)
            .assume_role()
            .role_arn(&ses_config.email_role_arn)
            .role_session_name(&ses_config.sts_role_session_name)
            .send()
            .await
            .change_context(AwsSesError::AssumeRoleFailure {
                region: conf.aws_region.to_owned(),
                role_arn: ses_config.email_role_arn.to_owned(),
                session_name: ses_config.sts_role_session_name.to_owned(),
            })?;

        let creds = role.credentials().ok_or(
            report!(AwsSesError::TemporaryCredentialsMissing(format!(
                "{role:?}"
            )))
            .attach_printable("Credentials object not available"),
        )?;

        let credentials = Credentials::new(
            creds.access_key_id(),
            creds.secret_access_key(),
            Some(creds.session_token().to_owned()),
            u64::try_from(creds.expiration().as_nanos())
                .ok()
                .map(Duration::from_nanos)
                .and_then(|val| SystemTime::UNIX_EPOCH.checked_add(val)),
            "custom_provider",
        );

        logger::debug!(
            "Obtained SES temporary credentials with expiry {:?}",
            credentials.expiry()
        );

        let ses_config = Self::get_shared_config(conf.aws_region.to_owned(), proxy_url)?
            .credentials_provider(credentials)
            .load()
            .await;

        Ok(Client::new(&ses_config))
    }

    fn get_shared_config(
        region: String,
        proxy_url: Option<impl AsRef<str>>,
    ) -> CustomResult<aws_config::ConfigLoader, AwsSesError> {
        let region_provider = Region::new(region);
        let mut config = aws_config::from_env().region(region_provider);

        if let Some(proxy_url) = proxy_url {
            let proxy_connector = Self::get_proxy_connector(proxy_url)?;
            let http_client = HyperClientBuilder::new().build(proxy_connector);
            config = config.http_client(http_client);
        };
        Ok(config)
    }

    fn get_proxy_connector(
        proxy_url: impl AsRef<str>,
    ) -> CustomResult<hyper_proxy::ProxyConnector<hyper::client::HttpConnector>, AwsSesError> {
        let proxy_uri = proxy_url
            .as_ref()
            .parse::<Uri>()
            .attach_printable("Unable to parse the proxy url {proxy_url}")
            .change_context(AwsSesError::BuildingProxyConnectorFailed)?;

        let proxy = hyper_proxy::Proxy::new(hyper_proxy::Intercept::All, proxy_uri);

        hyper_proxy::ProxyConnector::from_proxy(hyper::client::HttpConnector::new(), proxy)
            .change_context(AwsSesError::BuildingProxyConnectorFailed)
    }
}

#[async_trait::async_trait]
impl EmailClient for AwsSes {
    type RichText = Body;

    fn convert_to_rich_text(
        &self,
        intermediate_string: IntermediateString,
    ) -> CustomResult<Self::RichText, EmailError> {
        let email_body = Body::builder()
            .html(
                Content::builder()
                    .data(intermediate_string.into_inner())
                    .charset("UTF-8")
                    .build()
                    .change_context(EmailError::ContentBuildFailure)?,
            )
            .build();

        Ok(email_body)
    }

    async fn send_email(
        &self,
        recipient: pii::Email,
        subject: String,
        body: Self::RichText,
        proxy_url: Option<&String>,
    ) -> EmailResult<()> {
        // Not using the same email client which was created at startup as the role session would expire
        // Create a client every time when the email is being sent
        let email_client = Self::create_client(&self.settings, &self.ses_config, proxy_url)
            .await
            .change_context(EmailError::ClientBuildingFailure)?;

        email_client
            .send_email()
            .from_email_address(self.sender.to_owned())
            .destination(
                Destination::builder()
                    .to_addresses(recipient.peek())
                    .build(),
            )
            .content(
                EmailContent::builder()
                    .simple(
                        Message::builder()
                            .subject(
                                Content::builder()
                                    .data(subject)
                                    .build()
                                    .change_context(EmailError::ContentBuildFailure)?,
                            )
                            .body(body)
                            .build(),
                    )
                    .build(),
            )
            .send()
            .await
            .map_err(|e| AwsSesError::SendingFailure(Box::new(e)))
            .change_context(EmailError::EmailSendingFailure)?;

        Ok(())
    }
}


pub struct FlexitiPurchase {
    status: FlexitiPurchaseStatus,
}


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