From 60771b2be4833c40c1f8068991e66807b070d4dc Mon Sep 17 00:00:00 2001 From: Malay Awasthi Date: Tue, 10 Sep 2024 10:27:00 +0530 Subject: [PATCH] added DuitNow --- Cargo.lock | 2 + config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + crates/hyperswitch_connectors/Cargo.toml | 2 + .../src/connectors/fiuu.rs | 50 ++-- .../src/connectors/fiuu/transformers.rs | 226 +++++++++++++----- crates/hyperswitch_connectors/src/utils.rs | 36 +++ crates/hyperswitch_interfaces/src/configs.rs | 13 +- 12 files changed, 247 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de688a61f79d..62a49f20d4b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3921,8 +3921,10 @@ dependencies = [ "http 0.2.12", "hyperswitch_domain_models", "hyperswitch_interfaces", + "image", "masking", "once_cell", + "qrcode", "rand", "regex", "reqwest 0.11.27", diff --git a/config/config.example.toml b/config/config.example.toml index 052b10d0b900..3e9fa224a46b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -209,6 +209,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 16803295896c..4b82a2a340b5 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -50,6 +50,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index cc16d88c169d..f8ba1f53dc55 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -54,6 +54,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com" fiuu.base_url = "https://pay.merchant.razer.com/" fiuu.secondary_base_url="https://api.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 8a8030db0527..1f4d762cc405 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -54,6 +54,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/config/development.toml b/config/development.toml index 27ac5bb3f5f1..be99dca186e5 100644 --- a/config/development.toml +++ b/config/development.toml @@ -217,6 +217,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 98da1c309862..d8f0aa43b6c1 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -138,6 +138,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index c0fdc2213aac..8eb3ddd56259 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -19,7 +19,9 @@ bytes = "1.6.0" error-stack = "0.4.1" hex = "0.4.3" http = "0.2.12" +image = { version = "0.25.1", default-features = false, features = ["png"] } once_cell = "1.19.0" +qrcode = "0.14.0" rand = "0.8.5" regex = "1.10.4" reqwest = { version = "0.11.27" } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 97b5092787f9..df79159c653b 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::collections::HashMap; -use common_enums::{CaptureMethod, PaymentMethodType}; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; use common_utils::{ errors::{self as common_errors, CustomResult}, ext_traits::BytesExt, @@ -230,13 +230,19 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}RMS/API/Direct/1.4.0/index.php", - self.base_url(connectors) - )) + match req.payment_method { + PaymentMethod::RealTimePayment => { + let base_url = connectors.fiuu.third_base_url.clone(); + Ok(format!("{}RMS/API/staticqr/index.php", base_url)) + } + _ => Ok(format!( + "{}RMS/API/Direct/1.4.0/index.php", + self.base_url(connectors) + )), + } } fn get_request_body( @@ -284,7 +290,7 @@ impl ConnectorIntegration CustomResult { let response: fiuu::FiuuPaymentsResponse = res .response - .parse_struct("Fiuu PaymentsAuthorizeResponse") + .parse_struct("Fiuu FiuuPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -317,11 +323,7 @@ impl ConnectorIntegration for Fiu _req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/gate-query/index.php", base_url)) } fn get_request_body( @@ -384,11 +386,7 @@ impl ConnectorIntegration fo _req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/capstxn/index.php", base_url)) } @@ -462,11 +460,7 @@ impl ConnectorIntegration for Fi _req: &PaymentsCancelRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/refund.php", base_url)) } @@ -531,11 +525,7 @@ impl ConnectorIntegration for Fiuu { _req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/index.php", base_url)) } @@ -607,11 +597,7 @@ impl ConnectorIntegration for Fiuu { _req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/q_by_txn.php", base_url)) } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index a45b31b8967f..24ef27f40461 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -1,15 +1,18 @@ use std::collections::HashMap; +use api_models::payments; use cards::CardNumber; use common_enums::{enums, CaptureMethod, Currency}; use common_utils::{ crypto::GenerateDigest, + errors::CustomResult, + ext_traits::Encode, request::Method, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{PaymentMethodData, RealTimePaymentData}, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsAuthorizeData, ResponseId}, @@ -23,13 +26,14 @@ use hyperswitch_interfaces::errors; use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use strum::Display; +use url::Url; use crate::{ types::{ PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData, PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, }, - utils::{PaymentsAuthorizeRequestData, RouterData as _}, + utils::{PaymentsAuthorizeRequestData, QrImage, RouterData as _}, }; pub struct FiuuRouterData { @@ -88,15 +92,39 @@ impl TryFrom> for TxnType { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Display, Debug)] #[serde(rename_all = "UPPERCASE")] enum TxnChannel { + #[serde(rename = "CREDITAN")] + #[strum(serialize = "CREDITAN")] Creditan, + #[serde(rename = "DuitNowSQR")] + #[strum(serialize = "DuitNowSQR")] + DuitNowSqr, } #[derive(Serialize, Debug, Deserialize)] +#[serde(untagged)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsRequest { +pub enum FiuuPaymentsRequest { + QRPaymentRequest(FiuuQRPaymentRequest), + CardPaymentRequest(FiuuCardPaymentRequest), +} + +#[derive(Serialize, Debug, Deserialize)] +pub struct FiuuQRPaymentRequest { + #[serde(rename = "merchantID")] + merchant_id: Secret, + channel: TxnChannel, + orderid: String, + currency: Currency, + amount: StringMajorUnit, + checksum: Secret, +} + +#[derive(Serialize, Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct FiuuCardPaymentRequest { #[serde(rename = "MerchantID")] merchant_id: Secret, reference_no: String, @@ -140,32 +168,59 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ let txn_amount = item.amount.clone(); let reference_no = item.router_data.connector_request_reference_id.clone(); let verify_key = auth.verify_key.peek().to_string(); - let signature = calculate_signature(format!( - "{}{merchant_id}{reference_no}{verify_key}", - txn_amount.get_amount_as_string() - ))?; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => Ok(Self { - merchant_id: auth.merchant_id, - reference_no, - txn_type: match item.router_data.request.is_auto_capture()? { - true => TxnType::Sals, - false => TxnType::Auts, - }, - txn_channel: TxnChannel::Creditan, - txn_currency, - txn_amount, - signature, - cc_pan: req_card.card_number, - cc_cvv2: req_card.card_cvc, - cc_month: req_card.card_exp_month, - cc_year: req_card.card_exp_year, - non_3ds: match item.router_data.is_three_ds() { - false => 1, - true => 0, - }, - return_url: item.router_data.request.router_return_url.clone(), - }), + PaymentMethodData::Card(req_card) => { + let signature = calculate_signature(format!( + "{}{merchant_id}{reference_no}{verify_key}", + txn_amount.get_amount_as_string() + ))?; + + Ok(Self::CardPaymentRequest(FiuuCardPaymentRequest { + merchant_id: auth.merchant_id, + reference_no, + txn_type: match item.router_data.request.is_auto_capture()? { + true => TxnType::Sals, + false => TxnType::Auts, + }, + txn_channel: TxnChannel::Creditan, + txn_currency, + txn_amount, + signature, + cc_pan: req_card.card_number, + cc_cvv2: req_card.card_cvc, + cc_month: req_card.card_exp_month, + cc_year: req_card.card_exp_year, + non_3ds: match item.router_data.is_three_ds() { + false => 1, + true => 0, + }, + return_url: item.router_data.request.router_return_url.clone(), + })) + } + PaymentMethodData::RealTimePayment(real_time_payment_data) => { + match *real_time_payment_data { + RealTimePaymentData::DuitNow {} => { + Ok(Self::QRPaymentRequest(FiuuQRPaymentRequest { + merchant_id: auth.merchant_id, + channel: TxnChannel::DuitNowSqr, + orderid: reference_no.clone(), + currency: txn_currency, + amount: txn_amount.clone(), + checksum: calculate_signature(format!( + "{merchant_id}{}{reference_no}{txn_currency}{}{verify_key}", + TxnChannel::DuitNowSqr, + txn_amount.get_amount_as_string() + ))?, + })) + } + RealTimePaymentData::Fps {} + | RealTimePaymentData::PromptPay {} + | RealTimePaymentData::VietQr {} => Err( + errors::ConnectorError::NotImplemented("Payment methods".to_string()) + .into(), + ), + } + } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } @@ -173,7 +228,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsSuccessResponse { +pub struct CardPaymentsResponse { pub reference_no: String, #[serde(rename = "TxnID")] pub txn_id: String, @@ -187,7 +242,8 @@ pub struct FiuuPaymentsSuccessResponse { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum FiuuPaymentsResponse { - Success(Box), + CardPaymentResponse(Box), + QRPaymentResponse(Box), Error(FiuuErrorResponse), } @@ -244,6 +300,23 @@ impl >, ) -> Result { match item.response { + FiuuPaymentsResponse::QRPaymentResponse(response) => Ok(Self { + status: match response.status { + false => enums::AttemptStatus::Failure, + true => enums::AttemptStatus::AuthenticationPending, + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: None, + mandate_reference: None, + connector_metadata: get_qr_metadata(&response)?, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), FiuuPaymentsResponse::Error(error) => Ok(Self { response: Err(ErrorResponse { code: error.error_code.clone(), @@ -255,7 +328,7 @@ impl }), ..item.data }), - FiuuPaymentsResponse::Success(data) => match data.txn_data.request_data { + FiuuPaymentsResponse::CardPaymentResponse(data) => match data.txn_data.request_data { RequestData::ThreeDS(three_ds_data) => { let form_fields = { let mut map = HashMap::new(); @@ -287,8 +360,8 @@ impl }) } - RequestData::NonThreeDS(non_threeds_data) => Ok(Self { - status: match non_threeds_data.status.as_str() { + RequestData::NonThreeDS(non_threeds_data) => { + let status = match non_threeds_data.status.as_str() { "00" => { if item.data.request.is_auto_capture()? { Ok(enums::AttemptStatus::Charged) @@ -301,19 +374,40 @@ impl other => Err(errors::ConnectorError::UnexpectedResponseError( bytes::Bytes::from(other.to_owned()), )), - }?, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(data.txn_id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data - }), + }?; + let response = if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: non_threeds_data + .error_code + .clone() + .unwrap_or_else(|| "NO_ERROR_CODE".to_string()), + message: non_threeds_data + .error_desc + .clone() + .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()), + reason: non_threeds_data.error_desc.clone(), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(data.txn_id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }) + }; + Ok(Self { + status, + response, + ..item.data + }) + } }, } } @@ -335,15 +429,9 @@ pub struct FiuuRefundRequest { #[derive(Debug, Serialize, Display)] pub enum RefundType { #[serde(rename = "P")] + #[strum(serialize = "P")] Partial, } -impl RefundType { - pub fn as_str(&self) -> &'static str { - match self { - Self::Partial => "P", - } - } -} impl TryFrom<&FiuuRouterData<&RefundsRouterData>> for FiuuRefundRequest { type Error = Report; @@ -362,7 +450,7 @@ impl TryFrom<&FiuuRouterData<&RefundsRouterData>> for FiuuRefundRequest amount: txn_amount.clone(), signature: calculate_signature(format!( "{}{merchant_id}{reference_no}{txn_id}{}{secret_key}", - RefundType::Partial.as_str(), + RefundType::Partial, txn_amount.get_amount_as_string() ))?, }) @@ -877,3 +965,31 @@ impl From for enums::RefundStatus { } } } +#[derive(Debug, Serialize, Deserialize)] +pub struct DuitNowQrCodeResponse { + status: bool, + qrcode_data: Secret, +} + +pub fn get_qr_metadata( + response: &DuitNowQrCodeResponse, +) -> CustomResult, errors::ConnectorError> { + let image_data = QrImage::new_from_data(response.qrcode_data.peek().clone()) + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + + let image_data_url = Url::parse(image_data.data.clone().as_str()).ok(); + let display_to_timestamp = None; + + if let Some(image_data_url) = image_data_url { + let qr_code_info = payments::QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp, + }; + + Some(qr_code_info.encode_to_value()) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } else { + Ok(None) + } +} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 808f2c956de8..b6d6073d3e23 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use api_models::payments::{self, Address, AddressDetails, OrderDetailsWithAmount, PhoneDetails}; +use base64::Engine; use common_enums::{ enums, enums::{CanadaStatesAbbreviation, FutureUsage, UsStatesAbbreviation}, @@ -23,6 +24,7 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{api, errors}; +use image::Luma; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; @@ -1529,3 +1531,37 @@ pub trait ForeignTryFrom: Sized { fn foreign_try_from(from: F) -> Result; } + +#[derive(Debug)] +pub struct QrImage { + pub data: String, +} +pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; +pub(crate) const BASE64_ENGINE: base64::engine::GeneralPurpose = + base64::engine::general_purpose::STANDARD; + +impl QrImage { + pub fn new_from_data( + data: String, + ) -> Result> { + let qr_code = qrcode::QrCode::new(data.as_bytes()) + .change_context(common_utils::errors::QrCodeError::FailedToCreateQrCode)?; + + let qrcode_image_buffer = qr_code.render::>().build(); + let qrcode_dynamic_image = image::DynamicImage::ImageLuma8(qrcode_image_buffer); + + let mut image_bytes = std::io::BufWriter::new(std::io::Cursor::new(Vec::new())); + + // Encodes qrcode_dynamic_image and write it to image_bytes + let _ = qrcode_dynamic_image.write_to(&mut image_bytes, image::ImageFormat::Png); + + let image_data_source = format!( + "{},{}", + QR_IMAGE_DATA_SOURCE_STRING, + BASE64_ENGINE.encode(image_bytes.buffer()) + ); + Ok(Self { + data: image_data_source, + }) + } +} diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 37e5eecea6ac..ef3e71164cc1 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -36,7 +36,7 @@ pub struct Connectors { pub ebanx: ConnectorParams, pub fiserv: ConnectorParams, pub fiservemea: ConnectorParams, - pub fiuu: ConnectorParams, + pub fiuu: ConnectorParamsWithThreeUrls, pub forte: ConnectorParams, pub globalpay: ConnectorParams, pub globepay: ConnectorParams, @@ -165,3 +165,14 @@ pub struct ConnectorParamsWithSecondaryBaseUrl { /// secondary base url pub secondary_base_url: String, } +/// struct ConnectorParamsWithThreeUrls +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithThreeUrls { + /// base url + pub base_url: String, + /// secondary base url + pub secondary_base_url: String, + /// third base url + pub third_base_url: String, +}