pub async fn migrate_payment_method(
    state: &state::PaymentMethodsState,
    req: pm_api::PaymentMethodMigrate,
    merchant_id: &id_type::MerchantId,
    merchant_context: &merchant_context::MerchantContext,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodMigrateResponse>, errors::ApiErrorResponse>
{
    let mut req = req;
    let card_details = &req.card.get_required_value("card")?;

    let card_number_validation_result =
        cards::CardNumber::from_str(card_details.card_number.peek());

    let card_bin_details = populate_bin_details_for_masked_card(
        card_details,
        &*state.store,
        req.payment_method_type.as_ref(),
    )
    .await?;

    req.card = Some(api_models::payment_methods::MigrateCardDetail {
        card_issuing_country: card_bin_details.issuer_country.clone(),
        card_network: card_bin_details.card_network.clone(),
        card_issuer: card_bin_details.card_issuer.clone(),
        card_type: card_bin_details.card_type.clone(),
        ..card_details.clone()
    });

    if let Some(connector_mandate_details) = &req.connector_mandate_details {
        controller
            .validate_merchant_connector_ids_in_connector_mandate_details(
                merchant_context.get_merchant_key_store(),
                connector_mandate_details,
                merchant_id,
                card_bin_details.card_network.clone(),
            )
            .await?;
    };

    let should_require_connector_mandate_details = req.network_token.is_none();

    let mut migration_status = migration::RecordMigrationStatusBuilder::new();

    let resp = match card_number_validation_result {
        Ok(card_number) => {
            let payment_method_create_request =
                pm_api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
                    card_number,
                    &req,
                );

            logger::debug!("Storing the card in locker and migrating the payment method");
            get_client_secret_or_add_payment_method_for_migration(
                state,
                payment_method_create_request,
                merchant_context,
                &mut migration_status,
                controller,
            )
            .await?
        }
        Err(card_validation_error) => {
            logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}");
            skip_locker_call_and_migrate_payment_method(
                state,
                &req,
                merchant_id.to_owned(),
                merchant_context,
                card_bin_details.clone(),
                should_require_connector_mandate_details,
                &mut migration_status,
                controller,
            )
            .await?
        }
    };
    let payment_method_response = match resp {
        ApplicationResponse::Json(response) => response,
        _ => Err(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to fetch the payment method response")?,
    };

    let pm_id = payment_method_response.payment_method_id.clone();

    let network_token = req.network_token.clone();

    let network_token_migrated = match network_token {
        Some(nt_detail) => {
            logger::debug!("Network token migration");
            let network_token_requestor_ref_id = nt_detail.network_token_requestor_ref_id.clone();
            let network_token_data = &nt_detail.network_token_data;

            Some(
                controller
                    .save_network_token_and_update_payment_method(
                        &req,
                        merchant_context.get_merchant_key_store(),
                        network_token_data,
                        network_token_requestor_ref_id,
                        pm_id,
                    )
                    .await
                    .map_err(|err| logger::error!(?err, "Failed to save network token"))
                    .ok()
                    .unwrap_or_default(),
            )
        }
        None => {
            logger::debug!("Network token data is not available");
            None
        }
    };
    migration_status.network_token_migrated(network_token_migrated);
    let migrate_status = migration_status.build();

    Ok(ApplicationResponse::Json(
        pm_api::PaymentMethodMigrateResponse {
            payment_method_response,
            card_migrated: migrate_status.card_migrated,
            network_token_migrated: migrate_status.network_token_migrated,
            connector_mandate_details_migrated: migrate_status.connector_mandate_details_migrated,
            network_transaction_id_migrated: migrate_status.network_transaction_migrated,
        },
    ))
}


OpenAPI Block Path: components.schemas.ThreeDsMethodData
{
  "oneOf": [
    {
      "type": "object",
      "required": [
        "three_ds_method_data_submission",
        "three_ds_method_key"
      ],
      "properties": {
        "three_ds_method_data_submission": {
          "type": "boolean",
          "description": "Whether ThreeDS method data submission is required"
        },
        "three_ds_method_data": {
          "type": "string",
          "description": "ThreeDS method data",
          "nullable": true
        },
        "three_ds_method_url": {
          "type": "string",
          "description": "ThreeDS method url",
          "nullable": true
        },
        "three_ds_method_key": {
          "type": "string",
          "enum": [
            "threeDSMethodData"
          ]
        }
      }
    }
  ],
  "discriminator": {
    "propertyName": "three_ds_method_key"
  }
}


pub async fn get_org_refund_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetRefundFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetRefundFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req: GetRefundFilterRequest, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let auth: AuthInfo = AuthInfo::OrgLevel {
                    org_id: org_id.clone(),
                };
                analytics::refunds::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::OrganizationAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }


pub fn get_payment_method_data(&self) -> Option<api_models::payments::AdditionalPaymentData> {
        self.payment_method_data
            .clone()
            .and_then(|data| match data {
                serde_json::Value::Null => None,
                _ => Some(data.parse_value("AdditionalPaymentData")),
            })
            .transpose()
            .map_err(|err| logger::error!("Failed to parse AdditionalPaymentData {err:?}"))
            .ok()
            .flatten()
    }


pub async fn tokio_mock_sleep(delay: u64, tolerance: u64) {
    let mut rng = rand::thread_rng();
    // TODO: change this to `Uniform::try_from`
    // this would require changing the fn signature
    // to return a Result
    let effective_delay = Uniform::from((delay - tolerance)..(delay + tolerance));
    tokio::sleep(tokio::Duration::from_millis(
        effective_delay.sample(&mut rng),
    ))
    .await
}


impl GlobalPaymentMethodSessionId {
    /// Create a new GlobalPaymentMethodSessionId from cell id information
    pub fn generate(
        cell_id: &CellId,
    ) -> error_stack::Result<Self, GlobalPaymentMethodSessionIdError> {
        let global_id = GlobalId::generate(cell_id, GlobalEntity::PaymentMethodSession);
        Ok(Self(global_id))
    }

    /// Get the string representation of the id
    pub fn get_string_repr(&self) -> &str {
        self.0.get_string_repr()
    }

    /// Construct a redis key from the id to be stored in redis
    pub fn get_redis_key(&self) -> String {
        format!("payment_method_session:{}", self.get_string_repr())
    }
}


File: crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
 Public structs: 16

use common_utils::{pii, types::StringMajorUnit};
use masking::Secret;
use serde::{Deserialize, Serialize};

use super::requests::DocumentType;

