diff --git a/Cargo.lock b/Cargo.lock index de688a61f7..b32d26bcc1 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", @@ -6226,7 +6228,6 @@ dependencies = [ "hyperswitch_constraint_graph", "hyperswitch_domain_models", "hyperswitch_interfaces", - "image", "infer", "iso_currency", "isocountry", @@ -6244,7 +6245,6 @@ dependencies = [ "openidconnect", "openssl", "pm_auth", - "qrcode", "quick-xml", "rand", "rand_chacha", diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 8e4112e80a..f7b817bccc 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -2487,6 +2487,7 @@ "alliance_bank", "am_bank", "bank_of_america", + "bank_of_china", "bank_islam", "bank_muamalat", "bank_rakyat", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 1facbee595..4bf7274e52 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -6117,6 +6117,7 @@ "alliance_bank", "am_bank", "bank_of_america", + "bank_of_china", "bank_islam", "bank_muamalat", "bank_rakyat", diff --git a/config/config.example.toml b/config/config.example.toml index afdbdc954b..4c543fbdec 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/" @@ -555,6 +556,7 @@ payout_connector_list = "stripe,wise" [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 142eaaa6c0..4f109fc612 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -50,6 +51,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 e1b4f0a36f..9c387aa10a 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -54,6 +55,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 3b7993b4b1..706bcb6e1b 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -54,6 +55,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 687f0dc1ec..0b6c1c7690 100644 --- a/config/development.toml +++ b/config/development.toml @@ -218,6 +218,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/" @@ -337,6 +338,7 @@ adyen = { banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,hal [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index e3587986ee..bc1be36f82 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/" @@ -430,6 +431,7 @@ ach = { currency = "USD" } [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 46690ab7b0..3e85a7c336 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2822,6 +2822,7 @@ pub enum BankNames { AllianceBank, AmBank, BankOfAmerica, + BankOfChina, BankIslam, BankMuamalat, BankRakyat, diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index c0fdc2213a..8eb3ddd562 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 97b5092787..df79159c65 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 a45b31b896..561447fc07 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_enums::{enums, BankNames, 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::{BankRedirectData, 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::{self, PaymentsAuthorizeRequestData, QrImage, RouterData as _}, }; pub struct FiuuRouterData { @@ -88,15 +92,108 @@ 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, Deserialize, Display, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum FPXTxnChannel { + FpxAbb, + FpxUob, + FpxAbmb, + FpxScb, + FpxBsn, + FpxKfh, + FpxBmmb, + FpxBkrm, + FpxHsbc, + FpxAgrobank, + FpxBocm, + FpxMb2u, + FpxCimbclicks, + FpxAmb, + FpxHlb, + FpxPbb, + FpxRhb, + FpxBimb, + FpxOcbc, +} +impl TryFrom for FPXTxnChannel { + type Error = Report; + fn try_from(bank_names: BankNames) -> Result { + match bank_names { + BankNames::AffinBank => Ok(Self::FpxAbb), + BankNames::AgroBank => Ok(Self::FpxAgrobank), + BankNames::AllianceBank => Ok(Self::FpxAbmb), + BankNames::AmBank => Ok(Self::FpxAmb), + BankNames::BankOfChina => Ok(Self::FpxBocm), + BankNames::BankIslam => Ok(Self::FpxBimb), + BankNames::BankMuamalat => Ok(Self::FpxBmmb), + BankNames::BankRakyat => Ok(Self::FpxBkrm), + BankNames::BankSimpananNasional => Ok(Self::FpxBsn), + BankNames::CimbBank => Ok(Self::FpxCimbclicks), + BankNames::HongLeongBank => Ok(Self::FpxHlb), + BankNames::HsbcBank => Ok(Self::FpxHsbc), + BankNames::KuwaitFinanceHouse => Ok(Self::FpxKfh), + BankNames::Maybank => Ok(Self::FpxMb2u), + BankNames::PublicBank => Ok(Self::FpxPbb), + BankNames::RhbBank => Ok(Self::FpxRhb), + BankNames::StandardCharteredBank => Ok(Self::FpxScb), + BankNames::UobBank => Ok(Self::FpxUob), + BankNames::OcbcBank => Ok(Self::FpxOcbc), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Fiuu"), + ))?, + } + } +} + +#[derive(Serialize, Debug, Deserialize)] +#[serde(untagged)] +#[serde(rename_all = "PascalCase")] +pub enum FiuuPaymentsRequest { + QRPaymentRequest(FiuuQRPaymentRequest), + CardPaymentRequest(FiuuCardPaymentRequest), + FpxPaymentRequest(FiuuFPXPyamentRequest), } #[derive(Serialize, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsRequest { +pub struct FiuuFPXPyamentRequest { + #[serde(rename = "MerchantID")] + merchant_id: Secret, + reference_no: String, + txn_type: TxnType, + txn_channel: FPXTxnChannel, + txn_currency: Currency, + txn_amount: StringMajorUnit, + signature: Secret, + #[serde(rename = "ReturnURL")] + return_url: Option, +} +#[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 +237,77 @@ 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(), + ), + } + } + PaymentMethodData::BankRedirect(BankRedirectData::OnlineBankingFpx { issuer }) => { + Ok(Self::FpxPaymentRequest(FiuuFPXPyamentRequest { + merchant_id: auth.merchant_id.clone(), + reference_no: reference_no.clone(), + txn_type: match item.router_data.request.is_auto_capture()? { + true => TxnType::Sals, + false => TxnType::Auts, + }, + txn_channel: FPXTxnChannel::try_from(issuer)?, + txn_currency, + txn_amount: txn_amount.clone(), + signature: calculate_signature(format!( + "{}{merchant_id}{reference_no}{verify_key}", + txn_amount.get_amount_as_string() + ))?, + return_url: item.router_data.request.router_return_url.clone(), + })) + } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } @@ -173,7 +315,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsSuccessResponse { +pub struct PaymentsResponse { pub reference_no: String, #[serde(rename = "TxnID")] pub txn_id: String, @@ -184,10 +326,17 @@ pub struct FiuuPaymentsSuccessResponse { pub txn_data: TxnData, } +#[derive(Debug, Serialize, Deserialize)] +pub struct DuitNowQrCodeResponse { + status: bool, + qrcode_data: Secret, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum FiuuPaymentsResponse { - Success(Box), + PaymentResponse(Box), + QRPaymentResponse(Box), Error(FiuuErrorResponse), } @@ -208,17 +357,11 @@ pub enum RequestType { Response, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct ThreeDSResponseData { - #[serde(rename = "paRes")] - pa_res: String, -} - #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum RequestData { NonThreeDS(NonThreeDSResponseData), - ThreeDS(ThreeDSResponseData), + RedirectData(Option>), } #[derive(Debug, Serialize, Deserialize)] pub struct NonThreeDSResponseData { @@ -244,6 +387,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,13 +415,8 @@ impl }), ..item.data }), - FiuuPaymentsResponse::Success(data) => match data.txn_data.request_data { - RequestData::ThreeDS(three_ds_data) => { - let form_fields = { - let mut map = HashMap::new(); - map.insert("paRes".to_string(), three_ds_data.pa_res.clone()); - map - }; + FiuuPaymentsResponse::PaymentResponse(data) => match data.txn_data.request_data { + RequestData::RedirectData(redirection_data) => { let redirection_data = Some(RedirectForm::Form { endpoint: data.txn_data.request_url.to_string(), method: if data.txn_data.request_method.as_str() == "POST" { @@ -269,7 +424,7 @@ impl } else { Method::Get }, - form_fields, + form_fields: redirection_data.unwrap_or_default(), }); Ok(Self { status: enums::AttemptStatus::AuthenticationPending, @@ -286,9 +441,8 @@ impl ..item.data }) } - - 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 +455,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 +510,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 +531,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 +1046,26 @@ impl From for enums::RefundStatus { } } } + +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 01e9dd14b6..6a6f3bdc52 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; use api_models::payments::{self, Address, AddressDetails, OrderDetailsWithAmount, PhoneDetails}; +use base64::Engine; use common_enums::{ enums, enums::{CanadaStatesAbbreviation, FutureUsage, UsStatesAbbreviation}, }; use common_utils::{ + consts::BASE64_ENGINE, errors::{CustomResult, ReportSwitchExt}, ext_traits::{OptionExt, StringExt, ValueExt}, id_type, @@ -23,6 +25,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; @@ -1540,3 +1543,48 @@ pub trait ForeignTryFrom: Sized { fn foreign_try_from(from: F) -> Result; } + +#[derive(Debug)] +pub struct QrImage { + pub data: String, +} + +// Qr Image data source starts with this string +// The base64 image data will be appended to it to image data source +pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; + +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, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::utils; + #[test] + fn test_image_data_source_url() { + let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); + assert!(qr_image_data_source_url.is_ok()); + } +} diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index eae30eeb67..97223040ec 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, @@ -166,3 +166,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, +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index a614e5d1e7..0c6e06cc42 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -70,7 +70,6 @@ futures = "0.3.30" hex = "0.4.3" http = "0.2.12" hyper = "0.14.28" -image = { version = "0.25.1", default-features = false, features = ["png"] } infer = "0.15.0" iso_currency = "0.4.4" isocountry = "0.3.2" @@ -85,7 +84,6 @@ num-traits = "0.2.19" once_cell = "1.19.0" openidconnect = "3.5.0" # TODO: remove reqwest openssl = "0.10.64" -qrcode = "0.14.0" quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" rand_chacha = "0.3.1" diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 9066d55604..2539c5c52f 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -942,6 +942,7 @@ impl TryFrom<&common_enums::BankNames> for OpenBankingUKIssuer { | common_enums::BankNames::AllianceBank | common_enums::BankNames::AmBank | common_enums::BankNames::BankOfAmerica + | common_enums::BankNames::BankOfChina | common_enums::BankNames::BankIslam | common_enums::BankNames::BankMuamalat | common_enums::BankNames::BankRakyat diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/router/src/connector/multisafepay/transformers.rs index 5466d56c64..c44cf14ab6 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/router/src/connector/multisafepay/transformers.rs @@ -243,6 +243,7 @@ impl TryFrom<&BankNames> for MultisafepayBankNames { | BankNames::AllianceBank | BankNames::AmBank | BankNames::BankOfAmerica + | BankNames::BankOfChina | BankNames::BankIslam | BankNames::BankMuamalat | BankNames::BankRakyat diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index e671164082..2547443484 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -618,6 +618,7 @@ impl TryFrom for NuveiBIC { | common_enums::enums::BankNames::AllianceBank | common_enums::enums::BankNames::AmBank | common_enums::enums::BankNames::BankOfAmerica + | common_enums::enums::BankNames::BankOfChina | common_enums::enums::BankNames::BankIslam | common_enums::enums::BankNames::BankMuamalat | common_enums::enums::BankNames::BankRakyat diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 70cfb188ea..3d4a29e0f3 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -58,10 +58,6 @@ pub(crate) const API_KEY_LENGTH: usize = 64; pub(crate) const APPLEPAY_VALIDATION_URL: &str = "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"; -// Qr Image data source starts with this string -// The base64 image data will be appended to it to image data source -pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; - // OID (Object Identifier) for the merchant ID field extension. pub(crate) const MERCHANT_ID_FIELD_EXTENSION_ID: &str = "1.2.840.113635.100.6.32"; diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index db92ea0362..5fab106061 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -20,7 +20,6 @@ use api_models::{ payments::{self}, webhooks, }; -use base64::Engine; use common_utils::types::keymanager::KeyManagerState; pub use common_utils::{ crypto, @@ -35,12 +34,11 @@ use common_utils::{ types::keymanager::{Identifier, ToEncryptable}, }; use error_stack::ResultExt; +pub use hyperswitch_connectors::utils::QrImage; use hyperswitch_domain_models::payments::PaymentIntent; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation}; -use image::Luma; use nanoid::nanoid; -use qrcode; use router_env::metrics::add_attributes; use serde::de::DeserializeOwned; use serde_json::Value; @@ -166,37 +164,6 @@ impl ConnectorResponseExt pub fn get_payout_attempt_id(payout_id: impl std::fmt::Display, attempt_count: i16) -> String { format!("{payout_id}_{attempt_count}") } -#[derive(Debug)] -pub struct QrImage { - pub data: String, -} - -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)?; - - // Renders the QR code into an image. - 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!( - "{},{}", - consts::QR_IMAGE_DATA_SOURCE_STRING, - consts::BASE64_ENGINE.encode(image_bytes.buffer()) - ); - Ok(Self { - data: image_data_source, - }) - } -} pub async fn find_payment_intent_from_payment_id_type( state: &SessionState, @@ -1186,13 +1153,3 @@ pub async fn flatten_join_error(handle: Handle) -> RouterResult { .attach_printable("Join Error"), } } - -#[cfg(test)] -mod tests { - use crate::utils; - #[test] - fn test_image_data_source_url() { - let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); - assert!(qr_image_data_source_url.is_ok()); - } -} diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index f352644ea7..b1f74f57fe 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -104,6 +104,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/"