From 027080683d00a616e6dec40f57a4586951d76b09 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 04:41:36 +0000 Subject: [PATCH 01/12] chore(version): 2024.03.26.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e967966117dd..c4b683bada54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.03.26.0 + +### Features + +- **events:** Allow listing webhook events and webhook delivery attempts by business profile ([#4159](https://github.com/juspay/hyperswitch/pull/4159)) ([`4c8cdf1`](https://github.com/juspay/hyperswitch/commit/4c8cdf1475ac74fb2df5bea419dfa7657f26f298)) +- **payouts:** Add user roles for payouts ([#4167](https://github.com/juspay/hyperswitch/pull/4167)) ([`13fe584`](https://github.com/juspay/hyperswitch/commit/13fe58450bad094fb2b4745ecf76bc2df8b96798)) + +### Miscellaneous Tasks + +- Address Rust 1.77 clippy lints ([#4172](https://github.com/juspay/hyperswitch/pull/4172)) ([`f213c51`](https://github.com/juspay/hyperswitch/commit/f213c51b3e5c4f0b3546b35bac4dde9698818e01)) + +**Full Changelog:** [`2024.03.22.0...2024.03.26.0`](https://github.com/juspay/hyperswitch/compare/2024.03.22.0...2024.03.26.0) + +- - - + ## 2024.03.22.0 ### Features From 4f0c788cf26907e2be784978c412081a93386d04 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:04:46 +0530 Subject: [PATCH 02/12] fix(core): make eci in AuthenticationData optional (#4187) --- crates/router/src/connector/checkout/transformers.rs | 2 +- crates/router/src/core/payments/types.rs | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 8004b3b5a12a..64fddca13b55 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -391,7 +391,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme enums::AuthenticationType::ThreeDs => CheckoutThreeDS { enabled: true, force_3ds: true, - eci: authentication_data.map(|auth| auth.eci.clone()), + eci: authentication_data.and_then(|auth| auth.eci.clone()), cryptogram: authentication_data.map(|auth| auth.cavv.clone()), xid: authentication_data.map(|auth| auth.threeds_server_transaction_id.clone()), version: authentication_data.map(|auth| auth.message_version.clone()), diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 6e33b2f60e4a..ac54e1b7aa3b 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -379,7 +379,7 @@ impl SurchargeMetadata { #[derive(Debug, Clone)] pub struct AuthenticationData { - pub eci: String, + pub eci: Option, pub cavv: String, pub threeds_server_transaction_id: String, pub message_version: String, @@ -409,14 +409,8 @@ impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { .get_required_value("cavv") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("cavv must not be null when authentication_status is success")?; - let eci = authentication - .eci - .clone() - .get_required_value("eci") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("eci must not be null when authentication_status is success")?; Ok(Self { - eci, + eci: authentication.eci.clone(), cavv, threeds_server_transaction_id, message_version: message_version.to_string(), From 84bef251480a77027b43c3dc91353a0cb40d5ff1 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:39:37 +0530 Subject: [PATCH 03/12] fix(connector): [Trustpay] fix deserialization error for incoming webhook response for trustpay and add error code mapping '800.100.203' (#4199) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/connector/trustpay.rs | 2 +- .../src/connector/trustpay/transformers.rs | 47 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 18775dd39812..116cea9e2396 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -983,7 +983,7 @@ impl api::IncomingWebhook for Trustpay { dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id, connector_reason: reason.reason.reject_reason, - connector_reason_code: Some(reason.reason.code), + connector_reason_code: reason.reason.code, challenge_required_by: None, connector_status: payment_info.status.to_string(), created_at: None, diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 5c5227aedc47..9d5d2e3d749e 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -112,7 +112,7 @@ pub struct Amount { #[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] #[serde(rename_all = "PascalCase")] pub struct Reason { - pub code: String, + pub code: Option, pub reject_reason: Option, } @@ -510,6 +510,7 @@ fn is_payment_failed(payment_status: &str) -> (bool, &'static str) { "800.100.172" => (true, "Transaction declined (account blocked)"), "800.100.190" => (true, "Transaction declined (invalid configuration data)"), "800.100.202" => (true, "Account Closed"), + "800.100.203" => (true, "Insufficient Funds"), "800.120.100" => (true, "Rejected by throttling"), "800.300.102" => (true, "Country blacklisted"), "800.300.401" => (true, "Bin blacklisted"), @@ -823,9 +824,16 @@ fn handle_bank_redirects_sync_response( .status_reason_information .unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code.clone(), + code: reason_info + .reason + .code + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), // message vary for the same code, so relying on code alone as it is unique - message: reason_info.reason.code, + message: reason_info + .reason + .code + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: reason_info.reason.reject_reason, status_code, attempt_status: None, @@ -875,9 +883,16 @@ pub fn handle_webhook_response( .status_reason_information .unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code.clone(), + code: reason_info + .reason + .code + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), // message vary for the same code, so relying on code alone as it is unique - message: reason_info.reason.code, + message: reason_info + .reason + .code + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: reason_info.reason.reject_reason, status_code, attempt_status: None, @@ -1483,9 +1498,16 @@ fn handle_webhooks_refund_response( let error = if utils::is_refund_failure(refund_status) { let reason_info = response.status_reason_information.unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code.clone(), + code: reason_info + .reason + .code + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), // message vary for the same code, so relying on code alone as it is unique - message: reason_info.reason.code, + message: reason_info + .reason + .code + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: reason_info.reason.reject_reason, status_code, attempt_status: None, @@ -1540,9 +1562,16 @@ fn handle_bank_redirects_refund_sync_response( .status_reason_information .unwrap_or_default(); Some(types::ErrorResponse { - code: reason_info.reason.code.clone(), + code: reason_info + .reason + .code + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), // message vary for the same code, so relying on code alone as it is unique - message: reason_info.reason.code, + message: reason_info + .reason + .code + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: reason_info.reason.reject_reason, status_code, attempt_status: None, From 0429399c29f76c97bf2096bbe9e9b429c025e56b Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:49:25 +0530 Subject: [PATCH 04/12] fix(connector): [CRYPTOPAY] Skip metadata serialization if none (#4205) --- crates/router/src/connector/cryptopay/transformers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/cryptopay/transformers.rs b/crates/router/src/connector/cryptopay/transformers.rs index 635d4a66c262..707cfd8f06b6 100644 --- a/crates/router/src/connector/cryptopay/transformers.rs +++ b/crates/router/src/connector/cryptopay/transformers.rs @@ -49,6 +49,7 @@ pub struct CryptopayPaymentsRequest { pay_currency: String, success_redirect_url: Option, unsuccess_redirect_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] metadata: Option, custom_id: String, } From 7f5ad621a18bdf8fa57e51aa3d8c478756ac7da1 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:14:05 +0000 Subject: [PATCH 05/12] chore(version): 2024.03.27.0 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b683bada54..69d68df55c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.03.27.0 + +### Bug Fixes + +- **connector:** + - [Trustpay] fix deserialization error for incoming webhook response for trustpay and add error code mapping '800.100.203' ([#4199](https://github.com/juspay/hyperswitch/pull/4199)) ([`84bef25`](https://github.com/juspay/hyperswitch/commit/84bef251480a77027b43c3dc91353a0cb40d5ff1)) + - [CRYPTOPAY] Skip metadata serialization if none ([#4205](https://github.com/juspay/hyperswitch/pull/4205)) ([`0429399`](https://github.com/juspay/hyperswitch/commit/0429399c29f76c97bf2096bbe9e9b429c025e56b)) +- **core:** Make eci in AuthenticationData optional ([#4187](https://github.com/juspay/hyperswitch/pull/4187)) ([`4f0c788`](https://github.com/juspay/hyperswitch/commit/4f0c788cf26907e2be784978c412081a93386d04)) + +**Full Changelog:** [`2024.03.26.0...2024.03.27.0`](https://github.com/juspay/hyperswitch/compare/2024.03.26.0...2024.03.27.0) + +- - - + ## 2024.03.26.0 ### Features From 070622125f49c4cc9c35f5ba9c634f1fef6b26d2 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:46:22 +0530 Subject: [PATCH 06/12] fix(log): adding span metadata to `tokio` spawned futures (#4118) --- crates/common_utils/src/macros.rs | 7 --- crates/diesel_models/src/lib.rs | 1 - crates/diesel_models/src/macros.rs | 6 --- crates/drainer/src/handler.rs | 24 ++++++---- crates/drainer/src/lib.rs | 8 +++- crates/router/src/bin/scheduler.rs | 23 ++++++---- .../src/core/payments/flows/authorize_flow.rs | 45 +++++++++++-------- .../payments/operations/payment_confirm.rs | 45 +++++++++++-------- crates/router/src/lib.rs | 3 +- crates/router/tests/utils.rs | 3 +- crates/scheduler/src/consumer.rs | 8 +++- crates/scheduler/src/producer.rs | 8 +++- crates/storage_impl/src/redis.rs | 22 +++++---- 13 files changed, 117 insertions(+), 86 deletions(-) delete mode 100644 crates/diesel_models/src/macros.rs diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs index c07b2112db2c..b8d5743d8fe0 100644 --- a/crates/common_utils/src/macros.rs +++ b/crates/common_utils/src/macros.rs @@ -47,13 +47,6 @@ macro_rules! newtype { }; } -#[macro_export] -macro_rules! async_spawn { - ($t:block) => { - tokio::spawn(async move { $t }); - }; -} - /// Use this to ensure that the corresponding /// openapi route has been implemented in the openapi crate #[macro_export] diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index c93306f49d29..24df19ff7375 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -24,7 +24,6 @@ pub mod gsm; #[cfg(feature = "kv_store")] pub mod kv; pub mod locker_mock_up; -pub mod macros; pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; diff --git a/crates/diesel_models/src/macros.rs b/crates/diesel_models/src/macros.rs deleted file mode 100644 index de3596ecc10c..000000000000 --- a/crates/diesel_models/src/macros.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[macro_export] -macro_rules! async_spawn { - ($t:block) => { - tokio::spawn(async move { $t }); - }; -} diff --git a/crates/drainer/src/handler.rs b/crates/drainer/src/handler.rs index 5aa902d84c5a..47b60db80d51 100644 --- a/crates/drainer/src/handler.rs +++ b/crates/drainer/src/handler.rs @@ -1,5 +1,6 @@ use std::sync::{atomic, Arc}; +use router_env::tracing::Instrument; use tokio::{ sync::{mpsc, oneshot}, time::{self, Duration}, @@ -68,13 +69,16 @@ impl Handler { while self.running.load(atomic::Ordering::SeqCst) { metrics::DRAINER_HEALTH.add(&metrics::CONTEXT, 1, &[]); if self.store.is_stream_available(stream_index).await { - tokio::spawn(drainer_handler( - self.store.clone(), - stream_index, - self.conf.max_read_count, - self.active_tasks.clone(), - jobs_picked.clone(), - )); + let _task_handle = tokio::spawn( + drainer_handler( + self.store.clone(), + stream_index, + self.conf.max_read_count, + self.active_tasks.clone(), + jobs_picked.clone(), + ) + .in_current_span(), + ); } stream_index = utils::increment_stream_index( (stream_index, jobs_picked.clone()), @@ -116,10 +120,12 @@ impl Handler { let redis_conn_clone = self.store.redis_conn.clone(); // Spawn a task to monitor if redis is down or not - tokio::spawn(async move { redis_conn_clone.on_error(redis_error_tx).await }); + let _task_handle = tokio::spawn( + async move { redis_conn_clone.on_error(redis_error_tx).await }.in_current_span(), + ); //Spawns a task to send shutdown signal if redis goes down - tokio::spawn(redis_error_receiver(redis_error_rx, tx)); + let _task_handle = tokio::spawn(redis_error_receiver(redis_error_rx, tx).in_current_span()); Ok(()) } diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 0ed6183faef0..e7ae76213651 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -18,7 +18,10 @@ use common_utils::signals::get_allowed_signals; use diesel_models::kv; use error_stack::{IntoReport, ResultExt}; use hyperswitch_interfaces::secrets_interface::secret_state::RawSecret; -use router_env::{instrument, tracing}; +use router_env::{ + instrument, + tracing::{self, Instrument}, +}; use tokio::sync::mpsc; pub(crate) type Settings = crate::settings::Settings; @@ -39,7 +42,8 @@ pub async fn start_drainer(store: Arc, conf: DrainerSettings) -> errors:: "Failed while getting allowed signals".to_string(), ))?; let handle = signal.handle(); - let task_handle = tokio::spawn(common_utils::signals::signal_handler(signal, tx.clone())); + let task_handle = + tokio::spawn(common_utils::signals::signal_handler(signal, tx.clone()).in_current_span()); let handler_clone = drainer_handler.clone(); diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index c586bfecdb70..1df37e9f6d10 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -16,7 +16,10 @@ use router::{ services::{self, api}, workflows, }; -use router_env::{instrument, tracing}; +use router_env::{ + instrument, + tracing::{self, Instrument}, +}; use scheduler::{ consumer::workflows::ProcessTrackerWorkflow, errors::ProcessTrackerError, workflows::ProcessTrackerWorkflows, SchedulerAppState, @@ -49,10 +52,9 @@ async fn main() -> CustomResult<(), ProcessTrackerError> { .await; // channel to shutdown scheduler gracefully let (tx, rx) = mpsc::channel(1); - tokio::spawn(router::receiver_for_error( - redis_shutdown_signal_rx, - tx.clone(), - )); + let _task_handle = tokio::spawn( + router::receiver_for_error(redis_shutdown_signal_rx, tx.clone()).in_current_span(), + ); #[allow(clippy::expect_used)] let scheduler_flow_str = @@ -81,10 +83,13 @@ async fn main() -> CustomResult<(), ProcessTrackerError> { .await .expect("Failed to create the server"); - tokio::spawn(async move { - let _ = web_server.await; - logger::error!("The health check probe stopped working!"); - }); + let _task_handle = tokio::spawn( + async move { + let _ = web_server.await; + logger::error!("The health check probe stopped working!"); + } + .in_current_span(), + ); logger::debug!(startup_config=?state.conf); diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index efc0e8852da0..7757313d8676 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use error_stack; +use router_env::tracing::Instrument; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -125,26 +126,32 @@ impl Feature for types::PaymentsAu let state = state.clone(); logger::info!("Call to save_payment_method in locker"); - tokio::spawn(async move { - logger::info!("Starting async call to save_payment_method in locker"); - - let result = Box::pin(tokenization::save_payment_method( - &state, - &connector, - response, - &maybe_customer, - &merchant_account, - self.request.payment_method_type, - &key_store, - Some(resp.request.amount), - Some(resp.request.currency), - )) - .await; - - if let Err(err) = result { - logger::error!("Asynchronously saving card in locker failed : {:?}", err); + let _task_handle = tokio::spawn( + async move { + logger::info!("Starting async call to save_payment_method in locker"); + + let result = Box::pin(tokenization::save_payment_method( + &state, + &connector, + response, + &maybe_customer, + &merchant_account, + self.request.payment_method_type, + &key_store, + Some(resp.request.amount), + Some(resp.request.currency), + )) + .await; + + if let Err(err) = result { + logger::error!( + "Asynchronously saving card in locker failed : {:?}", + err + ); + } } - }); + .in_current_span(), + ); Ok(resp) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 2ab98ae124bf..cb4226bcfbec 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -160,18 +160,21 @@ impl let store = state.store.clone(); - let business_profile_fut = tokio::spawn(async move { - store - .find_business_profile_by_profile_id(&profile_id) - .map(|business_profile_result| { - business_profile_result.to_not_found_response( - errors::ApiErrorResponse::BusinessProfileNotFound { - id: profile_id.to_string(), - }, - ) - }) - .await - }); + let business_profile_fut = tokio::spawn( + async move { + store + .find_business_profile_by_profile_id(&profile_id) + .map(|business_profile_result| { + business_profile_result.to_not_found_response( + errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.to_string(), + }, + ) + }) + .await + } + .in_current_span(), + ); let store = state.store.clone(); @@ -498,13 +501,17 @@ impl let store = state.clone().store; - let additional_pm_data_fut = tokio::spawn(async move { - Ok(n_request_payment_method_data - .async_map(|payment_method_data| async move { - helpers::get_additional_payment_data(&payment_method_data, store.as_ref()).await - }) - .await) - }); + let additional_pm_data_fut = tokio::spawn( + async move { + Ok(n_request_payment_method_data + .async_map(|payment_method_data| async move { + helpers::get_additional_payment_data(&payment_method_data, store.as_ref()) + .await + }) + .await) + } + .in_current_span(), + ); let store = state.clone().store; diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index cc4adca77c9a..a8d6e792f3ca 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -31,6 +31,7 @@ use actix_web::{ }; use http::StatusCode; use hyperswitch_interfaces::secrets_interface::secret_state::SecuredSecret; +use router_env::tracing::Instrument; use routes::AppState; use storage_impl::errors::ApplicationResult; use tokio::sync::{mpsc, oneshot}; @@ -192,7 +193,7 @@ pub async fn start_server(conf: settings::Settings) -> Applicatio .workers(server.workers) .shutdown_timeout(server.shutdown_timeout) .run(); - tokio::spawn(receiver_for_error(rx, server.handle())); + let _task_handle = tokio::spawn(receiver_for_error(rx, server.handle()).in_current_span()); Ok(server) } diff --git a/crates/router/tests/utils.rs b/crates/router/tests/utils.rs index e1ab3e80f329..5d48d6e8ad08 100644 --- a/crates/router/tests/utils.rs +++ b/crates/router/tests/utils.rs @@ -12,6 +12,7 @@ use actix_web::{ }; use derive_deref::Deref; use router::{configs::settings::Settings, routes::AppState, services}; +use router_env::tracing::Instrument; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::{json, Value}; use tokio::sync::{oneshot, OnceCell}; @@ -24,7 +25,7 @@ async fn spawn_server() -> bool { .await .expect("failed to create server"); - let _server = tokio::spawn(server); + let _server = tokio::spawn(server.in_current_span()); true } diff --git a/crates/scheduler/src/consumer.rs b/crates/scheduler/src/consumer.rs index e069db28da7d..471f689ffc81 100644 --- a/crates/scheduler/src/consumer.rs +++ b/crates/scheduler/src/consumer.rs @@ -10,7 +10,10 @@ pub use diesel_models::{self, process_tracker as storage}; use error_stack::{IntoReport, ResultExt}; use futures::future; use redis_interface::{RedisConnectionPool, RedisEntryId}; -use router_env::{instrument, tracing}; +use router_env::{ + instrument, + tracing::{self, Instrument}, +}; use time::PrimitiveDateTime; use tokio::sync::mpsc; use uuid::Uuid; @@ -64,7 +67,8 @@ pub async fn start_consumer( .into_report() .attach_printable("Failed while creating a signals handler")?; let handle = signal.handle(); - let task_handle = tokio::spawn(common_utils::signals::signal_handler(signal, tx)); + let task_handle = + tokio::spawn(common_utils::signals::signal_handler(signal, tx).in_current_span()); 'consumer: loop { match rx.try_recv() { diff --git a/crates/scheduler/src/producer.rs b/crates/scheduler/src/producer.rs index bcf37cdf6f22..b8081c2b9ae4 100644 --- a/crates/scheduler/src/producer.rs +++ b/crates/scheduler/src/producer.rs @@ -3,7 +3,10 @@ use std::sync::Arc; use common_utils::errors::CustomResult; use diesel_models::enums::ProcessTrackerStatus; use error_stack::{report, IntoReport, ResultExt}; -use router_env::{instrument, tracing}; +use router_env::{ + instrument, + tracing::{self, Instrument}, +}; use time::Duration; use tokio::sync::mpsc; @@ -57,7 +60,8 @@ where .into_report() .attach_printable("Failed while creating a signals handler")?; let handle = signal.handle(); - let task_handle = tokio::spawn(common_utils::signals::signal_handler(signal, tx)); + let task_handle = + tokio::spawn(common_utils::signals::signal_handler(signal, tx).in_current_span()); loop { match rx.try_recv() { diff --git a/crates/storage_impl/src/redis.rs b/crates/storage_impl/src/redis.rs index e4e0c021ac84..be82d4cc293c 100644 --- a/crates/storage_impl/src/redis.rs +++ b/crates/storage_impl/src/redis.rs @@ -6,7 +6,7 @@ use std::sync::{atomic, Arc}; use error_stack::{IntoReport, ResultExt}; use redis_interface::PubsubInterface; -use router_env::logger; +use router_env::{logger, tracing::Instrument}; use self::{kv_store::RedisConnInterface, pub_sub::PubSubInterface}; @@ -35,9 +35,12 @@ impl RedisStore { pub fn set_error_callback(&self, callback: tokio::sync::oneshot::Sender<()>) { let redis_clone = self.redis_conn.clone(); - tokio::spawn(async move { - redis_clone.on_error(callback).await; - }); + let _task_handle = tokio::spawn( + async move { + redis_clone.on_error(callback).await; + } + .in_current_span(), + ); } pub async fn subscribe_to_channel( @@ -54,11 +57,14 @@ impl RedisStore { .change_context(redis_interface::errors::RedisError::SubscribeError)?; let redis_clone = self.redis_conn.clone(); - tokio::spawn(async move { - if let Err(e) = redis_clone.on_message().await { - logger::error!(pubsub_err=?e); + let _task_handle = tokio::spawn( + async move { + if let Err(e) = redis_clone.on_message().await { + logger::error!(pubsub_err=?e); + } } - }); + .in_current_span(), + ); Ok(()) } } From 929848f8713b45daf479ba24fb0a49b8e327b6fd Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:47:30 +0530 Subject: [PATCH 07/12] fix(connectors): fix wallet token deserialization error (#4133) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../router/src/compatibility/stripe/errors.rs | 10 ++++++++ .../connector/bankofamerica/transformers.rs | 7 +++++- .../src/connector/bluesnap/transformers.rs | 9 ++++--- .../braintree_graphql_transformers.rs | 15 ++++++----- .../src/connector/checkout/transformers.rs | 11 +++++--- .../src/connector/cybersource/transformers.rs | 13 +++++++--- .../src/connector/globalpay/transformers.rs | 2 +- .../src/connector/mollie/transformers.rs | 7 +++++- .../src/connector/nexinets/transformers.rs | 2 +- .../router/src/connector/noon/transformers.rs | 3 ++- .../src/connector/payme/transformers.rs | 7 +++--- .../src/connector/square/transformers.rs | 7 +++--- .../router/src/connector/stax/transformers.rs | 13 +++++----- .../src/connector/stripe/transformers.rs | 20 +++++++++------ crates/router/src/connector/utils.rs | 25 +++++++++++++------ .../router/src/connector/zen/transformers.rs | 8 ++++-- crates/router/src/core/errors.rs | 20 +++++++++++++-- .../src/core/errors/api_error_response.rs | 6 ++++- crates/router/src/core/errors/transformers.rs | 5 ++++ crates/router/src/core/errors/utils.rs | 10 +++++--- crates/router/src/core/payments/helpers.rs | 6 +++-- crates/storage_impl/src/errors.rs | 2 +- 22 files changed, 147 insertions(+), 61 deletions(-) diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index 60f4391bdf26..0181ec4e7998 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -51,6 +51,12 @@ pub enum StripeErrorCode { #[error(error_type = StripeErrorType::CardError, code = "invalid_card_type", message = "Card data is invalid")] InvalidCardType, + #[error( + error_type = StripeErrorType::ConnectorError, code = "invalid_wallet_token", + message = "Invalid {wallet_name} wallet token" + )] + InvalidWalletToken { wallet_name: String }, + #[error(error_type = StripeErrorType::ApiError, code = "refund_failed", message = "refund has failed")] RefundFailed, // stripe error code @@ -625,6 +631,9 @@ impl From for StripeErrorCode { } errors::ApiErrorResponse::CurrencyConversionFailed => Self::CurrencyConversionFailed, errors::ApiErrorResponse::PaymentMethodDeleteFailed => Self::PaymentMethodDeleteFailed, + errors::ApiErrorResponse::InvalidWalletToken { wallet_name } => { + Self::InvalidWalletToken { wallet_name } + } } } } @@ -671,6 +680,7 @@ impl actix_web::ResponseError for StripeErrorCode { | Self::PaymentIntentInvalidParameter { .. } | Self::SerdeQsError { .. } | Self::InvalidRequestData { .. } + | Self::InvalidWalletToken { .. } | Self::PreconditionFailed { .. } | Self::DuplicateMandate | Self::SuccessfulPaymentNotFound diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index b9a14321025a..b4b8ce87ec86 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -22,6 +22,7 @@ use crate::{ transformers::ForeignFrom, ApplePayPredecryptData, }, + unimplemented_payment_method, }; pub struct BankOfAmericaAuthType { @@ -708,7 +709,11 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> Self::try_from((item, decrypt_data, apple_pay_data)) } types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Bank Of America" + ))? } }, None => { diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index 24cc3069f3d4..a48e38705c42 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -310,14 +310,15 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues )) } api_models::payments::WalletData::ApplePay(payment_method_data) => { - let apple_pay_payment_data = payment_method_data - .get_applepay_decoded_payment_data() - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let apple_pay_payment_data = + payment_method_data.get_applepay_decoded_payment_data()?; let apple_pay_payment_data: ApplePayEncodedPaymentData = apple_pay_payment_data .expose()[..] .as_bytes() .parse_struct("ApplePayEncodedPaymentData") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?; let billing = item.router_data.get_billing()?.to_owned(); diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index 190cd2765728..78c4ee0e100a 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -10,6 +10,7 @@ use crate::{ core::errors, services, types::{self, api, storage::enums}, + unimplemented_payment_method, }; pub const CLIENT_TOKEN_MUTATION: &str = "mutation createClientToken($input: CreateClientTokenInput!) { createClientToken(input: $input) { clientToken}}"; @@ -1334,9 +1335,9 @@ impl input: PaymentInput { payment_method_id: match item.router_data.get_payment_method_token()? { types::PaymentMethodToken::Token(token) => token.into(), - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"), + )?, }, transaction: TransactionBody { amount: item.amount.to_owned(), @@ -1416,9 +1417,11 @@ fn get_braintree_redirect_form( .expose(), card_token: match payment_method_token { types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err(unimplemented_payment_method!( + "Apple Pay", + "Simplified", + "Braintree" + ))?, }, bin: match card_details { api_models::payments::PaymentMethodData::Card(card_details) => { diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 64fddca13b55..6328f98081d8 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -14,6 +14,7 @@ use crate::{ core::errors, services, types::{self, api, storage::enums, transformers::ForeignFrom}, + unimplemented_payment_method, }; #[derive(Debug, Serialize)] @@ -92,12 +93,12 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { api::PaymentMethodData::Wallet(wallet_data) => match wallet_data.clone() { api_models::payments::WalletData::GooglePay(_data) => { let json_wallet_data: CheckoutGooglePayData = - wallet_data.get_wallet_token_as_json()?; + wallet_data.get_wallet_token_as_json("Google Pay".to_string())?; Ok(Self::Googlepay(json_wallet_data)) } api_models::payments::WalletData::ApplePay(_data) => { let json_wallet_data: CheckoutApplePayData = - wallet_data.get_wallet_token_as_json()?; + wallet_data.get_wallet_token_as_json("Apple Pay".to_string())?; Ok(Self::Applepay(json_wallet_data)) } api_models::payments::WalletData::AliPayQr(_) @@ -308,7 +309,11 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme token: match item.router_data.get_payment_method_token()? { types::PaymentMethodToken::Token(token) => token.into(), types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + Err(unimplemented_payment_method!( + "Apple Pay", + "Simplified", + "Checkout" + ))? } }, })) diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index d9fab9a9025c..19d5e177226a 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -23,6 +23,7 @@ use crate::{ transformers::ForeignFrom, ApplePayPredecryptData, }, + unimplemented_payment_method, }; #[derive(Debug, Serialize)] @@ -139,9 +140,9 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { Some(PaymentSolution::ApplePay), ) } - types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::Token(_) => Err( + unimplemented_payment_method!("Apple Pay", "Manual", "Cybersource"), + )?, }, None => ( PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { @@ -980,7 +981,11 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> Self::try_from((item, decrypt_data, apple_pay_data)) } types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Cybersource" + ))? } }, None => { diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index 1e4109462513..35987ae26b32 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -459,7 +459,7 @@ fn get_wallet_data( api_models::payments::WalletData::GooglePay(_) => { Ok(PaymentMethodData::DigitalWallet(requests::DigitalWallet { provider: Some(requests::DigitalWalletProvider::PayByGoogle), - payment_token: wallet_data.get_wallet_token_as_json()?, + payment_token: wallet_data.get_wallet_token_as_json("Google Pay".to_string())?, })) } _ => Err(errors::ConnectorError::NotImplemented( diff --git a/crates/router/src/connector/mollie/transformers.rs b/crates/router/src/connector/mollie/transformers.rs index 4c18b59cd141..be0c0475fd73 100644 --- a/crates/router/src/connector/mollie/transformers.rs +++ b/crates/router/src/connector/mollie/transformers.rs @@ -15,6 +15,7 @@ use crate::{ core::errors, services, types, types::storage::enums as storage_enums, + unimplemented_payment_method, }; type Error = error_stack::Report; @@ -178,7 +179,11 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP card_token: Some(Secret::new(match pm_token { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + Err(unimplemented_payment_method!( + "Apple Pay", + "Simplified", + "Mollie" + ))? } })), }, diff --git a/crates/router/src/connector/nexinets/transformers.rs b/crates/router/src/connector/nexinets/transformers.rs index 9fdaaa36b360..dc871891145a 100644 --- a/crates/router/src/connector/nexinets/transformers.rs +++ b/crates/router/src/connector/nexinets/transformers.rs @@ -665,7 +665,7 @@ fn get_applepay_details( wallet_data: &api_models::payments::WalletData, applepay_data: &api_models::payments::ApplePayWalletData, ) -> CustomResult { - let payment_data = wallet_data.get_wallet_token_as_json()?; + let payment_data = wallet_data.get_wallet_token_as_json("Apple Pay".to_string())?; Ok(ApplePayDetails { payment_data, payment_method: ApplepayPaymentMethod { diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 08cde4041d6d..92def17aeae5 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -259,7 +259,8 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { api_models::payments::WalletData::ApplePay(apple_pay_data) => { let payment_token_data = NoonApplePayTokenData { token: NoonApplePayData { - payment_data: wallet_data.get_wallet_token_as_json()?, + payment_data: wallet_data + .get_wallet_token_as_json("Apple Pay".to_string())?, payment_method: NoonApplePayPaymentMethod { display_name: apple_pay_data.payment_method.display_name, network: apple_pay_data.payment_method.network, diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 5a84d7c4ed86..4db0a0bd07b5 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -21,6 +21,7 @@ use crate::{ core::errors, services, types::{self, api, storage::enums, MandateReference}, + unimplemented_payment_method, }; const LANGUAGE: &str = "en"; @@ -708,9 +709,9 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for Pay3dsRequest { let pm_token = item.get_payment_method_token()?; let buyer_key = match pm_token { types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Payme"), + )?, }; Ok(Self { buyer_email, diff --git a/crates/router/src/connector/square/transformers.rs b/crates/router/src/connector/square/transformers.rs index 03bd0ac83ee0..66a0282f9234 100644 --- a/crates/router/src/connector/square/transformers.rs +++ b/crates/router/src/connector/square/transformers.rs @@ -10,6 +10,7 @@ use crate::{ self, api, storage::{self, enums}, }, + unimplemented_payment_method, }; impl TryFrom<(&types::TokenizationRouterData, BankDebitData)> for SquareTokenRequest { @@ -257,9 +258,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { idempotency_key: Secret::new(item.attempt_id.clone()), source_id: Secret::new(match pm_token { types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Square"), + )?, }), amount_money: SquarePaymentsAmountData { amount: item.request.amount, diff --git a/crates/router/src/connector/stax/transformers.rs b/crates/router/src/connector/stax/transformers.rs index a9bb68d558e7..d28b5c1fa466 100644 --- a/crates/router/src/connector/stax/transformers.rs +++ b/crates/router/src/connector/stax/transformers.rs @@ -9,6 +9,7 @@ use crate::{ }, core::errors, types::{self, api, storage::enums}, + unimplemented_payment_method, }; #[derive(Debug, Serialize)] @@ -80,9 +81,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme pre_auth, payment_method_id: Secret::new(match pm_token { types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), + )?, }), idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) @@ -99,9 +100,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme pre_auth, payment_method_id: Secret::new(match pm_token { types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), + )?, }), idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index a4c1668816c6..860065b2b039 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -29,6 +29,7 @@ use crate::{ storage::enums, transformers::{ForeignFrom, ForeignTryFrom}, }, + unimplemented_payment_method, utils::OptionExt, }; @@ -1541,9 +1542,7 @@ impl TryFrom<(&payments::WalletData, Option)> if apple_pay_decrypt_data.is_none() { apple_pay_decrypt_data = Some(Self::Wallet(StripeWallet::ApplepayToken(StripeApplePay { - pk_token: applepay_data - .get_applepay_decoded_payment_data() - .change_context(errors::ConnectorError::RequestEncodingFailed)?, + pk_token: applepay_data.get_applepay_decoded_payment_data()?, pk_token_instrument_name: applepay_data .payment_method .pm_type @@ -1715,7 +1714,9 @@ impl TryFrom<&payments::GooglePayWalletData> for StripePaymentMethodData { .token .as_bytes() .parse_struct::("StripeGpayToken") - .change_context(errors::ConnectorError::RequestEncodingFailed)? + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Google Pay".to_string(), + })? .id, ), payment_type: StripePaymentMethodType::Card, @@ -1862,12 +1863,15 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { .payment_method_token .to_owned() .get_required_value("payment_token") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?; + let payment_method_token = match payment_method_token { types::PaymentMethodToken::Token(payment_method_token) => payment_method_token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } + types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Stripe"), + )?, }; Some(StripePaymentMethodData::Wallet( StripeWallet::ApplepayPayment(ApplepayPayment { diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index e510f3bfafc0..a41c05f19be0 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -900,7 +900,7 @@ fn get_card_issuer(card_number: &str) -> Result { } pub trait WalletData { fn get_wallet_token(&self) -> Result, Error>; - fn get_wallet_token_as_json(&self) -> Result + fn get_wallet_token_as_json(&self, wallet_name: String) -> Result where T: serde::de::DeserializeOwned; fn get_encoded_wallet_token(&self) -> Result; @@ -915,26 +915,31 @@ impl WalletData for api::WalletData { _ => Err(errors::ConnectorError::InvalidWallet.into()), } } - fn get_wallet_token_as_json(&self) -> Result + fn get_wallet_token_as_json(&self, wallet_name: String) -> Result where T: serde::de::DeserializeOwned, { serde_json::from_str::(self.get_wallet_token()?.peek()) .into_report() - .change_context(errors::ConnectorError::InvalidWalletToken) + .change_context(errors::ConnectorError::InvalidWalletToken { wallet_name }) } fn get_encoded_wallet_token(&self) -> Result { match self { Self::GooglePay(_) => { - let json_token: serde_json::Value = self.get_wallet_token_as_json()?; + let json_token: serde_json::Value = + self.get_wallet_token_as_json("Google Pay".to_owned())?; let token_as_vec = serde_json::to_vec(&json_token) .into_report() - .change_context(errors::ConnectorError::InvalidWalletToken)?; + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Google Pay".to_string(), + })?; let encoded_token = consts::BASE64_ENGINE.encode(token_as_vec); Ok(encoded_token) } - _ => Err(errors::ConnectorError::InvalidWalletToken.into()), + _ => Err( + errors::ConnectorError::NotImplemented("SELECTED PAYMENT METHOD".to_owned()).into(), + ), } } } @@ -950,10 +955,14 @@ impl ApplePay for payments::ApplePayWalletData { consts::BASE64_ENGINE .decode(&self.payment_data) .into_report() - .change_context(errors::ConnectorError::InvalidWalletToken)?, + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?, ) .into_report() - .change_context(errors::ConnectorError::InvalidWalletToken)?, + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?, ); Ok(token) } diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index b01b3f027a07..3e527423578b 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -473,13 +473,17 @@ impl ZenPaymentChannels::PclApplepay, session .apple_pay - .ok_or(errors::ConnectorError::RequestEncodingFailed)?, + .ok_or(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?, ), api_models::payments::WalletData::GooglePayRedirect(_) => ( ZenPaymentChannels::PclGooglepay, session .google_pay - .ok_or(errors::ConnectorError::RequestEncodingFailed)?, + .ok_or(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Google Pay".to_string(), + })?, ), api_models::payments::WalletData::WeChatPayRedirect(_) | api_models::payments::WalletData::PaypalRedirect(_) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index c468fea54c03..a7d36ff5128b 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -68,6 +68,22 @@ macro_rules! capture_method_not_supported { }; } +#[macro_export] +macro_rules! unimplemented_payment_method { + ($payment_method:expr, $connector:expr) => { + errors::ConnectorError::NotImplemented(format!( + "{} through {}", + $payment_method, $connector + )) + }; + ($payment_method:expr, $flow:expr, $connector:expr) => { + errors::ConnectorError::NotImplemented(format!( + "{} {} through {}", + $payment_method, $flow, $connector + )) + }; +} + macro_rules! impl_error_type { ($name: ident, $arg: tt) => { #[derive(Debug)] @@ -179,8 +195,8 @@ pub enum ConnectorError { InvalidDataFormat { field_name: &'static str }, #[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")] MismatchedPaymentData, - #[error("Failed to parse Wallet token")] - InvalidWalletToken, + #[error("Failed to parse {wallet_name} wallet token")] + InvalidWalletToken { wallet_name: String }, #[error("Missing Connector Related Transaction ID")] MissingConnectorRelatedTransactionID { id: String }, #[error("File Validation failed")] diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs index 3fc93e4e737a..b3fbbaaf141f 100644 --- a/crates/router/src/core/errors/api_error_response.rs +++ b/crates/router/src/core/errors/api_error_response.rs @@ -96,6 +96,11 @@ pub enum ApiErrorResponse { FileProviderNotSupported { message: String }, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] UnprocessableEntity { message: String }, + #[error( + error_type = ErrorType::ProcessingError, code = "IR_24", + message = "Invalid {wallet_name} wallet token" + )] + InvalidWalletToken { wallet_name: String }, #[error(error_type = ErrorType::ConnectorError, code = "CE_00", message = "{code}: {message}", ignore = "status_code")] ExternalConnectorError { code: String, @@ -122,7 +127,6 @@ pub enum ApiErrorResponse { VerificationFailed { data: Option }, #[error(error_type = ErrorType::ProcessingError, code = "CE_08", message = "Dispute operation failed while processing with connector. Retry operation")] DisputeFailed { data: Option }, - #[error(error_type = ErrorType::ServerNotAvailable, code = "HE_00", message = "Something went wrong")] InternalServerError, #[error(error_type = ErrorType::LockTimeout, code = "HE_00", message = "Resource is busy. Please try again later.")] diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index b570dda43751..110feb22df3c 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -96,6 +96,11 @@ impl ErrorSwitch for ApiErrorRespon AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None)) }, Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 23, message.to_string(), None)), + Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new( + "IR", + 24, + format!("Invalid {wallet_name} wallet token"), None + )), Self::ExternalConnectorError { code, message, diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index d9b4e190137d..f664c33681cd 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -204,7 +204,7 @@ impl ConnectorErrorExt for error_stack::Result | errors::ConnectorError::DateFormattingFailed | errors::ConnectorError::InvalidDataFormat { .. } | errors::ConnectorError::MismatchedPaymentData - | errors::ConnectorError::InvalidWalletToken + | errors::ConnectorError::InvalidWalletToken { .. } | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. } | errors::ConnectorError::FileValidationFailed { .. } | errors::ConnectorError::MissingConnectorRedirectionPayload { .. } @@ -265,6 +265,7 @@ impl ConnectorErrorExt for error_stack::Result errors::ConnectorError::InvalidDataFormat { field_name } => { errors::ApiErrorResponse::InvalidDataValue { field_name } }, + errors::ConnectorError::InvalidWalletToken {wallet_name} => errors::ApiErrorResponse::InvalidWalletToken {wallet_name: wallet_name.to_string()}, errors::ConnectorError::CurrencyNotSupported { message, connector} => errors::ApiErrorResponse::CurrencyNotSupported { message: format!("Credentials for the currency {message} are not configured with the connector {connector}/hyperswitch") }, errors::ConnectorError::FailedToObtainAuthType => errors::ApiErrorResponse::InvalidConnectorConfiguration {config: "connector_account_details".to_string()}, errors::ConnectorError::InvalidConnectorConfig { config } => errors::ApiErrorResponse::InvalidConnectorConfiguration { config: config.to_string() }, @@ -299,7 +300,6 @@ impl ConnectorErrorExt for error_stack::Result errors::ConnectorError::WebhookResponseEncodingFailed | errors::ConnectorError::InvalidDateFormat | errors::ConnectorError::DateFormattingFailed | - errors::ConnectorError::InvalidWalletToken | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. } | errors::ConnectorError::FileValidationFailed { .. } | errors::ConnectorError::MissingConnectorRedirectionPayload { .. } | @@ -347,6 +347,11 @@ impl ConnectorErrorExt for error_stack::Result config: field_name.to_string(), } } + errors::ConnectorError::InvalidWalletToken { wallet_name } => { + errors::ApiErrorResponse::InvalidWalletToken { + wallet_name: wallet_name.to_string(), + } + } errors::ConnectorError::RequestEncodingFailed | errors::ConnectorError::RequestEncodingFailedWithReason(_) | errors::ConnectorError::ParsingFailed @@ -384,7 +389,6 @@ impl ConnectorErrorExt for error_stack::Result | errors::ConnectorError::DateFormattingFailed | errors::ConnectorError::InvalidDataFormat { .. } | errors::ConnectorError::MismatchedPaymentData - | errors::ConnectorError::InvalidWalletToken | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. } | errors::ConnectorError::FileValidationFailed { .. } | errors::ConnectorError::MissingConnectorRedirectionPayload { .. } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 112e3e64d6d8..7eab4c0d26ac 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3605,8 +3605,10 @@ impl ApplePayData { pub fn token_json( wallet_data: api_models::payments::WalletData, ) -> CustomResult { - let json_wallet_data: Self = - connector::utils::WalletData::get_wallet_token_as_json(&wallet_data)?; + let json_wallet_data: Self = connector::utils::WalletData::get_wallet_token_as_json( + &wallet_data, + "Apple Pay".to_string(), + )?; Ok(json_wallet_data) } diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index 59ddacc5dc43..7002d0946f7e 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -374,7 +374,7 @@ pub enum ConnectorError { #[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")] MismatchedPaymentData, #[error("Failed to parse Wallet token")] - InvalidWalletToken, + InvalidWalletToken { wallet_name: String }, #[error("Missing Connector Related Transaction ID")] MissingConnectorRelatedTransactionID { id: String }, #[error("File Validation failed")] From 37be05d31d97651ddaa2c67b828d24563b35d37e Mon Sep 17 00:00:00 2001 From: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:37:49 +0530 Subject: [PATCH 08/12] feat(connector): [billwerk] add connector template code (#4123) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/api_models/src/enums.rs | 3 + crates/common_enums/src/enums.rs | 1 + crates/connector_configs/src/connector.rs | 2 + crates/router/src/configs/settings.rs | 1 + crates/router/src/connector.rs | 23 +- crates/router/src/connector/billwerk.rs | 561 ++++++++++++++++++ .../src/connector/billwerk/transformers.rs | 244 ++++++++ crates/router/src/core/admin.rs | 4 + crates/router/src/core/payments/flows.rs | 82 ++- crates/router/src/types/api.rs | 1 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/billwerk.rs | 421 +++++++++++++ crates/router/tests/connectors/main.rs | 2 + .../router/tests/connectors/sample_auth.toml | 4 + crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 2 + scripts/add_connector.sh | 2 +- 20 files changed, 1321 insertions(+), 39 deletions(-) create mode 100644 crates/router/src/connector/billwerk.rs create mode 100644 crates/router/src/connector/billwerk/transformers.rs create mode 100644 crates/router/tests/connectors/billwerk.rs diff --git a/config/config.example.toml b/config/config.example.toml index 81cd78f72445..ee868beb092b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -170,6 +170,7 @@ applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" +billwerk.base_url = "https://api.reepay.com/" bitpay.base_url = "https://test.bitpay.com" bluesnap.base_url = "https://sandbox.bluesnap.com/" bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" diff --git a/config/development.toml b/config/development.toml index 66ed76c8f3cd..8d18e63122f4 100644 --- a/config/development.toml +++ b/config/development.toml @@ -97,6 +97,7 @@ cards = [ "authorizedotnet", "bambora", "bankofamerica", + "billwerk", "bitpay", "bluesnap", "boku", @@ -165,6 +166,7 @@ applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" +billwerk.base_url = "https://api.reepay.com/" bitpay.base_url = "https://test.bitpay.com" bluesnap.base_url = "https://sandbox.bluesnap.com/" bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 7677fc4582d9..ceb00ff212e3 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -104,6 +104,7 @@ applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" +billwerk.base_url = "https://api.reepay.com/" bitpay.base_url = "https://test.bitpay.com" bluesnap.base_url = "https://sandbox.bluesnap.com/" bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" @@ -176,6 +177,7 @@ cards = [ "authorizedotnet", "bambora", "bankofamerica", + "billwerk", "bitpay", "bluesnap", "boku", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index f712871abe1c..b29377886d5e 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -78,6 +78,7 @@ pub enum Connector { Authorizedotnet, Bambora, Bankofamerica, + // Billwerk, Added as template code for future usage Bitpay, Bluesnap, Boku, @@ -165,6 +166,7 @@ impl Connector { | Self::Authorizedotnet | Self::Bambora | Self::Bankofamerica + // | Self::Billwerk Added as template code for future usage | Self::Bitpay | Self::Bluesnap | Self::Boku @@ -221,6 +223,7 @@ impl Connector { | Self::Authorizedotnet | Self::Bambora | Self::Bankofamerica + // | Self::Billwerk Added as template for future usage | Self::Bitpay | Self::Bluesnap | Self::Boku diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 89ca80e72b33..65842a4203da 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -115,6 +115,7 @@ pub enum RoutableConnectors { Airwallex, Authorizedotnet, Bankofamerica, + // Billwerk, Added as template code for future usage Bitpay, Bambora, Bluesnap, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 2ef80f8f2f69..74317319f500 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -116,6 +116,7 @@ pub struct ConnectorConfig { pub airwallex: Option, pub authorizedotnet: Option, pub bankofamerica: Option, + pub billwerk: Option, pub bitpay: Option, pub bluesnap: Option, pub boku: Option, @@ -227,6 +228,7 @@ impl ConnectorConfig { Connector::Airwallex => Ok(connector_data.airwallex), Connector::Authorizedotnet => Ok(connector_data.authorizedotnet), Connector::Bankofamerica => Ok(connector_data.bankofamerica), + // Connector::Billwerk => Ok(connector_data.billwerk), Added as template code for future usage Connector::Bitpay => Ok(connector_data.bitpay), Connector::Bluesnap => Ok(connector_data.bluesnap), Connector::Boku => Ok(connector_data.boku), diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 2f87c5c21d72..d92812cee406 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -480,6 +480,7 @@ pub struct Connectors { pub authorizedotnet: ConnectorParams, pub bambora: ConnectorParams, pub bankofamerica: ConnectorParams, + pub billwerk: ConnectorParams, pub bitpay: ConnectorParams, pub bluesnap: ConnectorParamsWithSecondaryBaseUrl, pub boku: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index eccdf3f9ddfa..d786ed4e31b7 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -4,6 +4,7 @@ pub mod airwallex; pub mod authorizedotnet; pub mod bambora; pub mod bankofamerica; +pub mod billwerk; pub mod bitpay; pub mod bluesnap; pub mod boku; @@ -60,15 +61,15 @@ pub mod zen; pub use self::dummyconnector::DummyConnector; pub use self::{ aci::Aci, adyen::Adyen, airwallex::Airwallex, authorizedotnet::Authorizedotnet, - bambora::Bambora, bankofamerica::Bankofamerica, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, - braintree::Braintree, cashtocode::Cashtocode, checkout::Checkout, coinbase::Coinbase, - cryptopay::Cryptopay, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv, forte::Forte, - globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, helcim::Helcim, - iatapay::Iatapay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, - nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, - payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, placetopay::Placetopay, - powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, - shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, - threedsecureio::Threedsecureio, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, - worldline::Worldline, worldpay::Worldpay, zen::Zen, + bambora::Bambora, bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, + bluesnap::Bluesnap, boku::Boku, braintree::Braintree, cashtocode::Cashtocode, + checkout::Checkout, coinbase::Coinbase, cryptopay::Cryptopay, cybersource::Cybersource, + dlocal::Dlocal, fiserv::Fiserv, forte::Forte, globalpay::Globalpay, globepay::Globepay, + gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, klarna::Klarna, mollie::Mollie, + multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, + opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, + placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, + riskified::Riskified, shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, + stripe::Stripe, threedsecureio::Threedsecureio, trustpay::Trustpay, tsys::Tsys, volt::Volt, + wise::Wise, worldline::Worldline, worldpay::Worldpay, zen::Zen, }; diff --git a/crates/router/src/connector/billwerk.rs b/crates/router/src/connector/billwerk.rs new file mode 100644 index 000000000000..c7d81e06c324 --- /dev/null +++ b/crates/router/src/connector/billwerk.rs @@ -0,0 +1,561 @@ +pub mod transformers; + +use std::fmt::Debug; + +use error_stack::{IntoReport, ResultExt}; +use masking::ExposeInterface; +use transformers as billwerk; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + events::connector_api_logs::ConnectorEvent, + headers, + services::{ + self, + request::{self, Mask}, + ConnectorIntegration, ConnectorValidation, + }, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + ErrorResponse, RequestContent, Response, + }, + utils::BytesExt, +}; + +#[derive(Debug, Clone)] +pub struct Billwerk; + +impl api::Payment for Billwerk {} +impl api::PaymentSession for Billwerk {} +impl api::ConnectorAccessToken for Billwerk {} +impl api::MandateSetup for Billwerk {} +impl api::PaymentAuthorize for Billwerk {} +impl api::PaymentSync for Billwerk {} +impl api::PaymentCapture for Billwerk {} +impl api::PaymentVoid for Billwerk {} +impl api::Refund for Billwerk {} +impl api::RefundExecute for Billwerk {} +impl api::RefundSync for Billwerk {} +impl api::PaymentToken for Billwerk {} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Billwerk +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Billwerk +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Billwerk { + fn id(&self) -> &'static str { + "billwerk" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.billwerk.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = billwerk::BillwerkAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: billwerk::BillwerkErrorResponse = res + .response + .parse_struct("BillwerkErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Billwerk { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration + for Billwerk +{ + //TODO: implement sessions flow +} + +impl ConnectorIntegration + for Billwerk +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Billwerk +{ +} + +impl ConnectorIntegration + for Billwerk +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = billwerk::BillwerkRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = billwerk::BillwerkPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: billwerk::BillwerkPaymentsResponse = res + .response + .parse_struct("Billwerk PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Billwerk +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: billwerk::BillwerkPaymentsResponse = res + .response + .parse_struct("billwerk PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Billwerk +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: billwerk::BillwerkPaymentsResponse = res + .response + .parse_struct("Billwerk PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Billwerk +{ +} + +impl ConnectorIntegration + for Billwerk +{ + fn get_headers( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = billwerk::BillwerkRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let connector_req = billwerk::BillwerkRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: billwerk::RefundResponse = res + .response + .parse_struct("billwerk RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Billwerk { + fn get_headers( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: billwerk::RefundResponse = res + .response + .parse_struct("billwerk RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Billwerk { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/billwerk/transformers.rs b/crates/router/src/connector/billwerk/transformers.rs new file mode 100644 index 000000000000..c6f025b78986 --- /dev/null +++ b/crates/router/src/connector/billwerk/transformers.rs @@ -0,0 +1,244 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + connector::utils::PaymentsAuthorizeRequestData, + core::errors, + types::{self, api, storage::enums}, +}; + +//TODO: Fill the struct with respective fields +pub struct BillwerkRouterData { + pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for BillwerkRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Ok(Self { + amount, + router_data: item, + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct BillwerkPaymentsRequest { + amount: i64, + card: BillwerkCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct BillwerkCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&BillwerkRouterData<&types::PaymentsAuthorizeRouterData>> for BillwerkPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &BillwerkRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + api::PaymentMethodData::Card(req_card) => { + let card = BillwerkCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.to_owned(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct BillwerkAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for BillwerkAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum BillwerkPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::AttemptStatus { + fn from(item: BillwerkPaymentStatus) -> Self { + match item { + BillwerkPaymentStatus::Succeeded => Self::Charged, + BillwerkPaymentStatus::Failed => Self::Failure, + BillwerkPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BillwerkPaymentsResponse { + status: BillwerkPaymentStatus, + id: String, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BillwerkPaymentsResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct BillwerkRefundRequest { + pub amount: i64, +} + +impl TryFrom<&BillwerkRouterData<&types::RefundsRouterData>> for BillwerkRefundRequest { + type Error = error_stack::Report; + fn try_from( + item: &BillwerkRouterData<&types::RefundsRouterData>, + ) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct BillwerkErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 2f546cfa5acc..a4be66242eeb 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1739,6 +1739,10 @@ pub(crate) fn validate_auth_and_metadata_type( bankofamerica::transformers::BankOfAmericaAuthType::try_from(val)?; Ok(()) } + // api_enums::Connector::Billwerk => { + // billwerk::transformers::BillwerkAuthType::try_from(val)?; + // Ok(()) + // } Added as template code for future usage api_enums::Connector::Bitpay => { bitpay::transformers::BitpayAuthType::try_from(val)?; Ok(()) diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index fe4a504861d1..6c952dc2593c 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -145,9 +145,9 @@ impl } default_imp_for_complete_authorize!( - connector::Threedsecureio, connector::Aci, connector::Adyen, + connector::Billwerk, connector::Bitpay, connector::Boku, connector::Cashtocode, @@ -176,6 +176,7 @@ default_imp_for_complete_authorize!( connector::Square, connector::Stax, connector::Stripe, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -211,13 +212,13 @@ impl { } default_imp_for_webhook_source_verification!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Braintree, @@ -257,6 +258,7 @@ default_imp_for_webhook_source_verification!( connector::Square, connector::Stax, connector::Stripe, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -294,13 +296,13 @@ impl } default_imp_for_create_customer!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -338,6 +340,7 @@ default_imp_for_create_customer!( connector::Shift4, connector::Signifyd, connector::Square, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -377,11 +380,11 @@ impl services::ConnectorRedirectResponse for connector::DummyConnec } default_imp_for_connector_redirect_response!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Bitpay, connector::Bankofamerica, + connector::Billwerk, connector::Boku, connector::Cashtocode, connector::Coinbase, @@ -410,6 +413,7 @@ default_imp_for_connector_redirect_response!( connector::Signifyd, connector::Square, connector::Stax, + connector::Threedsecureio, connector::Tsys, connector::Volt, connector::Wise, @@ -429,13 +433,13 @@ macro_rules! default_imp_for_connector_request_id { impl api::ConnectorTransactionId for connector::DummyConnector {} default_imp_for_connector_request_id!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -474,6 +478,7 @@ default_imp_for_connector_request_id!( connector::Square, connector::Stax, connector::Stripe, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -514,13 +519,13 @@ impl } default_imp_for_accept_dispute!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -560,6 +565,7 @@ default_imp_for_accept_dispute!( connector::Square, connector::Stax, connector::Stripe, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -619,13 +625,13 @@ impl } default_imp_for_file_upload!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -663,6 +669,7 @@ default_imp_for_file_upload!( connector::Signifyd, connector::Square, connector::Stax, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -701,13 +708,13 @@ impl } default_imp_for_submit_evidence!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -745,6 +752,7 @@ default_imp_for_submit_evidence!( connector::Signifyd, connector::Square, connector::Stax, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -783,13 +791,13 @@ impl } default_imp_for_defend_dispute!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -828,6 +836,7 @@ default_imp_for_defend_dispute!( connector::Square, connector::Stax, connector::Stripe, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -866,11 +875,11 @@ impl } default_imp_for_pre_processing_steps!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -905,6 +914,7 @@ default_imp_for_pre_processing_steps!( connector::Signifyd, connector::Square, connector::Stax, + connector::Threedsecureio, connector::Tsys, connector::Volt, connector::Wise, @@ -925,12 +935,12 @@ macro_rules! default_imp_for_payouts { impl api::Payouts for connector::DummyConnector {} default_imp_for_payouts!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -971,6 +981,7 @@ default_imp_for_payouts!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1008,12 +1019,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_create!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1054,6 +1065,7 @@ default_imp_for_payouts_create!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1094,12 +1106,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_eligibility!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1140,6 +1152,7 @@ default_imp_for_payouts_eligibility!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1177,12 +1190,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_fulfill!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1223,6 +1236,7 @@ default_imp_for_payouts_fulfill!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1260,12 +1274,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_cancel!( - connector::Threedsecureio, connector::Aci, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1306,6 +1320,7 @@ default_imp_for_payouts_cancel!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1343,13 +1358,13 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_quote!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1390,6 +1405,7 @@ default_imp_for_payouts_quote!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1427,13 +1443,13 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_recipient!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1474,6 +1490,7 @@ default_imp_for_payouts_recipient!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1510,13 +1527,13 @@ impl } default_imp_for_approve!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1557,6 +1574,7 @@ default_imp_for_approve!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1594,13 +1612,13 @@ impl } default_imp_for_reject!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1641,6 +1659,7 @@ default_imp_for_reject!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1662,13 +1681,13 @@ macro_rules! default_imp_for_fraud_check { impl api::FraudCheck for connector::DummyConnector {} default_imp_for_fraud_check!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1707,6 +1726,7 @@ default_imp_for_fraud_check!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1746,13 +1766,13 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_sale!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1791,6 +1811,7 @@ default_imp_for_frm_sale!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1830,13 +1851,13 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_checkout!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1875,6 +1896,7 @@ default_imp_for_frm_checkout!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1914,13 +1936,13 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_transaction!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -1959,6 +1981,7 @@ default_imp_for_frm_transaction!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -1998,13 +2021,13 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_fulfillment!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -2043,6 +2066,7 @@ default_imp_for_frm_fulfillment!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -2082,13 +2106,13 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_record_return!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -2127,6 +2151,7 @@ default_imp_for_frm_record_return!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -2164,13 +2189,13 @@ impl } default_imp_for_incremental_authorization!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -2210,6 +2235,7 @@ default_imp_for_incremental_authorization!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -2245,13 +2271,13 @@ impl { } default_imp_for_revoking_mandates!( - connector::Threedsecureio, connector::Aci, connector::Adyen, connector::Airwallex, connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -2290,6 +2316,7 @@ default_imp_for_revoking_mandates!( connector::Stax, connector::Stripe, connector::Shift4, + connector::Threedsecureio, connector::Trustpay, connector::Tsys, connector::Volt, @@ -2373,6 +2400,7 @@ default_imp_for_connector_authentication!( connector::Authorizedotnet, connector::Bambora, connector::Bankofamerica, + connector::Billwerk, connector::Bitpay, connector::Bluesnap, connector::Boku, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index cd9eee46eec2..95a0989ac2be 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -328,6 +328,7 @@ impl ConnectorData { enums::Connector::Authorizedotnet => Ok(Box::new(&connector::Authorizedotnet)), enums::Connector::Bambora => Ok(Box::new(&connector::Bambora)), enums::Connector::Bankofamerica => Ok(Box::new(&connector::Bankofamerica)), + // enums::Connector::Billwerk => Ok(Box::new(&connector::Billwerk)), Added as template code for future usage enums::Connector::Bitpay => Ok(Box::new(&connector::Bitpay)), enums::Connector::Bluesnap => Ok(Box::new(&connector::Bluesnap)), enums::Connector::Boku => Ok(Box::new(&connector::Boku)), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 29774be91af0..2f0c94799140 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -188,6 +188,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Authorizedotnet => Self::Authorizedotnet, api_enums::Connector::Bambora => Self::Bambora, api_enums::Connector::Bankofamerica => Self::Bankofamerica, + // api_enums::Connector::Billwerk => Self::Billwerk, Added as template code for future usage api_enums::Connector::Bitpay => Self::Bitpay, api_enums::Connector::Bluesnap => Self::Bluesnap, api_enums::Connector::Boku => Self::Boku, diff --git a/crates/router/tests/connectors/billwerk.rs b/crates/router/tests/connectors/billwerk.rs new file mode 100644 index 000000000000..1dec066e5d26 --- /dev/null +++ b/crates/router/tests/connectors/billwerk.rs @@ -0,0 +1,421 @@ +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct BillwerkTest; +impl ConnectorActions for BillwerkTest {} +impl utils::Connector for BillwerkTest { + fn get_data(&self) -> types::api::ConnectorData { + use router::connector::Billwerk; + types::api::ConnectorData { + connector: Box::new(&Billwerk), + // Added as Dummy connector as template code is added for future usage + connector_name: types::Connector::DummyConnector1, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .billwerk + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "billwerk".to_string() + } +} + +static CONNECTOR: BillwerkTest = BillwerkTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenerios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 8db743d6098e..eb428a36cb95 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -13,6 +13,8 @@ mod authorizedotnet; mod bambora; #[cfg(feature = "dummy_connector")] mod bankofamerica; +#[cfg(feature = "dummy_connector")] +mod billwerk; mod bitpay; mod bluesnap; mod boku; diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 3f78ff7f491d..bd17f279c2d0 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -198,3 +198,7 @@ key1= "Trankey" [threedsecureio] api_key="API Key" + + +[billwerk] +api_key="API Key" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index a2753a2c517b..34a5e35928df 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -18,6 +18,7 @@ pub struct ConnectorAuthentication { pub authorizedotnet: Option, pub bambora: Option, pub bankofamerica: Option, + pub billwerk: Option, pub bitpay: Option, pub bluesnap: Option, pub boku: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 6dfd921a2516..9a9a7606ca1d 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -71,6 +71,7 @@ applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" +billwerk.base_url = "https://api.reepay.com/" bitpay.base_url = "https://test.bitpay.com" bluesnap.base_url = "https://sandbox.bluesnap.com/" bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" @@ -142,6 +143,7 @@ cards = [ "authorizedotnet", "bambora", "bankofamerica", + "billwerk", "bitpay", "bluesnap", "boku", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 2be49d123b09..9098be40e218 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay "$1") + connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From 9798db4558d926a218a0ca6f7f7c4e24a187b3da Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:57:14 +0530 Subject: [PATCH 09/12] fix(trustpay): [Trustpay] Add error code mapping '800.100.100' (#4224) --- crates/router/src/connector/trustpay/transformers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 9d5d2e3d749e..d898bd248967 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -491,6 +491,7 @@ fn is_payment_failed(payment_status: &str) -> (bool, &'static str) { ), "700.500.001" => (true, "Referenced session contains too many transactions"), "700.500.003" => (true, "Test accounts not allowed in production"), + "800.100.100" => (true, "Transaction declined for unknown reason"), "800.100.151" => (true, "Transaction declined (invalid card)"), "800.100.152" => (true, "Transaction declined by authorization system"), "800.100.153" => (true, "Transaction declined (invalid CVV)"), From 246898fbb00a67d5791827527ce45e01b01b232c Mon Sep 17 00:00:00 2001 From: Jeeva Ramachandran <120017870+JeevaRamu0104@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:59:10 +0530 Subject: [PATCH 10/12] fix(euclid_wasm): checkout wasm metadata issue (#4198) Co-authored-by: Arjun Karthik --- crates/connector_configs/src/common_config.rs | 10 ++ .../src/response_modifier.rs | 121 +++++++----------- crates/connector_configs/src/transformer.rs | 16 +++ 3 files changed, 73 insertions(+), 74 deletions(-) diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index 2af5b88b337b..495c0e5b6ccf 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -82,6 +82,11 @@ pub struct ApiModelMetaData { pub apple_pay: Option, pub apple_pay_combined: Option, pub endpoint_prefix: Option, + pub mcc: Option, + pub merchant_country_code: Option, + pub merchant_name: Option, + pub acquirer_bin: Option, + pub acquirer_merchant_id: Option, } #[serde_with::skip_serializing_none] @@ -175,4 +180,9 @@ pub struct DashboardMetaData { pub apple_pay: Option, pub apple_pay_combined: Option, pub endpoint_prefix: Option, + pub mcc: Option, + pub merchant_country_code: Option, + pub merchant_name: Option, + pub acquirer_bin: Option, + pub acquirer_merchant_id: Option, } diff --git a/crates/connector_configs/src/response_modifier.rs b/crates/connector_configs/src/response_modifier.rs index 5942a9e01ae1..6fbf85f4fcf7 100644 --- a/crates/connector_configs/src/response_modifier.rs +++ b/crates/connector_configs/src/response_modifier.rs @@ -1,6 +1,7 @@ use crate::common_config::{ - CardProvider, ConnectorApiIntegrationPayload, DashboardMetaData, DashboardPaymentMethodPayload, - DashboardRequestPayload, GoogleApiModelData, GooglePayData, GpayDashboardPayLoad, Provider, + ApiModelMetaData, CardProvider, ConnectorApiIntegrationPayload, DashboardMetaData, + DashboardPaymentMethodPayload, DashboardRequestPayload, GoogleApiModelData, GooglePayData, + GpayDashboardPayLoad, Provider, }; impl ConnectorApiIntegrationPayload { @@ -275,52 +276,7 @@ impl ConnectorApiIntegrationPayload { card_provider: Some(credit_details), }; - let google_pay = Self::get_google_pay_metadata_response(response.clone()); - let account_name = match response.metadata.clone() { - Some(meta_data) => meta_data.account_name, - _ => None, - }; - - let merchant_account_id = match response.metadata.clone() { - Some(meta_data) => meta_data.merchant_account_id, - _ => None, - }; - let merchant_id = match response.metadata.clone() { - Some(meta_data) => meta_data.merchant_id, - _ => None, - }; - let terminal_id = match response.metadata.clone() { - Some(meta_data) => meta_data.terminal_id, - _ => None, - }; - let endpoint_prefix = match response.metadata.clone() { - Some(meta_data) => meta_data.endpoint_prefix, - _ => None, - }; - let apple_pay = match response.metadata.clone() { - Some(meta_data) => meta_data.apple_pay, - _ => None, - }; - let apple_pay_combined = match response.metadata.clone() { - Some(meta_data) => meta_data.apple_pay_combined, - _ => None, - }; - let merchant_config_currency = match response.metadata.clone() { - Some(meta_data) => meta_data.merchant_config_currency, - _ => None, - }; - - let meta_data = DashboardMetaData { - merchant_config_currency, - merchant_account_id, - apple_pay, - apple_pay_combined, - google_pay, - account_name, - terminal_id, - merchant_id, - endpoint_prefix, - }; + let meta_data = response.metadata.map(DashboardMetaData::from); DashboardRequestPayload { connector: response.connector_name, @@ -339,35 +295,52 @@ impl ConnectorApiIntegrationPayload { credit_details, gift_card, ]), - metadata: Some(meta_data), + metadata: meta_data, } } +} - pub fn get_google_pay_metadata_response(response: Self) -> Option { - match response.metadata { - Some(meta_data) => { - match meta_data.google_pay { - Some(google_pay) => match google_pay { - GoogleApiModelData::Standard(standard_data) => { - let data = standard_data.allowed_payment_methods.first().map( - |allowed_pm| { - allowed_pm.tokenization_specification.parameters.clone() - }, - )?; - Some(GooglePayData::Standard(GpayDashboardPayLoad { - gateway_merchant_id: data.gateway_merchant_id, - stripe_version: data.stripe_version, - stripe_publishable_key: data.stripe_publishable_key, - merchant_name: standard_data.merchant_info.merchant_name, - merchant_id: standard_data.merchant_info.merchant_id, - })) - } - GoogleApiModelData::Zen(data) => Some(GooglePayData::Zen(data)), - }, - None => None, - } - } - None => None, +impl From for DashboardMetaData { + fn from(api_model: ApiModelMetaData) -> Self { + Self { + merchant_config_currency: api_model.merchant_config_currency, + merchant_account_id: api_model.merchant_account_id, + account_name: api_model.account_name, + terminal_id: api_model.terminal_id, + merchant_id: api_model.merchant_id, + google_pay: get_google_pay_metadata_response(api_model.google_pay), + apple_pay: api_model.apple_pay, + apple_pay_combined: api_model.apple_pay_combined, + endpoint_prefix: api_model.endpoint_prefix, + mcc: api_model.mcc, + merchant_country_code: api_model.merchant_country_code, + merchant_name: api_model.merchant_name, + acquirer_bin: api_model.acquirer_bin, + acquirer_merchant_id: api_model.acquirer_merchant_id, } } } + +pub fn get_google_pay_metadata_response( + google_pay_data: Option, +) -> Option { + match google_pay_data { + Some(google_pay) => match google_pay { + GoogleApiModelData::Standard(standard_data) => { + let data = standard_data + .allowed_payment_methods + .first() + .map(|allowed_pm| allowed_pm.tokenization_specification.parameters.clone())?; + Some(GooglePayData::Standard(GpayDashboardPayLoad { + gateway_merchant_id: data.gateway_merchant_id, + stripe_version: data.stripe_version, + stripe_publishable_key: data.stripe_publishable_key, + merchant_name: standard_data.merchant_info.merchant_name, + merchant_id: standard_data.merchant_info.merchant_id, + })) + } + GoogleApiModelData::Zen(data) => Some(GooglePayData::Zen(data)), + }, + None => None, + } +} diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index 735178cd676a..09a28ac2b07a 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -187,6 +187,11 @@ impl DashboardRequestPayload { merchant_id: None, merchant_config_currency: None, endpoint_prefix: None, + mcc: None, + merchant_country_code: None, + merchant_name: None, + acquirer_bin: None, + acquirer_merchant_id: None, }; let meta_data = match request.metadata { Some(data) => data, @@ -201,6 +206,12 @@ impl DashboardRequestPayload { let apple_pay = meta_data.apple_pay; let apple_pay_combined = meta_data.apple_pay_combined; let merchant_config_currency = meta_data.merchant_config_currency; + let mcc = meta_data.mcc; + let merchant_country_code = meta_data.merchant_country_code; + let merchant_name = meta_data.merchant_name; + let acquirer_bin = meta_data.acquirer_bin; + let acquirer_merchant_id = meta_data.acquirer_merchant_id; + Some(ApiModelMetaData { google_pay, apple_pay, @@ -211,6 +222,11 @@ impl DashboardRequestPayload { merchant_config_currency, apple_pay_combined, endpoint_prefix, + mcc, + merchant_country_code, + merchant_name, + acquirer_bin, + acquirer_merchant_id, }) } From 9523cf4bbac488503c31640cade326095937e33c Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:46:53 +0530 Subject: [PATCH 11/12] fix(core): Amount capturable remain same for `processing` status in capture (#4229) --- crates/router/src/types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index e0ab81311991..6f73efa832e4 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -723,9 +723,9 @@ impl Capturable for PaymentsCaptureData { let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); match intent_status { common_enums::IntentStatus::Succeeded - | common_enums::IntentStatus::PartiallyCaptured - | common_enums::IntentStatus::Processing => Some(0), - common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::PartiallyCaptured => Some(0), + common_enums::IntentStatus::Processing + | common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Failed | common_enums::IntentStatus::RequiresCustomerAction | common_enums::IntentStatus::RequiresMerchantAction From 65874728094bb550d6c311965fbb5f1577091bbb Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Wed, 27 Mar 2024 20:26:04 +0530 Subject: [PATCH 12/12] refactor(config): allow wildcard origin for development and Docker Compose setups (#4231) --- config/development.toml | 4 ++-- config/docker_compose.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/development.toml b/config/development.toml index 8d18e63122f4..5c063acf2f71 100644 --- a/config/development.toml +++ b/config/development.toml @@ -239,9 +239,9 @@ workers = 1 [cors] max_age = 30 -origins = "http://localhost:8080,http://localhost:9000" +# origins = "http://localhost:8080,http://localhost:9000" allowed_methods = "GET,POST,PUT,DELETE" -wildcard_origin = false +wildcard_origin = true [email] sender_email = "example@example.com" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ceb00ff212e3..0e034f2e1457 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -84,9 +84,9 @@ max_feed_count = 200 [cors] max_age = 30 -origins = "http://localhost:8080,http://localhost:9000" +# origins = "http://localhost:8080,http://localhost:9000" allowed_methods = "GET,POST,PUT,DELETE" -wildcard_origin = false +wildcard_origin = true [refund] max_attempts = 10