// Response body for POST /sign_in
#[derive(Debug, Deserialize, Serialize)]
pub struct FacilitapayAuthResponse {
    username: Secret<String>,
    name: Secret<String>,
    pub jwt: Secret<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SubjectKycStatus {
    // Customer is able to send/receive money through the platform. No action is needed on your side.
    Approved,

    // Customer is required to upload documents or uploaded documents have been rejected by KYC.
    Reproved,

    // Customer has uploaded KYC documents but awaiting analysis from the backoffice. No action is needed on your side.
    WaitingApproval,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct FacilitapaySubject {
    pub social_name: Secret<String>,
    pub document_type: DocumentType,
    pub document_number: Secret<String>,
    // In documentation, both CountryAlpha2 and String are used. We cannot rely on CountryAlpha2.
    pub fiscal_country: String,
    pub updated_at: Option<String>,
    pub status: SubjectKycStatus,
    #[serde(rename = "id")]
    pub customer_id: Secret<String>,
    pub birth_date: Option<time::Date>,
    pub email: Option<pii::Email>,
    pub phone_country_code: Option<Secret<String>>,
    pub phone_area_code: Option<Secret<String>>,
    pub phone_number: Option<Secret<String>>,
    pub address_street: Option<Secret<String>>,
    pub address_number: Option<Secret<String>>,
    pub address_complement: Option<Secret<String>>,
    pub address_city: Option<String>,
    pub address_state: Option<String>,
    pub address_postal_code: Option<Secret<String>>,
    pub address_country: Option<String>,
    pub address_neighborhood: Option<Secret<String>>,
    pub net_monthly_average_income: Option<StringMajorUnit>,
    pub clearance_level: Option<i32>,
    pub required_clearance_level: Option<i32>,
    pub inserted_at: Option<String>,
    pub references: Option<Vec<serde_json::Value>>,
    pub rfc_pf: Option<Secret<String>>, // 13-digit RFC, specific to Mexico users
    pub documents: Option<Vec<serde_json::Value>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct FacilitapayCustomerResponse {
    pub data: FacilitapaySubject,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PixInfo {
    #[serde(rename = "type")]
    pub key_type: String,
    pub key: Secret<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreditCardResponseInfo {
    pub id: String,
    pub status: Option<String>,
    pub document_type: String,
    pub document_number: Secret<String>,
    pub birthdate: Option<String>,
    pub phone_country_code: Option<String>,
    pub phone_area_code: Option<String>,
    pub phone_number: Option<Secret<String>>,
    pub inserted_at: Option<String>,
}

// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[derive(strum::Display)]
pub enum FacilitapayPaymentStatus {
    /// Transaction has been created but it is waiting for an incoming TED/Wire.
    /// This is the first recorded status in production mode.
    #[default]
    Pending,
    /// Incoming TED/Wire has been identified into Facilita´s bank account.
    /// When it is a deposit into an internal bank account and there is no
    /// conversion involved (BRL to BRL for instance), that is the final state.
    Identified,
    /// The conversion rate has been closed and therefore the exchanged value
    /// is defined - when it is a deposit into an internal bank account, that is the final state.
    Exchanged,
    /// The exchanged value has been wired to its destination (a real bank account) - that is also a final state.
    Wired,
    /// When for any reason the transaction cannot be concluded or need to be reversed, it is canceled.
    #[serde(rename = "canceled")]
    Cancelled,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FacilitapayPaymentsResponse {
    pub data: TransactionData,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OwnerCompany {
    pub social_name: Option<Secret<String>>,
    #[serde(rename = "id")]
    pub company_id: Option<String>,
    pub document_type: DocumentType,
    pub document_number: Secret<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BankInfo {
    pub swift: Option<Secret<String>>,
    pub name: Option<String>,
    #[serde(rename = "id")]
    pub bank_id: Secret<String>,
    pub code: Option<Secret<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BankAccountDetail {
    pub routing_number: Option<Secret<String>>,
    pub pix_info: Option<PixInfo>,
    pub owner_name: Option<Secret<String>>,
    pub owner_document_type: Option<String>,
    pub owner_document_number: Option<Secret<String>>,
    pub owner_company: Option<OwnerCompany>,
    pub internal: Option<bool>,
    pub intermediary_bank_account: Option<Secret<String>>,
    pub intermediary_bank_account_id: Option<Secret<String>>,
    #[serde(rename = "id")]
    pub bank_account_id: Secret<String>,
    pub iban: Option<Secret<String>>,
    pub flow_type: Option<String>,
    pub currency: api_models::enums::Currency,
    pub branch_number: Option<Secret<String>>,
    pub branch_country: Option<String>,
    pub bank: Option<BankInfo>,
    pub account_type: Option<String>,
    pub account_number: Option<Secret<String>>,
    pub aba: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TransactionData {
    #[serde(rename = "id")]
    pub transaction_id: String,
    pub value: StringMajorUnit,
    pub status: FacilitapayPaymentStatus,
    pub currency: api_models::enums::Currency,
    pub exchange_currency: api_models::enums::Currency,

    // Details about the destination account (Required)
    pub to_bank_account: BankAccountDetail,

    // Details about the source - mutually exclusive
    pub from_credit_card: Option<CreditCardResponseInfo>,
    pub from_bank_account: Option<BankAccountDetail>, // Populated for PIX

    // Subject information (customer)
    pub subject_id: String,
    pub subject: Option<FacilitapaySubject>,
    pub subject_is_receiver: Option<bool>,

    // Source identification (potentially redundant with subject or card/bank info)
    pub source_name: Option<Secret<String>>,
    pub source_document_type: DocumentType,
    pub source_document_number: Secret<String>,

    // Timestamps and flags
    pub inserted_at: Option<String>,
    pub for_exchange: Option<bool>,
    pub exchange_under_request: Option<bool>,
    pub estimated_value_until_exchange: Option<bool>,
    pub cleared: Option<bool>,

    // PIX specific field
    pub dynamic_pix_code: Option<String>, // QR code string for PIX

    // Exchange details
    pub exchanged_value: Option<StringMajorUnit>,

    // Cancelation details
    #[serde(rename = "canceled_reason")]
    pub cancelled_reason: Option<String>,
    #[serde(rename = "canceled_at")]
    pub cancelled_at: Option<String>,

    // Other fields
    pub bank_transaction: Option<Secret<serde_json::Value>>,
    pub meta: Option<serde_json::Value>,
}

// Void response structures (for /refund endpoint)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoidBankTransaction {
    #[serde(rename = "id")]
    pub transaction_id: String,
    pub value: StringMajorUnit,
    pub currency: api_models::enums::Currency,
    pub iof_value: Option<StringMajorUnit>,
    pub fx_value: Option<StringMajorUnit>,
    pub exchange_rate: Option<StringMajorUnit>,
    pub exchange_currency: api_models::enums::Currency,
    pub exchanged_value: StringMajorUnit,
    pub exchange_approved: bool,
    pub wire_id: Option<String>,
    pub exchange_id: Option<String>,
    pub movement_date: String,
    pub source_name: Secret<String>,
    pub source_document_number: Secret<String>,
    pub source_document_type: String,
    pub source_id: String,
    pub source_type: String,
    pub source_description: String,
    pub source_bank: Option<String>,
    pub source_branch: Option<String>,
    pub source_account: Option<String>,
    pub source_bank_ispb: Option<String>,
    pub company_id: String,
    pub company_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoidData {
    #[serde(rename = "id")]
    pub void_id: String,
    pub reason: Option<String>,
    pub inserted_at: String,
    pub status: FacilitapayPaymentStatus,
    pub transaction_kind: String,
    pub bank_transaction: VoidBankTransaction,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FacilitapayVoidResponse {
    pub data: VoidData,
}

// Refund response uses the same TransactionData structure as payments
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FacilitapayRefundResponse {
    pub data: TransactionData,
}

// Webhook structures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FacilitapayWebhookNotification {
    pub notification: FacilitapayWebhookBody,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct FacilitapayWebhookBody {
    #[serde(rename = "type")]
    pub event_type: FacilitapayWebhookEventType,
    pub secret: Secret<String>,
    #[serde(flatten)]
    pub data: FacilitapayWebhookData,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FacilitapayWebhookEventType {
    ExchangeCreated,
    Identified,
    PaymentApproved,
    PaymentExpired,
    PaymentFailed,
    PaymentRefunded,
    WireCreated,
    WireWaitingCorrection,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum FacilitapayWebhookErrorCode {
    /// Creditor account number invalid or missing (branch_number or account_number incorrect)
    Ac03,
    /// Creditor account type missing or invalid (account_type incorrect)
    Ac14,
    /// Value in Creditor Identifier is incorrect (owner_document_number incorrect)
    Ch11,
    /// Transaction type not supported/authorized on this account (account rejected the payment)
    Ag03,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FacilitapayWebhookData {
    CardPayment {
        transaction_id: String,
        checkout_id: Option<String>,
    },
    Exchange {
        exchange_id: String,
        transaction_ids: Vec<String>,
    },
    Transaction {
        transaction_id: String,
    },
    Wire {
        wire_id: String,
        transaction_ids: Vec<String>,
    },
    WireError {
        error_code: FacilitapayWebhookErrorCode,
        error_description: String,
        bank_account_owner_id: String,
        bank_account_id: String,
        transaction_ids: Vec<String>,
        wire_id: String,
    },
}


File: crates/hyperswitch_connectors/src/connectors.rs

pub mod aci;
pub mod adyen;
pub mod adyenplatform;
pub mod affirm;
pub mod airwallex;
pub mod amazonpay;
pub mod archipel;
pub mod authipay;
pub mod authorizedotnet;
pub mod bambora;
pub mod bamboraapac;
pub mod bankofamerica;
pub mod barclaycard;
pub mod billwerk;
pub mod bitpay;
pub mod blackhawknetwork;
pub mod bluecode;
pub mod bluesnap;
pub mod boku;
pub mod braintree;
pub mod breadpay;
pub mod cashtocode;
pub mod celero;
pub mod chargebee;
pub mod checkbook;
pub mod checkout;
pub mod coinbase;
pub mod coingate;
pub mod cryptopay;
pub mod ctp_mastercard;
pub mod custombilling;
pub mod cybersource;
pub mod datatrans;
pub mod deutschebank;
pub mod digitalvirgo;
pub mod dlocal;
#[cfg(feature = "dummy_connector")]
pub mod dummyconnector;
pub mod dwolla;
pub mod ebanx;
pub mod elavon;
pub mod facilitapay;
pub mod fiserv;
pub mod fiservemea;
pub mod fiuu;
pub mod flexiti;
pub mod forte;
pub mod getnet;
pub mod globalpay;
pub mod globepay;
pub mod gocardless;
pub mod gpayments;
pub mod helcim;
pub mod hipay;
pub mod hyperswitch_vault;
pub mod hyperwallet;
pub mod iatapay;
pub mod inespay;
pub mod itaubank;
pub mod jpmorgan;
pub mod juspaythreedsserver;
pub mod katapult;
pub mod klarna;
pub mod mifinity;
pub mod mollie;
pub mod moneris;
pub mod mpgs;
pub mod multisafepay;
pub mod netcetera;
pub mod nexinets;
pub mod nexixpay;
pub mod nmi;
pub mod nomupay;
pub mod noon;
pub mod nordea;
pub mod novalnet;
pub mod nuvei;
pub mod opayo;
pub mod opennode;
pub mod paybox;
pub mod payeezy;
pub mod payload;
pub mod payme;
pub mod payone;
pub mod paypal;
pub mod paysafe;
pub mod paystack;
pub mod paytm;
pub mod payu;
pub mod phonepe;
pub mod placetopay;
pub mod plaid;
pub mod powertranz;
pub mod prophetpay;
pub mod rapyd;
pub mod razorpay;
pub mod recurly;
pub mod redsys;
pub mod riskified;
pub mod santander;
pub mod shift4;
pub mod sift;
pub mod signifyd;
pub mod silverflow;
pub mod square;
pub mod stax;
pub mod stripe;
pub mod stripebilling;
pub mod taxjar;
pub mod threedsecureio;
pub mod thunes;
pub mod tokenio;
pub mod trustpay;
pub mod trustpayments;
pub mod tsys;
pub mod unified_authentication_service;
pub mod vgs;
pub mod volt;
pub mod wellsfargo;
pub mod wellsfargopayout;
pub mod wise;
pub mod worldline;
pub mod worldpay;
pub mod worldpayvantiv;
pub mod worldpayxml;
pub mod xendit;
pub mod zen;
pub mod zsl;
#[cfg(feature = "dummy_connector")]
pub use self::dummyconnector::DummyConnector;
pub use self::{
    aci::Aci, adyen::Adyen, adyenplatform::Adyenplatform, affirm::Affirm, airwallex::Airwallex,
    amazonpay::Amazonpay, archipel::Archipel, authipay::Authipay, authorizedotnet::Authorizedotnet,
    bambora::Bambora, bamboraapac::Bamboraapac, bankofamerica::Bankofamerica,
    barclaycard::Barclaycard, billwerk::Billwerk, bitpay::Bitpay,
    blackhawknetwork::Blackhawknetwork, bluecode::Bluecode, bluesnap::Bluesnap, boku::Boku,
    braintree::Braintree, breadpay::Breadpay, cashtocode::Cashtocode, celero::Celero,
    chargebee::Chargebee, checkbook::Checkbook, checkout::Checkout, coinbase::Coinbase,
    coingate::Coingate, cryptopay::Cryptopay, ctp_mastercard::CtpMastercard,
    custombilling::Custombilling, cybersource::Cybersource, datatrans::Datatrans,
    deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, dwolla::Dwolla,
    ebanx::Ebanx, elavon::Elavon, facilitapay::Facilitapay, fiserv::Fiserv, fiservemea::Fiservemea,
    fiuu::Fiuu, flexiti::Flexiti, forte::Forte, getnet::Getnet, globalpay::Globalpay,
    globepay::Globepay, gocardless::Gocardless, gpayments::Gpayments, helcim::Helcim, hipay::Hipay,
    hyperswitch_vault::HyperswitchVault, hyperwallet::Hyperwallet, iatapay::Iatapay,
    inespay::Inespay, itaubank::Itaubank, jpmorgan::Jpmorgan,
    juspaythreedsserver::Juspaythreedsserver, katapult::Katapult, klarna::Klarna,
    mifinity::Mifinity, mollie::Mollie, moneris::Moneris, mpgs::Mpgs, multisafepay::Multisafepay,
    netcetera::Netcetera, nexinets::Nexinets, nexixpay::Nexixpay, nmi::Nmi, nomupay::Nomupay,
    noon::Noon, nordea::Nordea, novalnet::Novalnet, nuvei::Nuvei, opayo::Opayo, opennode::Opennode,
    paybox::Paybox, payeezy::Payeezy, payload::Payload, payme::Payme, payone::Payone,
    paypal::Paypal, paysafe::Paysafe, paystack::Paystack, paytm::Paytm, payu::Payu,
    phonepe::Phonepe, placetopay::Placetopay, plaid::Plaid, powertranz::Powertranz,
    prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, recurly::Recurly, redsys::Redsys,
    riskified::Riskified, santander::Santander, shift4::Shift4, sift::Sift, signifyd::Signifyd,
    silverflow::Silverflow, square::Square, stax::Stax, stripe::Stripe,
    stripebilling::Stripebilling, taxjar::Taxjar, threedsecureio::Threedsecureio, thunes::Thunes,
    tokenio::Tokenio, trustpay::Trustpay, trustpayments::Trustpayments, tsys::Tsys,
    unified_authentication_service::UnifiedAuthenticationService, vgs::Vgs, volt::Volt,
    wellsfargo::Wellsfargo, wellsfargopayout::Wellsfargopayout, wise::Wise, worldline::Worldline,
    worldpay::Worldpay, worldpayvantiv::Worldpayvantiv, worldpayxml::Worldpayxml, xendit::Xendit,
    zen::Zen, zsl::Zsl,
};


pub struct PaymentGet;


File: crates/hyperswitch_connectors/src/connectors/nomupay.rs
 Public functions: 1
 Public structs: 1

pub mod transformers;

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

use common_utils::{
    errors::CustomResult,
    ext_traits::BytesExt,
    pii,
    request::{Method, RequestContent},
};
#[cfg(feature = "payouts")]
use common_utils::{
    request::{Request, RequestBuilder},
    types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods,
    },
};
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_flow_types::payouts::{
        PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount,
        PoSync,
    },
    router_request_types::PayoutsData,
    router_response_types::PayoutsResponseData,
    types::PayoutsRouterData,
};
#[cfg(feature = "payouts")]
use hyperswitch_interfaces::types;
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation,
    },
    configs::Connectors,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::Response,
    webhooks,
};
use josekit::{
    jws::{self, JwsHeader, ES256},
    jwt::{self, JwtPayload},
    Map, Value,
};
use masking::{ExposeInterface, Mask};
#[cfg(feature = "payouts")]
use router_env::{instrument, tracing};
use serde_json::json;
use transformers as nomupay;

use crate::{constants::headers, utils};
#[cfg(feature = "payouts")]
use crate::{types::ResponseRouterData, utils::RouterData as RouterDataTrait};

#[derive(Clone)]
pub struct Nomupay {
    #[cfg(feature = "payouts")]
    amount_converter: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}

impl Nomupay {
    pub fn new() -> &'static Self {
        &Self {
            #[cfg(feature = "payouts")]
            amount_converter: &FloatMajorUnitForConnector,
        }
    }
}

fn get_private_key(
    metadata: &Option<pii::SecretSerdeValue>,
) -> Result<String, errors::ConnectorError> {
    match nomupay::NomupayMetadata::try_from(metadata.as_ref()) {
        Ok(nomupay_metadata) => Ok(nomupay_metadata.private_key.expose()),
        Err(_e) => Err(errors::ConnectorError::NoConnectorMetaData),
    }
}

fn box_to_jwt_payload(
    body: Box<dyn masking::ErasedMaskSerialize + Send>,
) -> CustomResult<JwtPayload, errors::ConnectorError> {
    let str_result = serde_json::to_string(&body)
        .change_context(errors::ConnectorError::ProcessingStepFailed(None))?;

    let parsed_json: Map<String, Value> = serde_json::from_str(&str_result)
        .change_context(errors::ConnectorError::ProcessingStepFailed(None))?;

    let jwt_payload = JwtPayload::from_map(parsed_json)
        .change_context(errors::ConnectorError::ProcessingStepFailed(None))?;

    Ok(jwt_payload)
}

fn get_signature(
    metadata: &Option<pii::SecretSerdeValue>,
    auth: nomupay::NomupayAuthType,
    body: RequestContent,
    method: &str,
    path: String,
) -> CustomResult<String, errors::ConnectorError> {
    match body {
        RequestContent::Json(masked_json) => {
            let expiration_time = SystemTime::now() + Duration::from_secs(4 * 60);
            let expires_in = match expiration_time.duration_since(UNIX_EPOCH) {
                Ok(duration) => duration.as_secs(),
                Err(_e) => 0,
            };

            let mut option_map = Map::new();
            option_map.insert("alg".to_string(), json!(format!("ES256")));
            option_map.insert("aud".to_string(), json!(format!("{} {}", method, path)));
            option_map.insert("exp".to_string(), json!(expires_in));
            option_map.insert("kid".to_string(), json!(auth.kid));

            let header = JwsHeader::from_map(option_map)
                .change_context(errors::ConnectorError::ProcessingStepFailed(None))?;

            let payload = match method {
                "GET" => JwtPayload::new(),
                _ => box_to_jwt_payload(masked_json)
                    .change_context(errors::ConnectorError::ProcessingStepFailed(None))?,
            };

            let private_key = get_private_key(metadata)?;

            let signer = ES256
                .signer_from_pem(&private_key)
                .change_context(errors::ConnectorError::ProcessingStepFailed(None))?;

            let nomupay_jwt = match method {
                "GET" => jws::serialize_compact(b"", &header, &signer)
                    .change_context(errors::ConnectorError::ProcessingStepFailed(None))?,
                _ => jwt::encode_with_signer(&payload, &header, &signer)
                    .change_context(errors::ConnectorError::ProcessingStepFailed(None))?,
            };

            let jws_blocks: Vec<&str> = nomupay_jwt.split('.').collect();

            let jws_detached = jws_blocks
                .first()
                .zip(jws_blocks.get(2))
                .map(|(first, third)| format!("{first}..{third}"))
                .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                    field_name: "JWS blocks not sufficient for detached payload",
                })?;

            Ok(jws_detached)
        }
        _ => Err(errors::ConnectorError::ProcessingStepFailed(None).into()),
    }
}

impl api::Payment for Nomupay {}
impl api::PaymentSession for Nomupay {}
impl api::ConnectorAccessToken for Nomupay {}
impl api::MandateSetup for Nomupay {}
impl api::PaymentAuthorize for Nomupay {}
impl api::PaymentSync for Nomupay {}
impl api::PaymentCapture for Nomupay {}
impl api::PaymentVoid for Nomupay {}
impl api::Refund for Nomupay {}
impl api::RefundExecute for Nomupay {}
impl api::RefundSync for Nomupay {}
impl api::PaymentToken for Nomupay {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Nomupay
{
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Nomupay
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let is_post_req = matches!(self.get_http_method(), Method::Post);
        let body = self.get_request_body(req, connectors)?;
        let auth = nomupay::NomupayAuthType::try_from(&req.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let base_url = connectors.nomupay.base_url.as_str();
        let path: String = self
            .get_url(req, connectors)?
            .chars()
            .skip(base_url.len())
            .collect();
        let req_method = if is_post_req { "POST" } else { "GET" };

        let sign = get_signature(&req.connector_meta_data, auth, body, req_method, path)?;

        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];
        header.push((
            headers::X_SIGNATURE.to_string(),
            masking::Maskable::Normal(sign),
        ));

        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);

        Ok(header)
    }
}

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

impl ConnectorCommon for Nomupay {
    fn id(&self) -> &'static str {
        "nomupay"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.nomupay.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = nomupay::NomupayAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            auth.kid.expose().into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: nomupay::NomupayErrorResponse = res
            .response
            .parse_struct("NomupayErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        match (
            response.status,
            response.code,
            response.error,
            response.status_code,
            response.detail,
        ) {
            (Some(status), Some(code), _, _, _) => Ok(ErrorResponse {
                status_code: res.status_code,
                code: code.to_string(),
                message: status,
                reason: None,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            (None, None, Some(nomupay_inner_error), _, _) => {
                match (
                    nomupay_inner_error.error_description,
                    nomupay_inner_error.validation_errors,
                ) {
                    (Some(error_description), _) => Ok(ErrorResponse {
                        status_code: res.status_code,
                        code: nomupay_inner_error.error_code,
                        message: error_description,
                        reason: None,
                        attempt_status: None,
                        connector_transaction_id: None,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    (_, Some(validation_errors)) => Ok(ErrorResponse {
                        status_code: res.status_code,
                        code: nomupay_inner_error.error_code,
                        message: validation_errors
                            .first()
                            .map(|m| m.message.clone())
                            .unwrap_or_default(),
                        reason: Some(
                            validation_errors
                                .first()
                                .map(|m| m.field.clone())
                                .unwrap_or_default(),
                        ),
                        attempt_status: None,
                        connector_transaction_id: None,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    (None, None) => Ok(ErrorResponse {
                        status_code: res.status_code,
                        code: NO_ERROR_CODE.to_string(),
                        message: NO_ERROR_MESSAGE.to_string(),
                        reason: None,
                        attempt_status: None,
                        connector_transaction_id: None,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                }
            }
            (None, None, None, Some(status_code), Some(detail)) => Ok(ErrorResponse {
                status_code,
                code: detail
                    .get(1)
                    .map(|d| d.error_type.clone())
                    .unwrap_or_default(),
                message: status_code.to_string(),
                reason: None,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            _ => Ok(ErrorResponse {
                status_code: res.status_code,
                code: NO_ERROR_CODE.to_string(),
                message: NO_ERROR_MESSAGE.to_string(),
                reason: None,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        }
    }
}

impl ConnectorValidation for Nomupay {}
impl ConnectorRedirectResponse for Nomupay {}
impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Nomupay {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Nomupay {}
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Nomupay {}
impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Nomupay {}
impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Nomupay {}
impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Nomupay {}
impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Nomupay {}
impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Nomupay {}
impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Nomupay {}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoQuote, PayoutsData, PayoutsResponseData> for Nomupay {}
#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoCreate, PayoutsData, PayoutsResponseData> for Nomupay {}
#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoCancel, PayoutsData, PayoutsResponseData> for Nomupay {}
#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoEligibility, PayoutsData, PayoutsResponseData> for Nomupay {}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoSync, PayoutsData, PayoutsResponseData> for Nomupay {
    fn get_url(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let nomupay_payout_id = req
            .request
            .connector_payout_id
            .clone()
            .ok_or_else(utils::missing_field_err("connector_payout_id"))?;
        Ok(format!(
            "{}/v1alpha1/payments/{}",
            self.base_url(connectors),
            nomupay_payout_id
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_http_method(&self) -> Method {
        Method::Get
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoSync>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Get)
            .url(&types::PayoutSyncType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::PayoutSyncType::get_headers(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    #[instrument(skip_all)]
    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoSync>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoSync>, errors::ConnectorError> {
        let response: nomupay::NomupayPaymentResponse = res
            .response
            .parse_struct("NomupayPaymentResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl api::Payouts for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutCancel for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutCreate for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutEligibility for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutFulfill for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutQuote for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutRecipient for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutRecipientAccount for Nomupay {}
#[cfg(feature = "payouts")]
impl api::PayoutSync for Nomupay {}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoRecipient, PayoutsData, PayoutsResponseData> for Nomupay {
    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}/v1alpha1/sub-account",
            connectors.nomupay.base_url
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = nomupay::OnboardSubAccountRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&types::PayoutRecipientType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::PayoutRecipientType::get_headers(
                self, req, connectors,
            )?)
            .set_body(types::PayoutRecipientType::get_request_body(
                self, req, connectors,
            )?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoRecipient>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoRecipient>, errors::ConnectorError> {
        let response: nomupay::OnboardSubAccountResponse = res
            .response
            .parse_struct("OnboardSubAccountResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoRecipientAccount, PayoutsData, PayoutsResponseData> for Nomupay {
    fn get_url(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let sid = req.get_connector_customer_id()?;

        Ok(format!(
            "{}/v1alpha1/sub-account/{}/transfer-method",
            connectors.nomupay.base_url, sid
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = nomupay::OnboardTransferMethodRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&types::PayoutRecipientAccountType::get_url(
                self, req, connectors,
            )?)
            .attach_default_headers()
            .headers(types::PayoutRecipientAccountType::get_headers(
                self, req, connectors,
            )?)
            .set_body(types::PayoutRecipientAccountType::get_request_body(
                self, req, connectors,
            )?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoRecipientAccount>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoRecipientAccount>, errors::ConnectorError> {
        let response: nomupay::OnboardTransferMethodResponse = res
            .response
            .parse_struct("OnboardTransferMethodResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoFulfill, PayoutsData, PayoutsResponseData> for Nomupay {
    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/v1alpha1/payments", connectors.nomupay.base_url))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.destination_currency,
        )?;
        let connector_req = nomupay::NomupayPaymentRequest::try_from((req, amount))?;

        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&types::PayoutFulfillType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::PayoutFulfillType::get_headers(
                self, req, connectors,
            )?)
            .set_body(types::PayoutFulfillType::get_request_body(
                self, req, connectors,
            )?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoFulfill>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoFulfill>, errors::ConnectorError> {
        let response: nomupay::NomupayPaymentResponse = res
            .response
            .parse_struct("NomupayPaymentResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Nomupay {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

static NOMUPAY_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Nomupay",
    description: "Nomupay payouts connector for disbursements to recipients' bank accounts and alternative payment methods in Southeast Asia and the Pacific Islands",
    connector_type: common_enums::HyperswitchConnectorCategory::PayoutProcessor,
    integration_status: common_enums::ConnectorIntegrationStatus::Sandbox,
};

impl ConnectorSpecifications for Nomupay {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&NOMUPAY_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::enums::EventClass]> {
        None
    }
}


pub fn set_distinct(&mut self) {
        self.distinct = true
    }


impl PaymentMethodsStorageInterface for KafkaStore {}

impl PaymentMethodsStorageInterface for for KafkaStore


pub fn to_json_string<T: Serialize>(value: &T) -> String {
    serde_json::to_string(value)
        .map_err(|_| errors::RoutingError::GenericConversionError {
            from: "T".to_string(),
            to: "JsonValue".to_string(),
        })
        .unwrap_or_default()
}


File: crates/api_models/src/errors/types.rs
 Public functions: 2
 Public structs: 3

use reqwest::StatusCode;
use router_derive::PolymorphicSchema;
use serde::Serialize;
use utoipa::ToSchema;

#[derive(Debug, serde::Serialize)]
pub enum ErrorType {
    InvalidRequestError,
    RouterError,
    ConnectorError,
}

#[derive(Debug, serde::Serialize, Clone)]
pub struct ApiError {
    pub sub_code: &'static str,
    pub error_identifier: u16,
    pub error_message: String,
    pub extra: Option<Extra>,
    #[cfg(feature = "detailed_errors")]
    pub stacktrace: Option<serde_json::Value>,
}

impl ApiError {
    pub fn new(
        sub_code: &'static str,
        error_identifier: u16,
        error_message: impl ToString,
        extra: Option<Extra>,
    ) -> Self {
        Self {
            sub_code,
            error_identifier,
            error_message: error_message.to_string(),
            extra,
            #[cfg(feature = "detailed_errors")]
            stacktrace: None,
        }
    }
}

#[derive(Debug, serde::Serialize, ToSchema, PolymorphicSchema)]
#[generate_schemas(GenericErrorResponseOpenApi)]
pub struct ErrorResponse {
    #[serde(rename = "type")]
    #[schema(
        example = "invalid_request",
        value_type = &'static str
    )]
    pub error_type: &'static str,
    #[schema(
        example = "Missing required param: {param}",
        value_type = String
    )]
    pub message: String,
    #[schema(
        example = "IR_04",
        value_type = String
    )]
    pub code: String,
    #[serde(flatten)]
    pub extra: Option<Extra>,

    #[cfg(feature = "detailed_errors")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stacktrace: Option<serde_json::Value>,
}

impl From<&ApiErrorResponse> for ErrorResponse {
    fn from(value: &ApiErrorResponse) -> Self {
        let error_info = value.get_internal_error();
        let error_type = value.error_type();
        Self {
            code: format!("{}_{:02}", error_info.sub_code, error_info.error_identifier),
            message: error_info.error_message.clone(),
            error_type,
            extra: error_info.extra.clone(),

            #[cfg(feature = "detailed_errors")]
            stacktrace: error_info.stacktrace.clone(),
        }
    }
}

#[derive(Debug, serde::Serialize, Default, Clone)]
pub struct Extra {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub connector: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub connector_transaction_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fields: Option<String>,
}

#[derive(Serialize, Debug, Clone)]
#[serde(tag = "type", content = "value")]
pub enum ApiErrorResponse {
    Unauthorized(ApiError),
    ForbiddenCommonResource(ApiError),
    ForbiddenPrivateResource(ApiError),
    Conflict(ApiError),
    Gone(ApiError),
    Unprocessable(ApiError),
    InternalServerError(ApiError),
    NotImplemented(ApiError),
    ConnectorError(ApiError, #[serde(skip_serializing)] StatusCode),
    NotFound(ApiError),
    MethodNotAllowed(ApiError),
    BadRequest(ApiError),
    DomainError(ApiError),
}

impl ::core::fmt::Display for ApiErrorResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let error_response: ErrorResponse = self.into();
        write!(
            f,
            r#"{{"error":{}}}"#,
            serde_json::to_string(&error_response)
                .unwrap_or_else(|_| "API error response".to_string())
        )
    }
}

impl ApiErrorResponse {
    pub(crate) fn get_internal_error(&self) -> &ApiError {
        match self {
            Self::Unauthorized(i)
            | Self::ForbiddenCommonResource(i)
            | Self::ForbiddenPrivateResource(i)
            | Self::Conflict(i)
            | Self::Gone(i)
            | Self::Unprocessable(i)
            | Self::InternalServerError(i)
            | Self::NotImplemented(i)
            | Self::NotFound(i)
            | Self::MethodNotAllowed(i)
            | Self::BadRequest(i)
            | Self::DomainError(i)
            | Self::ConnectorError(i, _) => i,
        }
    }

    pub fn get_internal_error_mut(&mut self) -> &mut ApiError {
        match self {
            Self::Unauthorized(i)
            | Self::ForbiddenCommonResource(i)
            | Self::ForbiddenPrivateResource(i)
            | Self::Conflict(i)
            | Self::Gone(i)
            | Self::Unprocessable(i)
            | Self::InternalServerError(i)
            | Self::NotImplemented(i)
            | Self::NotFound(i)
            | Self::MethodNotAllowed(i)
            | Self::BadRequest(i)
            | Self::DomainError(i)
            | Self::ConnectorError(i, _) => i,
        }
    }

    pub(crate) fn error_type(&self) -> &'static str {
        match self {
            Self::Unauthorized(_)
            | Self::ForbiddenCommonResource(_)
            | Self::ForbiddenPrivateResource(_)
            | Self::Conflict(_)
            | Self::Gone(_)
            | Self::Unprocessable(_)
            | Self::NotImplemented(_)
            | Self::MethodNotAllowed(_)
            | Self::NotFound(_)
            | Self::BadRequest(_) => "invalid_request",
            Self::InternalServerError(_) => "api",
            Self::DomainError(_) => "blocked",
            Self::ConnectorError(_, _) => "connector",
        }
    }
}

impl std::error::Error for ApiErrorResponse {}


pub struct GpaymentsConnectorMetaData {
    pub authentication_url: String,
    pub three_ds_requestor_trans_id: Option<String>,
}


pub async fn tokenize_card_using_pm_api() {}


File: crates/analytics/src/sqlx.rs
 Public functions: 1
 Public structs: 1

use std::{fmt::Display, str::FromStr};

use api_models::{
    analytics::{frm::FrmTransactionType, refunds::RefundType},
    enums::{DisputeStage, DisputeStatus},
};
use common_enums::{
    AuthenticationConnectors, AuthenticationStatus, DecoupledAuthenticationType, TransactionStatus,
};
use common_utils::{
    errors::{CustomResult, ParsingError},
    DbConnectionParams,
};
use diesel_models::enums::{
    AttemptStatus, AuthenticationType, Currency, FraudCheckStatus, IntentStatus, PaymentMethod,
    RefundStatus, RoutingApproach,
};
use error_stack::ResultExt;
use sqlx::{
    postgres::{PgArgumentBuffer, PgPoolOptions, PgRow, PgTypeInfo, PgValueRef},
    Decode, Encode,
    Error::ColumnNotFound,
    FromRow, Pool, Postgres, Row,
};
use storage_impl::config::Database;
use time::PrimitiveDateTime;

use super::{
    health_check::HealthCheck,
    query::{Aggregate, ToSql, Window},
    types::{
        AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, LoadRow, QueryExecutionError,
        TableEngine,
    },
};

#[derive(Debug, Clone)]
pub struct SqlxClient {
    pool: Pool<Postgres>,
}

impl Default for SqlxClient {
    fn default() -> Self {
        let database_url = format!(
            "postgres://{}:{}@{}:{}/{}",
            "db_user", "db_pass", "localhost", 5432, "hyperswitch_db"
        );
        Self {
            #[allow(clippy::expect_used)]
            pool: PgPoolOptions::new()
                .connect_lazy(&database_url)
                .expect("SQLX Pool Creation failed"),
        }
    }
}

impl SqlxClient {
    pub async fn from_conf(conf: &Database, schema: &str) -> Self {
        let database_url = conf.get_database_url(schema);
        #[allow(clippy::expect_used)]
        let pool = PgPoolOptions::new()
            .max_connections(conf.pool_size)
            .acquire_timeout(std::time::Duration::from_secs(conf.connection_timeout))
            .connect_lazy(&database_url)
            .expect("SQLX Pool Creation failed");
        Self { pool }
    }
}

pub trait DbType {
    fn name() -> &'static str;
}

macro_rules! db_type {
    ($a: ident, $str: tt) => {
        impl DbType for $a {
            fn name() -> &'static str {
                stringify!($str)
            }
        }
    };
    ($a:ident) => {
        impl DbType for $a {
            fn name() -> &'static str {
                stringify!($a)
            }
        }
    };
}

db_type!(Currency);
db_type!(AuthenticationType);
db_type!(AttemptStatus);
db_type!(IntentStatus);
db_type!(PaymentMethod, TEXT);
db_type!(RefundStatus);
db_type!(RefundType);
db_type!(FraudCheckStatus);
db_type!(FrmTransactionType);
db_type!(DisputeStage);
db_type!(DisputeStatus);
db_type!(AuthenticationStatus);
db_type!(TransactionStatus);
db_type!(AuthenticationConnectors);
db_type!(DecoupledAuthenticationType);
db_type!(RoutingApproach);

impl<'q, Type> Encode<'q, Postgres> for DBEnumWrapper<Type>
where
    Type: DbType + FromStr + Display,
{
    fn encode_by_ref(
        &self,
        buf: &mut PgArgumentBuffer,
    ) -> Result<sqlx::encode::IsNull, Box<(dyn std::error::Error + Send + Sync + 'static)>> {
        <String as Encode<'q, Postgres>>::encode(self.0.to_string(), buf)
    }
    fn size_hint(&self) -> usize {
        <String as Encode<'q, Postgres>>::size_hint(&self.0.to_string())
    }
}

impl<'r, Type> Decode<'r, Postgres> for DBEnumWrapper<Type>
where
    Type: DbType + FromStr + Display,
{
    fn decode(
        value: PgValueRef<'r>,
    ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
        let str_value = <&'r str as Decode<'r, Postgres>>::decode(value)?;
        Type::from_str(str_value).map(DBEnumWrapper).or(Err(format!(
            "invalid value {:?} for enum {}",
            str_value,
            Type::name()
        )
        .into()))
    }
}

impl<Type> sqlx::Type<Postgres> for DBEnumWrapper<Type>
where
    Type: DbType + FromStr + Display,
{
    fn type_info() -> PgTypeInfo {
        PgTypeInfo::with_name(Type::name())
    }
}

impl<T> LoadRow<T> for SqlxClient
where
    for<'a> T: FromRow<'a, PgRow>,
{
    fn load_row(row: PgRow) -> CustomResult<T, QueryExecutionError> {
        T::from_row(&row).change_context(QueryExecutionError::RowExtractionFailure)
    }
}

impl super::payments::filters::PaymentFilterAnalytics for SqlxClient {}
impl super::payments::metrics::PaymentMetricAnalytics for SqlxClient {}
impl super::payments::distribution::PaymentDistributionAnalytics for SqlxClient {}
impl super::payment_intents::filters::PaymentIntentFilterAnalytics for SqlxClient {}
impl super::payment_intents::metrics::PaymentIntentMetricAnalytics for SqlxClient {}
impl super::refunds::metrics::RefundMetricAnalytics for SqlxClient {}
impl super::refunds::filters::RefundFilterAnalytics for SqlxClient {}
impl super::refunds::distribution::RefundDistributionAnalytics for SqlxClient {}
impl super::disputes::filters::DisputeFilterAnalytics for SqlxClient {}
impl super::disputes::metrics::DisputeMetricAnalytics for SqlxClient {}
impl super::frm::metrics::FrmMetricAnalytics for SqlxClient {}
impl super::frm::filters::FrmFilterAnalytics for SqlxClient {}
impl super::auth_events::metrics::AuthEventMetricAnalytics for SqlxClient {}
impl super::auth_events::filters::AuthEventFilterAnalytics for SqlxClient {}

#[async_trait::async_trait]
impl AnalyticsDataSource for SqlxClient {
    type Row = PgRow;

    async fn load_results<T>(&self, query: &str) -> CustomResult<Vec<T>, QueryExecutionError>
    where
        Self: LoadRow<T>,
    {
        sqlx::query(&format!("{query};"))
            .fetch_all(&self.pool)
            .await
            .change_context(QueryExecutionError::DatabaseError)
            .attach_printable_lazy(|| format!("Failed to run query {query}"))?
            .into_iter()
            .map(Self::load_row)
            .collect::<Result<Vec<_>, _>>()
            .change_context(QueryExecutionError::RowExtractionFailure)
    }
}
#[async_trait::async_trait]
impl HealthCheck for SqlxClient {
    async fn deep_health_check(&self) -> CustomResult<(), QueryExecutionError> {
        sqlx::query("SELECT 1")
            .fetch_all(&self.pool)
            .await
            .map(|_| ())
            .change_context(QueryExecutionError::DatabaseError)
    }
}

impl<'a> FromRow<'a, PgRow> for super::auth_events::metrics::AuthEventMetricRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let authentication_status: Option<DBEnumWrapper<AuthenticationStatus>> =
            row.try_get("authentication_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let trans_status: Option<DBEnumWrapper<TransactionStatus>> =
            row.try_get("trans_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let authentication_type: Option<DBEnumWrapper<DecoupledAuthenticationType>> =
            row.try_get("authentication_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let error_message: Option<String> = row.try_get("error_message").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let authentication_connector: Option<DBEnumWrapper<AuthenticationConnectors>> = row
            .try_get("authentication_connector")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let message_version: Option<String> =
            row.try_get("message_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;

        let platform: Option<String> = row.try_get("platform").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let acs_reference_number: Option<String> =
            row.try_get("acs_reference_number").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let count: Option<i64> = row.try_get("count").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        // Removing millisecond precision to get accurate diffs against clickhouse
        let start_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("start_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let end_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let mcc: Option<String> = row.try_get("mcc").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let merchant_country: Option<String> =
            row.try_get("merchant_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let billing_country: Option<String> =
            row.try_get("billing_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let shipping_country: Option<String> =
            row.try_get("shipping_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let issuer_country: Option<String> =
            row.try_get("issuer_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let earliest_supported_version: Option<String> = row
            .try_get("earliest_supported_version")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let latest_supported_version: Option<String> = row
            .try_get("latest_supported_version")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let whitelist_decision: Option<bool> =
            row.try_get("whitelist_decision").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let device_manufacturer: Option<String> =
            row.try_get("device_manufacturer").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let device_type: Option<String> = row.try_get("device_type").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_brand: Option<String> = row.try_get("device_brand").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_os: Option<String> = row.try_get("device_os").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_display: Option<String> =
            row.try_get("device_display").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let browser_name: Option<String> = row.try_get("browser_name").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let browser_version: Option<String> =
            row.try_get("browser_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let issuer_id: Option<String> = row.try_get("issuer_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let scheme_name: Option<String> = row.try_get("scheme_name").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let exemption_requested: Option<bool> =
            row.try_get("exemption_requested").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let exemption_accepted: Option<bool> =
            row.try_get("exemption_accepted").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;

        Ok(Self {
            authentication_status,
            trans_status,
            authentication_type,
            error_message,
            authentication_connector,
            message_version,
            acs_reference_number,
            platform,
            count,
            start_bucket,
            end_bucket,
            mcc,
            currency,
            merchant_country,
            billing_country,
            shipping_country,
            issuer_country,
            earliest_supported_version,
            latest_supported_version,
            whitelist_decision,
            device_manufacturer,
            device_type,
            device_brand,
            device_os,
            device_display,
            browser_name,
            browser_version,
            issuer_id,
            scheme_name,
            exemption_requested,
            exemption_accepted,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::auth_events::filters::AuthEventFilterRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let authentication_status: Option<DBEnumWrapper<AuthenticationStatus>> =
            row.try_get("authentication_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let trans_status: Option<DBEnumWrapper<TransactionStatus>> =
            row.try_get("trans_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let authentication_type: Option<DBEnumWrapper<DecoupledAuthenticationType>> =
            row.try_get("authentication_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let error_message: Option<String> = row.try_get("error_message").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let authentication_connector: Option<DBEnumWrapper<AuthenticationConnectors>> = row
            .try_get("authentication_connector")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let message_version: Option<String> =
            row.try_get("message_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let acs_reference_number: Option<String> =
            row.try_get("acs_reference_number").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let platform: Option<String> = row.try_get("platform").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let mcc: Option<String> = row.try_get("mcc").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let merchant_country: Option<String> =
            row.try_get("merchant_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let billing_country: Option<String> =
            row.try_get("billing_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let shipping_country: Option<String> =
            row.try_get("shipping_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let issuer_country: Option<String> =
            row.try_get("issuer_country").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let earliest_supported_version: Option<String> = row
            .try_get("earliest_supported_version")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let latest_supported_version: Option<String> = row
            .try_get("latest_supported_version")
            .or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let whitelist_decision: Option<bool> =
            row.try_get("whitelist_decision").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let device_manufacturer: Option<String> =
            row.try_get("device_manufacturer").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let device_type: Option<String> = row.try_get("device_type").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_brand: Option<String> = row.try_get("device_brand").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_os: Option<String> = row.try_get("device_os").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let device_display: Option<String> =
            row.try_get("device_display").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let browser_name: Option<String> = row.try_get("browser_name").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let browser_version: Option<String> =
            row.try_get("browser_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let issuer_id: Option<String> = row.try_get("issuer_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let scheme_name: Option<String> = row.try_get("scheme_name").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let exemption_requested: Option<bool> =
            row.try_get("exemption_requested").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let exemption_accepted: Option<bool> =
            row.try_get("exemption_accepted").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;

        Ok(Self {
            authentication_status,
            trans_status,
            authentication_type,
            error_message,
            authentication_connector,
            message_version,
            platform,
            acs_reference_number,
            mcc,
            currency,
            merchant_country,
            billing_country,
            shipping_country,
            issuer_country,
            earliest_supported_version,
            latest_supported_version,
            whitelist_decision,
            device_manufacturer,
            device_type,
            device_brand,
            device_os,
            device_display,
            browser_name,
            browser_version,
            issuer_id,
            scheme_name,
            exemption_requested,
            exemption_accepted,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::refunds::metrics::RefundMetricRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let refund_status: Option<DBEnumWrapper<RefundStatus>> =
            row.try_get("refund_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let connector: Option<String> = row.try_get("connector").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let refund_type: Option<DBEnumWrapper<RefundType>> =
            row.try_get("refund_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let profile_id: Option<String> = row.try_get("profile_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let refund_reason: Option<String> = row.try_get("refund_reason").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let refund_error_message: Option<String> =
            row.try_get("refund_error_message").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let total: Option<bigdecimal::BigDecimal> = row.try_get("total").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let count: Option<i64> = row.try_get("count").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        // Removing millisecond precision to get accurate diffs against clickhouse
        let start_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("start_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let end_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        Ok(Self {
            currency,
            refund_status,
            connector,
            refund_type,
            profile_id,
            refund_reason,
            refund_error_message,
            total,
            count,
            start_bucket,
            end_bucket,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::frm::metrics::FrmMetricRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let frm_name: Option<String> = row.try_get("frm_name").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let frm_status: Option<DBEnumWrapper<FraudCheckStatus>> =
            row.try_get("frm_status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let frm_transaction_type: Option<DBEnumWrapper<FrmTransactionType>> =
            row.try_get("frm_transaction_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let total: Option<bigdecimal::BigDecimal> = row.try_get("total").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let count: Option<i64> = row.try_get("count").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        // Removing millisecond precision to get accurate diffs against clickhouse
        let start_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("start_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let end_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        Ok(Self {
            frm_name,
            frm_status,
            frm_transaction_type,
            total,
            count,
            start_bucket,
            end_bucket,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let status: Option<DBEnumWrapper<AttemptStatus>> =
            row.try_get("status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let connector: Option<String> = row.try_get("connector").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let authentication_type: Option<DBEnumWrapper<AuthenticationType>> =
            row.try_get("authentication_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let payment_method: Option<String> =
            row.try_get("payment_method").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let payment_method_type: Option<String> =
            row.try_get("payment_method_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let client_source: Option<String> = row.try_get("client_source").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let client_version: Option<String> =
            row.try_get("client_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let profile_id: Option<String> = row.try_get("profile_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_network: Option<String> = row.try_get("card_network").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let merchant_id: Option<String> = row.try_get("merchant_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_last_4: Option<String> = row.try_get("card_last_4").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_issuer: Option<String> = row.try_get("card_issuer").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let error_reason: Option<String> = row.try_get("error_reason").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let first_attempt: Option<bool> = row.try_get("first_attempt").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let routing_approach: Option<DBEnumWrapper<RoutingApproach>> =
            row.try_get("routing_approach").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let signature_network: Option<String> =
            row.try_get("signature_network").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let is_issuer_regulated: Option<bool> =
            row.try_get("is_issuer_regulated").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let is_debit_routed: Option<bool> =
            row.try_get("is_debit_routed").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let total: Option<bigdecimal::BigDecimal> = row.try_get("total").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let count: Option<i64> = row.try_get("count").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        // Removing millisecond precision to get accurate diffs against clickhouse
        let start_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("start_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let end_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        Ok(Self {
            currency,
            status,
            connector,
            authentication_type,
            payment_method,
            payment_method_type,
            client_source,
            client_version,
            profile_id,
            card_network,
            merchant_id,
            card_last_4,
            card_issuer,
            error_reason,
            first_attempt,
            routing_approach,
            signature_network,
            is_issuer_regulated,
            is_debit_routed,
            total,
            count,
            start_bucket,
            end_bucket,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributionRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let status: Option<DBEnumWrapper<AttemptStatus>> =
            row.try_get("status").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let connector: Option<String> = row.try_get("connector").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let authentication_type: Option<DBEnumWrapper<AuthenticationType>> =
            row.try_get("authentication_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let payment_method: Option<String> =
            row.try_get("payment_method").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let payment_method_type: Option<String> =
            row.try_get("payment_method_type").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let client_source: Option<String> = row.try_get("client_source").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let client_version: Option<String> =
            row.try_get("client_version").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let profile_id: Option<String> = row.try_get("profile_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_network: Option<String> = row.try_get("card_network").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let merchant_id: Option<String> = row.try_get("merchant_id").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_last_4: Option<String> = row.try_get("card_last_4").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let card_issuer: Option<String> = row.try_get("card_issuer").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let error_reason: Option<String> = row.try_get("error_reason").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let routing_approach: Option<DBEnumWrapper<RoutingApproach>> =
            row.try_get("routing_approach").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;

        let signature_network: Option<String> =
            row.try_get("signature_network").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let is_issuer_regulated: Option<bool> =
            row.try_get("is_issuer_regulated").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let is_debit_routed: Option<bool> =
            row.try_get("is_debit_routed").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),
            })?;
        let total: Option<bigdecimal::BigDecimal> = row.try_get("total").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let count: Option<i64> = row.try_get("count").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let error_message: Option<String> = row.try_get("error_message").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        let first_attempt: Option<bool> = row.try_get("first_attempt").or_else(|e| match e {
            ColumnNotFound(_) => Ok(Default::default()),
            e => Err(e),
        })?;
        // Removing millisecond precision to get accurate diffs against clickhouse
        let start_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("start_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        let end_bucket: Option<PrimitiveDateTime> = row
            .try_get::<Option<PrimitiveDateTime>, _>("end_bucket")?
            .and_then(|dt| dt.replace_millisecond(0).ok());
        Ok(Self {
            currency,
            status,
            connector,
            authentication_type,
            payment_method,
            payment_method_type,
            client_source,
            client_version,
            profile_id,
            card_network,
            merchant_id,
            card_last_4,
            card_issuer,
            error_reason,
            first_attempt,
            total,
            count,
            error_message,
            routing_approach,
            signature_network,
            is_issuer_regulated,
            is_debit_routed,
            start_bucket,
            end_bucket,
        })
    }
}

impl<'a> FromRow<'a, PgRow> for super::payments::filters::PaymentFilterRow {
    fn from_row(row: &'a PgRow) -> sqlx::Result<Self> {
        let currency: Option<DBEnumWrapper<Currency>> =
            row.try_get("currency").or_else(|e| match e {
                ColumnNotFound(_) => Ok(Default::default()),
                e => Err(e),


File: crates/currency_conversion/src/error.rs

#[derive(Debug, thiserror::Error, serde::Serialize)]
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
pub enum CurrencyConversionError {
    #[error("Currency Conversion isn't possible")]
    DecimalMultiplicationFailed,
    #[error("Currency not supported: '{0}'")]
    ConversionNotSupported(String),
}


pub struct BankAccountDetail {
    pub routing_number: Option<Secret<String>>,
    pub pix_info: Option<PixInfo>,
    pub owner_name: Option<Secret<String>>,
    pub owner_document_type: Option<String>,
    pub owner_document_number: Option<Secret<String>>,
    pub owner_company: Option<OwnerCompany>,
    pub internal: Option<bool>,
    pub intermediary_bank_account: Option<Secret<String>>,
    pub intermediary_bank_account_id: Option<Secret<String>>,
    #[serde(rename = "id")]
    pub bank_account_id: Secret<String>,
    pub iban: Option<Secret<String>>,
    pub flow_type: Option<String>,
    pub currency: api_models::enums::Currency,
    pub branch_number: Option<Secret<String>>,
    pub branch_country: Option<String>,
    pub bank: Option<BankInfo>,
    pub account_type: Option<String>,
    pub account_number: Option<Secret<String>>,
    pub aba: Option<String>,
}


pub async fn pg_connection(
    pool: &PgPool,
) -> PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>> {
    pool.get()
        .await
        .expect("Couldn't retrieve PostgreSQL connection")
}


OpenAPI Block Path: components.schemas.CtpServiceProvider
{
  "type": "string",
  "enum": [
    "visa",
    "mastercard"
  ]
}


pub struct CalSuccessRateEventRequest {
    pub id: String,
    pub params: String,
    pub labels: Vec<String>,
    pub config: Option<CalSuccessRateConfigEventRequest>,
}


pub struct PaypalPayoutItem {
    amount: PayoutAmount,
    note: Option<String>,
    notification_language: String,
    #[serde(flatten)]
    payout_method_data: PaypalPayoutMethodData,
}