From 58cc8d6109ce49d385b06c762ab3f6670f5094eb Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:06:47 +0530 Subject: [PATCH 01/48] fix(connector_onboarding): Check if connector exists for the merchant account and add reset tracking id API (#3229) --- crates/api_models/src/connector_onboarding.rs | 6 + .../src/events/connector_onboarding.rs | 4 +- crates/router/src/consts.rs | 3 + .../router/src/core/connector_onboarding.rs | 44 +++++-- .../src/core/connector_onboarding/paypal.rs | 20 ++-- crates/router/src/routes/app.rs | 1 + .../router/src/routes/connector_onboarding.rs | 21 +++- crates/router/src/routes/lock_utils.rs | 4 +- .../router/src/utils/connector_onboarding.rs | 109 +++++++++++++++++- crates/router_env/src/logger/types.rs | 2 + 10 files changed, 186 insertions(+), 28 deletions(-) diff --git a/crates/api_models/src/connector_onboarding.rs b/crates/api_models/src/connector_onboarding.rs index 759d3cb97f13..7e8288d9747f 100644 --- a/crates/api_models/src/connector_onboarding.rs +++ b/crates/api_models/src/connector_onboarding.rs @@ -52,3 +52,9 @@ pub struct PayPalOnboardingDone { pub struct PayPalIntegrationDone { pub connector_id: String, } + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct ResetTrackingIdRequest { + pub connector_id: String, + pub connector: enums::Connector, +} diff --git a/crates/api_models/src/events/connector_onboarding.rs b/crates/api_models/src/events/connector_onboarding.rs index 998dc384d620..0da89f61da7e 100644 --- a/crates/api_models/src/events/connector_onboarding.rs +++ b/crates/api_models/src/events/connector_onboarding.rs @@ -2,11 +2,13 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::connector_onboarding::{ ActionUrlRequest, ActionUrlResponse, OnboardingStatus, OnboardingSyncRequest, + ResetTrackingIdRequest, }; common_utils::impl_misc_api_event_type!( ActionUrlRequest, ActionUrlResponse, OnboardingSyncRequest, - OnboardingStatus + OnboardingStatus, + ResetTrackingIdRequest ); diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index ed020b0c7e0f..387da3c06415 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -77,6 +77,9 @@ pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify"; #[cfg(feature = "olap")] pub const VERIFY_CONNECTOR_MERCHANT_ID: &str = "test_merchant"; +#[cfg(feature = "olap")] +pub const CONNECTOR_ONBOARDING_CONFIG_PREFIX: &str = "onboarding"; + /// Max payment session expiry pub const MAX_SESSION_EXPIRY: u32 = 7890000; diff --git a/crates/router/src/core/connector_onboarding.rs b/crates/router/src/core/connector_onboarding.rs index e48026edc2d5..e6c1fc9d378d 100644 --- a/crates/router/src/core/connector_onboarding.rs +++ b/crates/router/src/core/connector_onboarding.rs @@ -1,5 +1,4 @@ use api_models::{connector_onboarding as api, enums}; -use error_stack::ResultExt; use masking::Secret; use crate::{ @@ -19,16 +18,23 @@ pub trait AccessToken { pub async fn get_action_url( state: AppState, + user_from_token: auth::UserFromToken, request: api::ActionUrlRequest, ) -> RouterResponse { + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + let tracking_id = + utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector) + .await?; match (is_enabled, request.connector) { (Some(true), enums::Connector::Paypal) => { let action_url = Box::pin(paypal::get_action_url_from_paypal( state, - request.connector_id, + tracking_id, request.return_url, )) .await?; @@ -49,40 +55,42 @@ pub async fn sync_onboarding_status( user_from_token: auth::UserFromToken, request: api::OnboardingSyncRequest, ) -> RouterResponse { - let merchant_account = user_from_token - .get_merchant_account(state.clone()) - .await - .change_context(ApiErrorResponse::MerchantAccountNotFound)?; + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + let tracking_id = + utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector) + .await?; match (is_enabled, request.connector) { (Some(true), enums::Connector::Paypal) => { let status = Box::pin(paypal::sync_merchant_onboarding_status( state.clone(), - request.connector_id.clone(), + tracking_id, )) .await?; if let api::OnboardingStatus::PayPal(api::PayPalOnboardingStatus::Success( - ref inner_data, + ref paypal_onboarding_data, )) = status { let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let auth_details = oss_types::ConnectorAuthType::SignatureKey { api_key: connector_onboarding_conf.paypal.client_secret, key1: connector_onboarding_conf.paypal.client_id, - api_secret: Secret::new(inner_data.payer_id.clone()), + api_secret: Secret::new(paypal_onboarding_data.payer_id.clone()), }; - let some_data = paypal::update_mca( + let update_mca_data = paypal::update_mca( &state, - &merchant_account, + user_from_token.merchant_id, request.connector_id.to_owned(), auth_details, ) .await?; return Ok(ApplicationResponse::Json(api::OnboardingStatus::PayPal( - api::PayPalOnboardingStatus::ConnectorIntegrated(some_data), + api::PayPalOnboardingStatus::ConnectorIntegrated(update_mca_data), ))); } Ok(ApplicationResponse::Json(status)) @@ -94,3 +102,15 @@ pub async fn sync_onboarding_status( .into()), } } + +pub async fn reset_tracking_id( + state: AppState, + user_from_token: auth::UserFromToken, + request: api::ResetTrackingIdRequest, +) -> RouterResponse<()> { + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + utils::set_tracking_id_in_configs(&state, &request.connector_id, request.connector).await?; + + Ok(ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs index 30aa69067b5d..f18681f8cfdb 100644 --- a/crates/router/src/core/connector_onboarding/paypal.rs +++ b/crates/router/src/core/connector_onboarding/paypal.rs @@ -23,11 +23,11 @@ fn build_referral_url(state: AppState) -> String { async fn build_referral_request( state: AppState, - connector_id: String, + tracking_id: String, return_url: String, ) -> RouterResult { let access_token = utils::paypal::generate_access_token(state.clone()).await?; - let request_body = types::paypal::PartnerReferralRequest::new(connector_id, return_url); + let request_body = types::paypal::PartnerReferralRequest::new(tracking_id, return_url); utils::paypal::build_paypal_post_request( build_referral_url(state), @@ -38,12 +38,12 @@ async fn build_referral_request( pub async fn get_action_url_from_paypal( state: AppState, - connector_id: String, + tracking_id: String, return_url: String, ) -> RouterResult { let referral_request = Box::pin(build_referral_request( state.clone(), - connector_id, + tracking_id, return_url, )) .await?; @@ -137,7 +137,7 @@ async fn find_paypal_merchant_by_tracking_id( pub async fn update_mca( state: &AppState, - merchant_account: &oss_types::domain::MerchantAccount, + merchant_id: String, connector_id: String, auth_details: oss_types::ConnectorAuthType, ) -> RouterResult { @@ -159,13 +159,9 @@ pub async fn update_mca( connector_webhook_details: None, pm_auth_config: None, }; - let mca_response = admin::update_payment_connector( - state.clone(), - &merchant_account.merchant_id, - &connector_id, - request, - ) - .await?; + let mca_response = + admin::update_payment_connector(state.clone(), &merchant_id, &connector_id, request) + .await?; match mca_response { ApplicationResponse::Json(mca_data) => Ok(mca_data), diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0b2acaf4e506..77253d1d75c4 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -961,5 +961,6 @@ impl ConnectorOnboarding { .app_data(web::Data::new(state)) .service(web::resource("/action_url").route(web::post().to(get_action_url))) .service(web::resource("/sync").route(web::post().to(sync_onboarding_status))) + .service(web::resource("/reset_tracking_id").route(web::post().to(reset_tracking_id))) } } diff --git a/crates/router/src/routes/connector_onboarding.rs b/crates/router/src/routes/connector_onboarding.rs index b7c39b3c1d2e..f5555f5bf9bf 100644 --- a/crates/router/src/routes/connector_onboarding.rs +++ b/crates/router/src/routes/connector_onboarding.rs @@ -20,7 +20,7 @@ pub async fn get_action_url( state, &http_req, req_payload.clone(), - |state, _: auth::UserFromToken, req| core::get_action_url(state, req), + core::get_action_url, &auth::JWTAuth(Permission::MerchantAccountWrite), api_locking::LockAction::NotApplicable, )) @@ -45,3 +45,22 @@ pub async fn sync_onboarding_status( )) .await } + +pub async fn reset_tracking_id( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ResetTrackingId; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + core::reset_tracking_id, + &auth::JWTAuth(Permission::MerchantAccountWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 55c6cbc23d70..c560f0d988af 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -183,7 +183,9 @@ impl From for ApiIdentifier { Self::UserRole } - Flow::GetActionUrl | Flow::SyncOnboardingStatus => Self::ConnectorOnboarding, + Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => { + Self::ConnectorOnboarding + } } } } diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs index e8afcd68a468..03735e61cc70 100644 --- a/crates/router/src/utils/connector_onboarding.rs +++ b/crates/router/src/utils/connector_onboarding.rs @@ -1,6 +1,11 @@ +use diesel_models::{ConfigNew, ConfigUpdate}; +use error_stack::ResultExt; + +use super::errors::StorageErrorExt; use crate::{ + consts, core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, - routes::app::settings, + routes::{app::settings, AppState}, types::{self, api::enums}, }; @@ -34,3 +39,105 @@ pub fn is_enabled( _ => None, } } + +pub async fn check_if_connector_exists( + state: &AppState, + connector_id: &str, + merchant_id: &str, +) -> RouterResult<()> { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?; + + let _connector = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + merchant_id, + connector_id, + &key_store, + ) + .await + .to_not_found_response(ApiErrorResponse::MerchantConnectorAccountNotFound { + id: connector_id.to_string(), + })?; + + Ok(()) +} + +pub async fn set_tracking_id_in_configs( + state: &AppState, + connector_id: &str, + connector: enums::Connector, +) -> RouterResult<()> { + let timestamp = common_utils::date_time::now_unix_timestamp().to_string(); + let find_config = state + .store + .find_config_by_key(&build_key(connector_id, connector)) + .await; + + if find_config.is_ok() { + state + .store + .update_config_by_key( + &build_key(connector_id, connector), + ConfigUpdate::Update { + config: Some(timestamp), + }, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error updating data in configs table")?; + } else if find_config + .as_ref() + .map_err(|e| e.current_context().is_db_not_found()) + .err() + .unwrap_or(false) + { + state + .store + .insert_config(ConfigNew { + key: build_key(connector_id, connector), + config: timestamp, + }) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error inserting data in configs table")?; + } else { + find_config.change_context(ApiErrorResponse::InternalServerError)?; + } + + Ok(()) +} + +pub async fn get_tracking_id_from_configs( + state: &AppState, + connector_id: &str, + connector: enums::Connector, +) -> RouterResult { + let timestamp = state + .store + .find_config_by_key_unwrap_or( + &build_key(connector_id, connector), + Some(common_utils::date_time::now_unix_timestamp().to_string()), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error getting data from configs table")? + .config; + + Ok(format!("{}_{}", connector_id, timestamp)) +} + +fn build_key(connector_id: &str, connector: enums::Connector) -> String { + format!( + "{}_{}_{}", + consts::CONNECTOR_ONBOARDING_CONFIG_PREFIX, + connector, + connector_id, + ) +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index a6ac1b1e0a14..8f0b9bad3e80 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -317,6 +317,8 @@ pub enum Flow { GetActionUrl, /// Sync connector onboarding status SyncOnboardingStatus, + /// Reset tracking id + ResetTrackingId, /// Verify email Token VerifyEmail, /// Send verify email From 5ad3f8939afafce3eec39704dcaa92270b384dcd Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Tue, 16 Jan 2024 13:43:19 +0530 Subject: [PATCH 02/48] fix(payment_link): added expires_on in payment response (#3332) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/payments.rs | 5 +++++ crates/router/src/core/payments/transformers.rs | 2 ++ openapi/openapi_spec.json | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index cac94a07326a..06bd229586d9 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2277,6 +2277,11 @@ pub struct PaymentsResponse { /// List of incremental authorizations happened to the payment pub incremental_authorizations: Option>, + /// Date Time expiry of the payment + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub expires_on: Option, + /// Payment Fingerprint pub fingerprint: Option, } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index dffcff23595b..5ab6bffc8e6d 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -709,6 +709,7 @@ where .set_fingerprint(payment_intent.fingerprint_id) .set_authorization_count(payment_intent.authorization_count) .set_incremental_authorizations(incremental_authorizations_response) + .set_expires_on(payment_intent.session_expiry) .to_owned(), headers, )) @@ -775,6 +776,7 @@ where incremental_authorization_allowed: payment_intent.incremental_authorization_allowed, authorization_count: payment_intent.authorization_count, incremental_authorizations: incremental_authorizations_response, + expires_on: payment_intent.session_expiry, ..Default::default() }, headers, diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index c50f687a1810..466489e2f9f9 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -10948,6 +10948,13 @@ "description": "List of incremental authorizations happened to the payment", "nullable": true }, + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Date Time expiry of the payment", + "example": "2022-09-10T10:11:12Z", + "nullable": true + }, "fingerprint": { "type": "string", "description": "Payment Fingerprint", From 8678f8d1448b5ce430931bfbbc269ef979d9eea7 Mon Sep 17 00:00:00 2001 From: Kashif <46213975+kashif-m@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:37:44 +0530 Subject: [PATCH 03/48] feat(recon): add recon APIs (#3345) Co-authored-by: Kashif Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/Cargo.toml | 3 +- crates/api_models/src/events.rs | 2 + crates/api_models/src/events/recon.rs | 21 ++ crates/api_models/src/events/user.rs | 14 + crates/api_models/src/lib.rs | 2 + crates/api_models/src/recon.rs | 21 ++ crates/api_models/src/user.rs | 7 + crates/common_utils/src/events.rs | 1 + crates/router/Cargo.toml | 3 +- crates/router/src/core/user.rs | 29 ++ crates/router/src/lib.rs | 6 + crates/router/src/routes.rs | 4 + crates/router/src/routes/app.rs | 22 ++ crates/router/src/routes/lock_utils.rs | 6 + crates/router/src/routes/recon.rs | 250 ++++++++++++++++++ crates/router/src/routes/user.rs | 15 ++ crates/router/src/services.rs | 2 + crates/router/src/services/authentication.rs | 96 +++++++ ...n_activated.html => recon_activation.html} | 0 crates/router/src/services/email/types.rs | 105 +++++++- crates/router/src/services/recon.rs | 29 ++ crates/router_env/src/logger/types.rs | 8 + 22 files changed, 639 insertions(+), 7 deletions(-) create mode 100644 crates/api_models/src/events/recon.rs create mode 100644 crates/api_models/src/recon.rs create mode 100644 crates/router/src/routes/recon.rs rename crates/router/src/services/email/assets/{recon_activated.html => recon_activation.html} (100%) create mode 100644 crates/router/src/services/recon.rs diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index 69980361500c..45702a4ecb0a 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -default = ["payouts", "frm"] +default = ["payouts", "frm", "recon"] business_profile_routing = [] connector_choice_bcompat = [] errors = ["dep:actix-web", "dep:reqwest"] @@ -18,6 +18,7 @@ dummy_connector = ["euclid/dummy_connector", "common_enums/dummy_connector"] detailed_errors = [] payouts = [] frm = [] +recon = [] [dependencies] actix-web = { version = "4.3.1", optional = true } diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 6d9bd5db3429..26a9d222d6b9 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -5,6 +5,8 @@ mod locker_migration; pub mod payment; #[cfg(feature = "payouts")] pub mod payouts; +#[cfg(feature = "recon")] +pub mod recon; pub mod refund; pub mod routing; pub mod user; diff --git a/crates/api_models/src/events/recon.rs b/crates/api_models/src/events/recon.rs new file mode 100644 index 000000000000..aed648f4c869 --- /dev/null +++ b/crates/api_models/src/events/recon.rs @@ -0,0 +1,21 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::recon::{ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest}; + +impl ApiEventMetric for ReconUpdateMerchantRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} + +impl ApiEventMetric for ReconTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} + +impl ApiEventMetric for ReconStatusResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 1f4cb7359c79..c0743c8b8fc0 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -1,7 +1,11 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; +#[cfg(feature = "recon")] +use masking::PeekInterface; #[cfg(feature = "dummy_connector")] use crate::user::sample_data::SampleDataRequest; +#[cfg(feature = "recon")] +use crate::user::VerifyTokenResponse; use crate::user::{ dashboard_metadata::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, @@ -21,6 +25,16 @@ impl ApiEventMetric for DashboardEntryResponse { } } +#[cfg(feature = "recon")] +impl ApiEventMetric for VerifyTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::User { + merchant_id: self.merchant_id.clone(), + user_id: self.user_email.peek().to_string(), + }) + } +} + common_utils::impl_misc_api_event_type!( SignUpRequest, SignUpWithMerchantIdRequest, diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index dc1f6eb65375..1ea79ff6fe8f 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -26,6 +26,8 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod pm_auth; +#[cfg(feature = "recon")] +pub mod recon; pub mod refunds; pub mod routing; pub mod surcharge_decision_configs; diff --git a/crates/api_models/src/recon.rs b/crates/api_models/src/recon.rs new file mode 100644 index 000000000000..efbe28f96ba4 --- /dev/null +++ b/crates/api_models/src/recon.rs @@ -0,0 +1,21 @@ +use common_utils::pii; +use masking::Secret; + +use crate::enums; + +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct ReconUpdateMerchantRequest { + pub merchant_id: String, + pub recon_status: enums::ReconStatus, + pub user_email: pii::Email, +} + +#[derive(Debug, serde::Serialize)] +pub struct ReconTokenResponse { + pub token: Secret, +} + +#[derive(Debug, serde::Serialize)] +pub struct ReconStatusResponse { + pub recon_status: enums::ReconStatus, +} diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index f5af31c8e7f6..a04c4fef6601 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -140,3 +140,10 @@ pub struct UserMerchantAccount { pub merchant_id: String, pub merchant_name: OptionalEncryptableName, } + +#[cfg(feature = "recon")] +#[derive(serde::Serialize, Debug)] +pub struct VerifyTokenResponse { + pub merchant_id: String, + pub user_email: pii::Email, +} diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 6bbf78afe421..c2bf50d96c31 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -49,6 +49,7 @@ pub enum ApiEventsType { Miscellaneous, RustLocker, FraudCheck, + Recon, } impl ApiEventMetric for serde_json::Value {} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 8ecac3620919..0a544e0bd090 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" license.workspace = true [features] -default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"] +default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm", "recon"] s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config", "olap"] @@ -30,6 +30,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect external_access_dc = ["dummy_connector"] detailed_errors = ["api_models/detailed_errors", "error-stack/serde"] payouts = [] +recon = ["email"] retry = [] [dependencies] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index b1a582cedecf..27a4f67618e4 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -757,3 +757,32 @@ pub async fn send_verification_mail( Ok(ApplicationResponse::StatusOk) } + +#[cfg(feature = "recon")] +pub async fn verify_token( + state: AppState, + req: auth::ReconUser, +) -> UserResponse { + let user = state + .store + .find_user_by_id(&req.user_id) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::UserNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + let merchant_id = state + .store + .find_user_role_by_user_id(&req.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .merchant_id; + + Ok(ApplicationResponse::Json(user_api::VerifyTokenResponse { + merchant_id: merchant_id.to_string(), + user_email: user.email, + })) +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 696198f2153c..c38a4dc85b55 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -165,6 +165,12 @@ pub fn mk_app( { server_app = server_app.service(routes::StripeApis::server(state.clone())); } + + #[cfg(feature = "recon")] + { + server_app = server_app.service(routes::Recon::server(state.clone())); + } + server_app = server_app.service(routes::Cards::server(state.clone())); server_app = server_app.service(routes::Cache::server(state.clone())); server_app = server_app.service(routes::Health::server(state)); diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index d4bfabb6f92a..d9916f98e745 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -28,6 +28,8 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +#[cfg(feature = "recon")] +pub mod recon; pub mod refunds; #[cfg(feature = "olap")] pub mod routing; @@ -53,6 +55,8 @@ pub use self::app::DummyConnector; pub use self::app::Forex; #[cfg(feature = "payouts")] pub use self::app::Payouts; +#[cfg(all(feature = "olap", feature = "recon"))] +pub use self::app::Recon; #[cfg(all(feature = "olap", feature = "kms"))] pub use self::app::Verify; pub use self::app::{ diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 77253d1d75c4..0c489dbe63a7 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -40,6 +40,8 @@ use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; use super::{ephemeral_key::*, payment_methods::*, webhooks::*}; #[cfg(all(feature = "frm", feature = "oltp"))] use crate::routes::fraud_check as frm_routes; +#[cfg(all(feature = "recon", feature = "olap"))] +use crate::routes::recon as recon_routes; #[cfg(feature = "olap")] use crate::routes::verify_connector::payment_connector_verify; pub use crate::{ @@ -568,6 +570,26 @@ impl PaymentMethods { } } +#[cfg(all(feature = "olap", feature = "recon"))] +pub struct Recon; + +#[cfg(all(feature = "olap", feature = "recon"))] +impl Recon { + pub fn server(state: AppState) -> Scope { + web::scope("/recon") + .app_data(web::Data::new(state)) + .service( + web::resource("/update_merchant") + .route(web::post().to(recon_routes::update_merchant)), + ) + .service(web::resource("/token").route(web::get().to(recon_routes::get_recon_token))) + .service( + web::resource("/request").route(web::post().to(recon_routes::request_for_recon)), + ) + .service(web::resource("/verify_token").route(web::get().to(verify_recon_token))) + } +} + #[cfg(feature = "olap")] pub struct Blocklist; diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index c560f0d988af..12cf76be4759 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -31,6 +31,7 @@ pub enum ApiIdentifier { User, UserRole, ConnectorOnboarding, + Recon, } impl From for ApiIdentifier { @@ -186,6 +187,11 @@ impl From for ApiIdentifier { Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => { Self::ConnectorOnboarding } + + Flow::ReconMerchantUpdate + | Flow::ReconTokenRequest + | Flow::ReconServiceRequest + | Flow::ReconVerifyToken => Self::Recon, } } } diff --git a/crates/router/src/routes/recon.rs b/crates/router/src/routes/recon.rs new file mode 100644 index 000000000000..d34e30237ddc --- /dev/null +++ b/crates/router/src/routes/recon.rs @@ -0,0 +1,250 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::recon as recon_api; +use common_enums::ReconStatus; +use error_stack::ResultExt; +use masking::{ExposeInterface, PeekInterface, Secret}; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{ + api_locking, + errors::{self, RouterResponse, RouterResult, StorageErrorExt, UserErrors}, + }, + services::{ + api as service_api, api, + authentication::{self as auth, ReconUser, UserFromToken}, + email::types as email_types, + recon::ReconToken, + }, + types::{ + api::{self as api_types, enums}, + domain::{UserEmail, UserFromStorage, UserName}, + storage, + }, +}; + +pub async fn update_merchant( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ReconMerchantUpdate; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, _user, req| recon_merchant_account_update(state, req), + &auth::ReconAdmin, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn request_for_recon(state: web::Data, http_req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconServiceRequest; + Box::pin(api::server_wrap( + flow, + state, + &http_req, + (), + |state, user: UserFromToken, _req| send_recon_request(state, user), + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn get_recon_token(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconTokenRequest; + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, user: ReconUser, _| generate_recon_token(state, user), + &auth::ReconJWT, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn send_recon_request( + state: AppState, + user: UserFromToken, +) -> RouterResponse { + let db = &*state.store; + let user_from_db = db + .find_user_by_id(&user.user_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let merchant_id = db + .find_user_role_by_user_id(&user.user_id) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)? + .merchant_id; + let key_store = db + .get_merchant_key_store_by_merchant_id( + merchant_id.as_str(), + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + let merchant_account = db + .find_merchant_account_by_merchant_id(merchant_id.as_str(), &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let email_contents = email_types::ProFeatureRequest { + feature_name: "RECONCILIATION & SETTLEMENT".to_string(), + merchant_id: merchant_id.clone(), + user_name: UserName::new(user_from_db.name) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + recipient_email: UserEmail::from_pii_email(user_from_db.email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert to UserEmail from pii::Email")?, + settings: state.conf.clone(), + subject: format!( + "Dashboard Pro Feature Request by {}", + user_from_db.email.expose().peek() + ), + }; + + let is_email_sent = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to compose and send email for ProFeatureRequest") + .is_ok(); + + if is_email_sent { + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: enums::ReconStatus::Requested, + }; + + let response = db + .update_merchant(merchant_account, updated_merchant_account, &key_store) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id}") + })?; + + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconStatusResponse { + recon_status: response.recon_status, + }, + )) + } else { + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconStatusResponse { + recon_status: enums::ReconStatus::NotRequested, + }, + )) + } +} + +pub async fn recon_merchant_account_update( + state: AppState, + req: recon_api::ReconUpdateMerchantRequest, +) -> RouterResponse { + let merchant_id = &req.merchant_id.clone(); + let user_email = &req.user_email.clone(); + + let db = &*state.store; + + let key_store = db + .get_merchant_key_store_by_merchant_id( + &req.merchant_id, + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let merchant_account = db + .find_merchant_account_by_merchant_id(merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: req.recon_status, + }; + + let response = db + .update_merchant(merchant_account, updated_merchant_account, &key_store) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id}") + })?; + + let email_contents = email_types::ReconActivation { + recipient_email: UserEmail::from_pii_email(user_email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert to UserEmail from pii::Email")?, + user_name: UserName::new(Secret::new("HyperSwitch User".to_string())) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + settings: state.conf.clone(), + subject: "Approval of Recon Request - Access Granted to Recon Dashboard", + }; + + if req.recon_status == ReconStatus::Active { + let _is_email_sent = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to compose and send email for ReconActivation") + .is_ok(); + } + + Ok(service_api::ApplicationResponse::Json( + response + .try_into() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "merchant_account", + })?, + )) +} + +pub async fn generate_recon_token( + state: AppState, + req: ReconUser, +) -> RouterResponse { + let db = &*state.store; + let user = db + .find_user_by_id(&req.user_id) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(errors::ApiErrorResponse::InvalidJwtToken) + } else { + e.change_context(errors::ApiErrorResponse::InternalServerError) + } + })? + .into(); + + let token = Box::pin(get_recon_auth_token(user, state)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconTokenResponse { token }, + )) +} + +pub async fn get_recon_auth_token( + user: UserFromStorage, + state: AppState, +) -> RouterResult> { + ReconToken::new_token(user.0.user_id.clone(), &state.conf).await +} diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index a77b82c550e6..976fd5c9f564 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -388,3 +388,18 @@ pub async fn verify_email_request( )) .await } + +#[cfg(feature = "recon")] +pub async fn verify_recon_token(state: web::Data, http_req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconVerifyToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + (), + |state, user, _req| user_core::verify_token(state, user), + &auth::ReconJWT, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 57f3b802bd5d..8c973105d53b 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -7,6 +7,8 @@ pub mod jwt; pub mod kafka; pub mod logger; pub mod pm_auth; +#[cfg(feature = "recon")] +pub mod recon; #[cfg(feature = "email")] pub mod email; diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index b48465ebd174..3370912394e0 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -12,10 +12,14 @@ use serde::Serialize; use super::authorization::{self, permissions::Permission}; #[cfg(feature = "olap")] use super::jwt; +#[cfg(feature = "recon")] +use super::recon::ReconToken; #[cfg(feature = "olap")] use crate::consts; #[cfg(feature = "olap")] use crate::core::errors::UserResult; +#[cfg(feature = "recon")] +use crate::routes::AppState; use crate::{ configs::settings, core::{ @@ -822,3 +826,95 @@ where } default_auth } + +#[cfg(feature = "recon")] +static RECON_API_KEY: tokio::sync::OnceCell> = + tokio::sync::OnceCell::const_new(); + +#[cfg(feature = "recon")] +pub async fn get_recon_admin_api_key( + secrets: &settings::Secrets, + #[cfg(feature = "kms")] kms_client: &kms::KmsClient, +) -> RouterResult<&'static StrongSecret> { + RECON_API_KEY + .get_or_try_init(|| async { + #[cfg(feature = "kms")] + let recon_admin_api_key = secrets + .kms_encrypted_recon_admin_api_key + .decrypt_inner(kms_client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to KMS decrypt recon admin API key")?; + + #[cfg(not(feature = "kms"))] + let recon_admin_api_key = secrets.recon_admin_api_key.clone(); + + Ok(StrongSecret::new(recon_admin_api_key)) + }) + .await +} + +#[cfg(feature = "recon")] +pub struct ReconAdmin; + +#[async_trait] +#[cfg(feature = "recon")] +impl AuthenticateAndFetch<(), A> for ReconAdmin +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let request_admin_api_key = + get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; + let conf = state.conf(); + + let admin_api_key = get_recon_admin_api_key( + &conf.secrets, + #[cfg(feature = "kms")] + kms::get_kms_client(&conf.kms).await, + ) + .await?; + + if request_admin_api_key != admin_api_key.peek() { + Err(report!(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Recon Admin Authentication Failure"))?; + } + + Ok(((), AuthenticationType::NoAuth)) + } +} + +#[cfg(feature = "recon")] +pub struct ReconJWT; +#[cfg(feature = "recon")] +pub struct ReconUser { + pub user_id: String, +} +#[cfg(feature = "recon")] +impl AuthInfo for ReconUser { + fn get_merchant_id(&self) -> Option<&str> { + None + } +} +#[cfg(all(feature = "olap", feature = "recon"))] +#[async_trait] +impl AuthenticateAndFetch for ReconJWT { + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &AppState, + ) -> RouterResult<(ReconUser, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + Ok(( + ReconUser { + user_id: payload.user_id, + }, + AuthenticationType::NoAuth, + )) + } +} diff --git a/crates/router/src/services/email/assets/recon_activated.html b/crates/router/src/services/email/assets/recon_activation.html similarity index 100% rename from crates/router/src/services/email/assets/recon_activated.html rename to crates/router/src/services/email/assets/recon_activation.html diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index d5c28b1fd6af..0ef15eaa40d2 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -1,17 +1,37 @@ use common_utils::errors::CustomResult; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; -use masking::ExposeInterface; +use masking::{ExposeInterface, PeekInterface}; use crate::{configs, consts}; #[cfg(feature = "olap")] use crate::{core::errors::UserErrors, services::jwt, types::domain}; pub enum EmailBody { - Verify { link: String }, - Reset { link: String, user_name: String }, - MagicLink { link: String, user_name: String }, - InviteUser { link: String, user_name: String }, + Verify { + link: String, + }, + Reset { + link: String, + user_name: String, + }, + MagicLink { + link: String, + user_name: String, + }, + InviteUser { + link: String, + user_name: String, + }, + ReconActivation { + user_name: String, + }, + ProFeatureRequest { + feature_name: String, + merchant_id: String, + user_name: String, + user_email: String, + }, } pub mod html { @@ -43,6 +63,30 @@ pub mod html { link = link ) } + EmailBody::ReconActivation { user_name } => { + format!( + include_str!("assets/recon_activation.html"), + username = user_name, + ) + } + EmailBody::ProFeatureRequest { + feature_name, + merchant_id, + user_name, + user_email, + } => { + format!( + "Dear Hyperswitch Support Team, + + Dashboard Pro Feature Request, + Feature name : {feature_name} + Merchant ID : {merchant_id} + Merchant Name : {user_name} + Email : {user_email} + + (note: This is an auto generated email. use merchant email for any further comunications)", + ) + } } } } @@ -198,3 +242,54 @@ impl EmailData for InviteUser { }) } } + +pub struct ReconActivation { + pub recipient_email: domain::UserEmail, + pub user_name: domain::UserName, + pub settings: std::sync::Arc, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for ReconActivation { + async fn get_email_data(&self) -> CustomResult { + let body = html::get_html_body(EmailBody::ReconActivation { + user_name: self.user_name.clone().get_secret().expose(), + }); + + Ok(EmailContents { + subject: self.subject.to_string(), + body: external_services::email::IntermediateString::new(body), + recipient: self.recipient_email.clone().into_inner(), + }) + } +} + +pub struct ProFeatureRequest { + pub recipient_email: domain::UserEmail, + pub feature_name: String, + pub merchant_id: String, + pub user_name: domain::UserName, + pub settings: std::sync::Arc, + pub subject: String, +} + +#[async_trait::async_trait] +impl EmailData for ProFeatureRequest { + async fn get_email_data(&self) -> CustomResult { + let recipient = self.recipient_email.clone().into_inner(); + + let body = html::get_html_body(EmailBody::ProFeatureRequest { + user_name: self.user_name.clone().get_secret().expose(), + feature_name: self.feature_name.clone(), + merchant_id: self.merchant_id.clone(), + user_email: recipient.peek().to_string(), + }); + + Ok(EmailContents { + subject: self.subject.clone(), + body: external_services::email::IntermediateString::new(body), + recipient, + }) + } +} diff --git a/crates/router/src/services/recon.rs b/crates/router/src/services/recon.rs new file mode 100644 index 000000000000..d5a2151a487b --- /dev/null +++ b/crates/router/src/services/recon.rs @@ -0,0 +1,29 @@ +use error_stack::ResultExt; +use masking::Secret; + +use super::jwt; +use crate::{ + consts, + core::{self, errors::RouterResult}, + routes::app::settings::Settings, +}; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ReconToken { + pub user_id: String, + pub exp: u64, +} + +impl ReconToken { + pub async fn new_token(user_id: String, settings: &Settings) -> RouterResult> { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration) + .change_context(core::errors::ApiErrorResponse::InternalServerError)? + .as_secs(); + let token_payload = Self { user_id, exp }; + let token = jwt::generate_jwt(&token_payload, settings) + .await + .change_context(core::errors::ApiErrorResponse::InternalServerError)?; + Ok(Secret::new(token)) + } +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 8f0b9bad3e80..0d6636e567da 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -165,6 +165,14 @@ pub enum Flow { RefundsList, // Retrieve forex flow. RetrieveForexFlow, + /// Toggles recon service for a merchant. + ReconMerchantUpdate, + /// Recon token request flow. + ReconTokenRequest, + /// Initial request for recon service. + ReconServiceRequest, + /// Recon token verification flow + ReconVerifyToken, /// Routing create flow, RoutingCreateConfig, /// Routing link config From eaa8791ee8126ebce9c500c6b426cbcfa4d3caa2 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:11:29 +0530 Subject: [PATCH 04/48] ci(s3): fetch connector creds from s3 for added security (#3323) --- .github/secrets/connector_auth.toml.gpg | Bin 3435 -> 0 bytes .../workflows/connector-ui-sanity-tests.yml | 21 ++++++++---- .../workflows/postman-collection-runner.yml | 30 ++++++++++++------ scripts/decrypt_connector_auth.sh | 10 ------ 4 files changed, 35 insertions(+), 26 deletions(-) delete mode 100644 .github/secrets/connector_auth.toml.gpg delete mode 100755 scripts/decrypt_connector_auth.sh diff --git a/.github/secrets/connector_auth.toml.gpg b/.github/secrets/connector_auth.toml.gpg deleted file mode 100644 index ce62370c1494e7173b9e45d2276ad58269e51e72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3435 zcmV-x4V3bX4Fm@R0wOk|t4xzmF80#v0p1wQW-a)JS~rdB)2jSU?|ND~F~2BM2lDV} zxg6HrX#wIwXy$tyoq_T(}&>mw@$*3!^6eWb$kb(n(>$ z%N+qiuWs9b%576>uS~X6&~XVmyU1ht^^PI(u1_$V33&b4#`Z}|;y!DY0y_!QO>3{@ zYz+-eKQlV=WQL^~uB+63PP-FN*z@CP0jq)C|I8qFU#ZO;_n7kWwqAgl_Rarerg(~} z+LRL%FGc8X*Z(Zfl~i`0S$$B&LUqhTq;ek}d?JgAB=u5Uj&BK@U_ycpv5kw40Mob| zE_CImVg$eaESL#H+D=0gK+Th$pH||7``JHiVx}6xgz&?KL@?y@f?dhl5Eb{igEm)| z`chfZq){FDRlf+h!KBgh>kh7@_fm!Z+JGL410f}(6OuB{3~59UJdc7OQy49OI@C{I z8+K)NAZ?O`cGzyMV_D&OIeulkkc0e)3teIi zGBn@flHpsghh#w(^UbTLcFB=vdif>HMf##j=i^)iAp^B%;2UucvoBWz3i7x}@fLWb z{aH9q=1!!vE9#@-#sGx@FBr($*PaWFD2eD|>X>JdvElYiJURfhSC1XbM+~XUnU5X} zKHve^f<3H`EZyN9?dopP-28l=xy}|wK6x!m{lkek%DXy-XS>t>hw|JM*7pLKu;z5cE}G#Ac8b)q^`o2)Zu%TYsSZ z%F(%wF=|;M#Iy9JOt^-H)u-?B{nB<*-F(v#OJCa@ANrsqCU75lYR+teXCjJWC zUT!E-QUrfI`11_;K2$GS{e3$5n$M)K4)OacShDXK?tEs)w;So^zS)jx$aMHH7 z5a8yM>bOr6kIDekLc_ze-uTw$FfsqW6VanDQo77Dr7IEu8juwLubi?!KHsWRTkt1)}m!) znuE$4Z@32~1xZh6f?$X5`LmbTYuw;&kNzz0qt{L}y;D9t&`$;})|qC!-a*F5`6Jz= z0<9|zlyDh}%_!>7q6qtsa0@N?9b4URG{34)F$Tcm5j5Mw-Tx-KV-w5VM1Au=iKB`x zb&B`a*04JmH}qT7)lOzk+M+l|3$O*0jyCg&`_Q(!uP%9>XOF>o%CO0J>k5mK6hP8} zT3<66Jd<8o zN9eTuydHbHTWMB<486LZJ*UF*I9m60ja_2%@0=0@K|jYB7P7EWi;;?h^!W`J*6t8e zbM^G$S~YAm-jmv;EEQYV{b(uImY8A5Bzd`SoZ{X+;+x_t zbkg?>yt`2n0!BkV>FeR{y{~>59U)oFepE35G%zdO8twe=uL&cgqs3)WN{)GCC`OPo z5kw?!C3=)(3KGz2sv5Mq3-JW~u29xlyLyYNQY-ak!u@rmc8ig9Q_NVL(y%;uI}w1% zX0;hIpNWyedD2$NaVCLl9S7&@rPnEj_caU>?3Lzqt-%rw!JO7$U8+NOE|3%Q%>NAcH**c$e z!U`kLZrmC(>99y??VvDE-hF!AxzQ6|MyJw-eWK6O`0Z+I+6OJQ19ADYL}1^Q{B8#3 z&@zKGWJ;~+qs*Dwslv<@iH%8V7zArM00-Zn8ZrHI>oP=w*S}RkhU6S|n=8(yuukyf9r=E=b@y|7(PX;9C4)~$KtkUA~+qjrEG-j<^ zHc!!6wj;%kI`^M!;Jb#MqXJ^KQUQ=A?wgG4Y>)nA@CuV4FmPYIS1VY-c~jH8R+Q$7 z(`W_E;`nC^_R~uj-AB0s-n`5u5!O9EurdxAX)t0meid^`C2wz?1%(kedTw+R#C;YP z)-5x^>>C4fT5vB<$&Pn^Nhal>sHPsFthn3lTEo095ke0LMI*UU52$cx9!?~cTUgjV z3kvL6Q5Q_e1`I&Tj{y*dYoW?u7bR0GHyOj?^44pXUmO`d9+mR_?#-5xN;&gTVFG1& z4M~j|XnRyKu&RSy?Y~(SiCy!Y$HQ!$x@6kee`+VJ3J3HU)js<5ozHKc#EauVb44m z-dp;n(|3$6v>zy(^8YS5#v|*4S5C4v^kB*Wt;R-D3TeH5S|G8lAG}>U*N3b?oRC`= z!|4V=h>Ti*-l*DR+WdW)*c2Pwmo4q0y$Qv^jLz7TtV~IGUJ)y2SHz&p8OcnXgVt~F z_pJ?GUO6X<$!TF(+;Tt}wsglC&4R)Cqw51qBYHe{trZH5nD@{fj-W=8kL{T7b*G5-p(@T(F$pp%H^HhRF$lgOQwypNM;?UG-x0gArOY<~Ub~LBN-|ov3(Q#a6E1=v+DISwxWN!s$OU z0976l%gx$f&%x+y#OSUKqDct->X#`5;51Q|QKw+Nh@(1GfW6P+GS|ZlUtof~M2Ul= z(t|&8p+^L9V(_47y)>IzZWHNnc&XN7h0zH5hP%SH`X~LjQ!v*gu21Fm6yzKAk;@w( z?E&v581*>IMUkVEgcr3lfQ>^2Q=v^W2`8=svbV@-n8o4J3zEyGpfp8 zt4ruK)ek$>@s-+aA}vKsk`OwzUeWVN!rvA~-*V7VApz)_t@}I_{=7cbX~JN4tF0aG z>6h}DhkrZ0K;Vr4yVZo@s6INmPvzA*-w9n zCvqa={JaeZn|4r=HQVO0=IWlrV(4Xgo{dEns)zXev2J> $GITHUB_ENV + run: echo "CONNECTOR_AUTH_FILE_PATH=${HOME}/target/test/connector_auth.toml" >> $GITHUB_ENV - name: Set connector tests file path in env if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} shell: bash - run: echo "CONNECTOR_TESTS_FILE_PATH=$HOME/target/test/connector_tests.json" >> $GITHUB_ENV + run: echo "CONNECTOR_TESTS_FILE_PATH=${HOME}/target/test/connector_tests.json" >> $GITHUB_ENV - name: Set ignore_browser_profile usage in env if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} @@ -154,9 +163,9 @@ jobs: failed_connectors=() for i in $(echo "$INPUT" | tr "," "\n"); do - echo $i + echo "${i}" if ! cargo test --package test_utils --test connectors -- "${i}_ui::" --test-threads=1; then - failed_connectors+=("$i") + failed_connectors+=("${i}") fi done diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index d5434520715f..8cbbed8187c2 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -52,27 +52,37 @@ jobs: - name: Repository checkout uses: actions/checkout@v4 - - name: Decrypt connector auth file + - name: Download Encrypted TOML from S3 and Decrypt if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} env: + AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} + AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} + CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} + DESTINATION_FILE_NAME: "connector_auth.toml.gpg" + S3_SOURCE_FILE_NAME: "cf05a6ab-525e-4888-98b3-3b4a443b87c0.toml.gpg" shell: bash - run: ./scripts/decrypt_connector_auth.sh + run: | + mkdir -p ${HOME}/target/secrets ${HOME}/target/test + + aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" + gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output "${HOME}/target/test/connector_auth.toml" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" - name: Set paths in env if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} id: config_path shell: bash run: | - echo "CONNECTOR_AUTH_FILE_PATH=$HOME/target/test/connector_auth.toml" >> $GITHUB_ENV + echo "CONNECTOR_AUTH_FILE_PATH=${HOME}/target/test/connector_auth.toml" >> $GITHUB_ENV - name: Fetch keys if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} env: TOML_PATH: "./config/development.toml" run: | - LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' $TOML_PATH) - echo "ADMIN_API_KEY=$LOCAL_ADMIN_API_KEY" >> $GITHUB_ENV + LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) + echo "ADMIN_API_KEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV - name: Install Rust if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} @@ -118,7 +128,7 @@ jobs: while ! nc -z localhost 8080; do if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) echo "Server did not start within a reasonable time. Exiting." - kill $SERVER_PID + kill ${SERVER_PID} exit 1 else COUNT=$((COUNT+1)) @@ -141,10 +151,10 @@ jobs: export PATH=${NEWMAN_PATH}:${PATH} failed_connectors=() - for i in $(echo "$CONNECTORS" | tr "," "\n"); do - echo $i - if ! cargo run --bin test_utils -- --connector-name="$i" --base-url="$BASE_URL" --admin-api-key="$ADMIN_API_KEY"; then - failed_connectors+=("$i") + for i in $(echo "${CONNECTORS}" | tr "," "\n"); do + echo "${i}" + if ! cargo run --bin test_utils -- --connector-name="${i}" --base-url="${BASE_URL}" --admin-api-key="${ADMIN_API_KEY}"; then + failed_connectors+=("${i}") fi done diff --git a/scripts/decrypt_connector_auth.sh b/scripts/decrypt_connector_auth.sh deleted file mode 100755 index dc445f0afa6b..000000000000 --- a/scripts/decrypt_connector_auth.sh +++ /dev/null @@ -1,10 +0,0 @@ -#! /usr/bin/env bash - -mkdir -p $HOME/target/test - - -# Decrypt the file -# --batch to prevent interactive command -# --yes to assume "yes" for questions -gpg --quiet --batch --yes --decrypt --passphrase="$CONNECTOR_AUTH_PASSPHRASE" \ ---output $HOME/target/test/connector_auth.toml .github/secrets/connector_auth.toml.gpg \ No newline at end of file From d533c98b5107fb6876c11b183eb9bc382a77a2f1 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:59:22 +0530 Subject: [PATCH 05/48] feat(connector): [BANKOFAMERICA] Implement 3DS flow for cards (#3343) --- config/config.example.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + crates/router/src/connector/bankofamerica.rs | 260 +++++- .../connector/bankofamerica/transformers.rs | 738 +++++++++++++++++- crates/router/src/connector/cybersource.rs | 27 + crates/router/src/core/payments.rs | 3 +- crates/router/src/core/payments/flows.rs | 2 - 8 files changed, 1017 insertions(+), 16 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index e20f9c1b65d2..d4e119641922 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -351,6 +351,7 @@ stripe = { payment_method = "bank_transfer" } nuvei = { payment_method = "card" } shift4 = { payment_method = "card" } bluesnap = { payment_method = "card" } +bankofamerica = {payment_method = "card"} cybersource = {payment_method = "card"} nmi = {payment_method = "card"} diff --git a/config/development.toml b/config/development.toml index 5732d5f0d1de..91269005a0f0 100644 --- a/config/development.toml +++ b/config/development.toml @@ -428,6 +428,7 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +bankofamerica = {payment_method = "card"} cybersource = {payment_method = "card"} nmi = {payment_method = "card"} diff --git a/config/docker_compose.toml b/config/docker_compose.toml index c6934a64671f..450fe106a31f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -241,6 +241,7 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +bankofamerica = {payment_method = "card"} cybersource = {payment_method = "card"} nmi = {payment_method = "card"} diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index aeb3dafcfa21..0d901b990784 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -12,6 +12,7 @@ use time::OffsetDateTime; use transformers as bankofamerica; use url::Url; +use super::utils::{PaymentsAuthorizeRequestData, RouterData}; use crate::{ configs::settings, connector::{utils as connector_utils, utils::RefundsRequestData}, @@ -48,6 +49,8 @@ impl api::Refund for Bankofamerica {} impl api::RefundExecute for Bankofamerica {} impl api::RefundSync for Bankofamerica {} impl api::PaymentToken for Bankofamerica {} +impl api::PaymentsPreProcessing for Bankofamerica {} +impl api::PaymentsCompleteAuthorize for Bankofamerica {} impl Bankofamerica { pub fn generate_digest(&self, payload: &[u8]) -> String { @@ -299,6 +302,113 @@ impl } } +impl + ConnectorIntegration< + api::PreProcessing, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + > for Bankofamerica +{ + fn get_headers( + &self, + req: &types::PaymentsPreProcessingRouterData, + 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::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let redirect_response = req.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => Ok(format!( + "{}risk/v1/authentications", + self.base_url(connectors) + )), + Some(_) | None => Ok(format!( + "{}risk/v1/authentication-results", + self.base_url(connectors) + )), + } + } + fn get_request_body( + &self, + req: &types::PaymentsPreProcessingRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?, + req, + ))?; + let connector_req = + bankofamerica::BankOfAmericaPreProcessingRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsPreProcessingRouterData, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaPreProcessingResponse = res + .response + .parse_struct("BankOfAmerica AuthEnrollmentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + impl ConnectorIntegration for Bankofamerica { @@ -316,13 +426,17 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}pts/v2/payments/", - api::ConnectorCommon::base_url(self, connectors) - )) + if req.is_three_ds() && req.request.is_card() { + Ok(format!( + "{}risk/v1/authentication-setups", + self.base_url(connectors) + )) + } else { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } } fn get_request_body( @@ -336,9 +450,15 @@ impl ConnectorIntegration CustomResult { + if data.is_three_ds() && data.request.is_card() { + let response: bankofamerica::BankOfAmericaAuthSetupResponse = res + .response + .parse_struct("Bankofamerica AuthSetupResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } else { + let response: bankofamerica::BankOfAmericaPaymentsResponse = res + .response + .parse_struct("Bankofamerica PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } + + fn get_5xx_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res + .response + .parse_struct("BankOfAmericaServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } +} + +impl + ConnectorIntegration< + api::CompleteAuthorize, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + > for Bankofamerica +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + 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::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = + bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: Response, + ) -> CustomResult { let response: bankofamerica::BankOfAmericaPaymentsResponse = res .response .parse_struct("BankOfAmerica PaymentResponse") diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 6abe1b634df6..72e3de0bf777 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,6 +1,7 @@ use api_models::payments; use base64::Engine; -use common_utils::pii; +use common_utils::{ext_traits::ValueExt, pii}; +use error_stack::{IntoReport, ResultExt}; use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -8,10 +9,12 @@ use serde_json::Value; use crate::{ connector::utils::{ self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, - PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData, + PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, + PaymentsPreProcessingData, PaymentsSyncRequestData, RouterData, }, consts, core::errors, + services, types::{ self, api::{self, enums as api_enums}, @@ -85,14 +88,17 @@ pub struct BankOfAmericaPaymentsRequest { order_information: OrderInformationWithBill, client_reference_information: ClientReferenceInformation, #[serde(skip_serializing_if = "Option::is_none")] + consumer_authentication_information: Option, + #[serde(skip_serializing_if = "Option::is_none")] merchant_defined_information: Option>, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProcessingInformation { - capture: bool, + capture: Option, payment_solution: Option, + commerce_indicator: String, } #[derive(Debug, Serialize)] @@ -102,6 +108,17 @@ pub struct MerchantDefinedInformation { value: String, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + directory_server_transaction_id: Option, + specification_version: Option, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CaptureOptions { @@ -287,6 +304,28 @@ impl } } +impl + From<( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to, + } + } +} + impl From<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -300,11 +339,40 @@ impl ), ) -> Self { Self { - capture: matches!( + capture: Some(matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None - ), + )), payment_solution: solution.map(String::from), + commerce_indicator: String::from("internet"), + } + } +} + +impl + From<( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &BankOfAmericaConsumerAuthValidateResponse, + )> for ProcessingInformation +{ + fn from( + (item, solution, three_ds_data): ( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &BankOfAmericaConsumerAuthValidateResponse, + ), + ) -> Self { + Self { + capture: Some(matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + )), + payment_solution: solution.map(String::from), + commerce_indicator: three_ds_data + .indicator + .to_owned() + .unwrap_or(String::from("internet")), } } } @@ -319,6 +387,16 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } +impl From<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for ClientReferenceInformation +{ + fn from(item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } +} + impl ForeignFrom for Vec { fn foreign_from(metadata: Value) -> Self { let hashmap: std::collections::BTreeMap = @@ -367,6 +445,83 @@ pub struct Avs { code_raw: String, } +impl + TryFrom<( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + payments::Card, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + payments::Card, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + let client_reference_information = ClientReferenceInformation::from(item); + + let three_ds_info: BankOfAmericaThreeDSMetadata = item + .router_data + .request + .connector_meta + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "connector_meta", + })? + .parse_value("BankOfAmericaThreeDSMetadata") + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "Merchant connector account metadata", + })?; + + let processing_information = + ProcessingInformation::from((item, None, &three_ds_info.three_ds_data)); + + let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator: three_ds_info.three_ds_data.ucaf_collection_indicator, + cavv: three_ds_info.three_ds_data.cavv, + ucaf_authentication_data: three_ds_info.three_ds_data.ucaf_authentication_data, + xid: three_ds_info.three_ds_data.xid, + directory_server_transaction_id: three_ds_info + .three_ds_data + .directory_server_transaction_id, + specification_version: three_ds_info.three_ds_data.specification_version, + }); + + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information, + merchant_defined_information, + }) + } +} + impl TryFrom<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -410,6 +565,7 @@ impl order_information, client_reference_information, merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -455,6 +611,7 @@ impl order_information, client_reference_information, merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -496,6 +653,7 @@ impl order_information, client_reference_information, merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -552,6 +710,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> order_information, merchant_defined_information, client_reference_information, + consumer_authentication_information: None, }) } } @@ -608,6 +767,64 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthSetupRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> + for BankOfAmericaAuthSetupRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + let client_reference_information = ClientReferenceInformation::from(item); + Ok(Self { + payment_information, + client_reference_information, + }) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ) + .into()) + } + } + } +} + #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BankofamericaPaymentStatus { @@ -669,6 +886,29 @@ impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationResponse { + access_token: String, + device_data_collection_url: String, + reference_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientAuthSetupInfoResponse { + id: String, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BankOfAmericaAuthSetupResponse { + ClientAuthSetupInfo(ClientAuthSetupInfoResponse), + ErrorInformation(BankOfAmericaErrorInformationResponse), +} + #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum BankOfAmericaPaymentsResponse { @@ -799,6 +1039,494 @@ fn get_payment_response( } } +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaAuthSetupResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaAuthSetupResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(services::RedirectForm::CybersourceAuthSetup { + access_token: info_response + .consumer_authentication_information + .access_token, + ddc_url: info_response + .consumer_authentication_information + .device_data_collection_url, + reference_id: info_response + .consumer_authentication_information + .reference_id, + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed: None, + }), + ..item.data + }), + BankOfAmericaAuthSetupResponse::ErrorInformation(error_response) => { + let error_reason = error_response + .error_information + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason; + Ok(Self { + response: Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }), + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationRequest { + return_url: String, + reference_id: String, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthEnrollmentRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationRequest, + order_information: OrderInformationWithBill, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct BankOfAmericaRedirectionAuthResponse { + pub transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationValidateRequest { + authentication_transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthValidateRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationValidateRequest, + order_information: OrderInformation, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum BankOfAmericaPreProcessingRequest { + AuthEnrollment(BankOfAmericaAuthEnrollmentRequest), + AuthValidate(BankOfAmericaAuthValidateRequest), +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> + for BankOfAmericaPreProcessingRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>, + ) -> Result { + let client_reference_information = ClientReferenceInformation { + code: Some(item.router_data.connector_request_reference_id.clone()), + }; + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "payment_method_data", + }, + )?; + let payment_information = match payment_method_data { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + Ok(PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + })) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + )) + } + }?; + + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + + let amount_details = Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + }, + )?, + }; + + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => { + let reference_id = param + .clone() + .peek() + .split_once('=') + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.params.reference_id", + })? + .1 + .to_string(); + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill { + amount_details, + bill_to, + }; + Ok(Self::AuthEnrollment(BankOfAmericaAuthEnrollmentRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + BankOfAmericaConsumerAuthInformationRequest { + return_url: item.router_data.request.get_complete_authorize_url()?, + reference_id, + }, + order_information, + })) + } + Some(_) | None => { + let redirect_payload: BankOfAmericaRedirectionAuthResponse = redirect_response + .payload + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + })? + .peek() + .clone() + .parse_value("BankOfAmericaRedirectionAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let order_information = OrderInformation { amount_details }; + Ok(Self::AuthValidate(BankOfAmericaAuthValidateRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + BankOfAmericaConsumerAuthInformationValidateRequest { + authentication_transaction_id: redirect_payload.transaction_id, + }, + order_information, + })) + } + } + } +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_data", + }, + )?; + match payment_method_data { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ) + .into()) + } + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum BankOfAmericaAuthEnrollmentStatus { + PendingAuthentication, + AuthenticationSuccessful, + AuthenticationFailed, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthValidateResponse { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + specification_version: Option, + directory_server_transaction_id: Option, + indicator: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BankOfAmericaThreeDSMetadata { + three_ds_data: BankOfAmericaConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationEnrollmentResponse { + access_token: Option, + step_up_url: Option, + //Added to segregate the three_ds_data in a separate struct + #[serde(flatten)] + validate_response: BankOfAmericaConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientAuthCheckInfoResponse { + id: String, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationEnrollmentResponse, + status: BankOfAmericaAuthEnrollmentStatus, + error_information: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BankOfAmericaPreProcessingResponse { + ClientAuthCheckInfo(Box), + ErrorInformation(BankOfAmericaErrorInformationResponse), +} + +impl From for enums::AttemptStatus { + fn from(item: BankOfAmericaAuthEnrollmentStatus) -> Self { + match item { + BankOfAmericaAuthEnrollmentStatus::PendingAuthentication => Self::AuthenticationPending, + BankOfAmericaAuthEnrollmentStatus::AuthenticationSuccessful => { + Self::AuthenticationSuccessful + } + BankOfAmericaAuthEnrollmentStatus::AuthenticationFailed => Self::AuthenticationFailed, + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPreProcessingResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPreProcessingResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPreProcessingResponse::ClientAuthCheckInfo(info_response) => { + let status = enums::AttemptStatus::from(info_response.status); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + let response = Err(types::ErrorResponse::from(( + &info_response.error_information, + &risk_info, + item.http_code, + info_response.id.clone(), + ))); + + Ok(Self { + status, + response, + ..item.data + }) + } else { + let connector_response_reference_id = Some( + info_response + .client_reference_information + .code + .unwrap_or(info_response.id.clone()), + ); + + let redirection_data = match ( + info_response + .consumer_authentication_information + .access_token, + info_response + .consumer_authentication_information + .step_up_url, + ) { + (Some(access_token), Some(step_up_url)) => { + Some(services::RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + }) + } + _ => None, + }; + let three_ds_data = serde_json::to_value( + info_response + .consumer_authentication_information + .validate_response, + ) + .into_report() + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data, + mandate_reference: None, + connector_metadata: Some( + serde_json::json!({"three_ds_data":three_ds_data}), + ), + network_txn_id: None, + connector_response_reference_id, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } + } + BankOfAmericaPreProcessingResponse::ErrorInformation(ref error_response) => { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + Ok(Self { + response, + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))) + } + } + } +} + impl TryFrom< types::ResponseRouterData< diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 69159c10c8af..b300e97b44a9 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -874,6 +874,33 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourceServerErrorResponse = res + .response + .parse_struct("CybersourceServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(types::ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } } impl diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 21cdec92ccb4..49a9bcf66645 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1489,7 +1489,8 @@ where router_data = router_data.preprocessing_steps(state, connector).await?; (router_data, false) - } else if connector.connector_name == router_types::Connector::Cybersource + } else if (connector.connector_name == router_types::Connector::Cybersource + || connector.connector_name == router_types::Connector::Bankofamerica) && is_operation_complete_authorize(&operation) && router_data.auth_type == storage_enums::AuthenticationType::ThreeDs { diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 6dd692f15259..c9f9d6d87f5c 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -147,7 +147,6 @@ impl default_imp_for_complete_authorize!( connector::Aci, connector::Adyen, - connector::Bankofamerica, connector::Bitpay, connector::Boku, connector::Cashtocode, @@ -863,7 +862,6 @@ default_imp_for_pre_processing_steps!( connector::Airwallex, connector::Authorizedotnet, connector::Bambora, - connector::Bankofamerica, connector::Bitpay, connector::Bluesnap, connector::Boku, From 398c5ed51e0547504c3dfbd1d7c23568337e7d1c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:20:08 +0000 Subject: [PATCH 06/48] chore(version): 2024.01.17.0 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 739b8cd2c667..b9b01fe4e915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.17.0 + +### Features + +- **connector:** [BANKOFAMERICA] Implement 3DS flow for cards ([#3343](https://github.com/juspay/hyperswitch/pull/3343)) ([`d533c98`](https://github.com/juspay/hyperswitch/commit/d533c98b5107fb6876c11b183eb9bc382a77a2f1)) +- **recon:** Add recon APIs ([#3345](https://github.com/juspay/hyperswitch/pull/3345)) ([`8678f8d`](https://github.com/juspay/hyperswitch/commit/8678f8d1448b5ce430931bfbbc269ef979d9eea7)) + +### Bug Fixes + +- **connector_onboarding:** Check if connector exists for the merchant account and add reset tracking id API ([#3229](https://github.com/juspay/hyperswitch/pull/3229)) ([`58cc8d6`](https://github.com/juspay/hyperswitch/commit/58cc8d6109ce49d385b06c762ab3f6670f5094eb)) +- **payment_link:** Added expires_on in payment response ([#3332](https://github.com/juspay/hyperswitch/pull/3332)) ([`5ad3f89`](https://github.com/juspay/hyperswitch/commit/5ad3f8939afafce3eec39704dcaa92270b384dcd)) + +**Full Changelog:** [`2024.01.12.1...2024.01.17.0`](https://github.com/juspay/hyperswitch/compare/2024.01.12.1...2024.01.17.0) + +- - - + ## 2024.01.12.1 ### Miscellaneous Tasks From 01c2de223f60595d77c06a59a40dfe041e02cfee Mon Sep 17 00:00:00 2001 From: Kashif <46213975+kashif-m@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:35:13 +0530 Subject: [PATCH 07/48] feat(payment_method): add capability to store bank details using /payment_methods endpoint (#3113) Co-authored-by: Kashif Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Bernard Eugine <114725419+bernard-eugine@users.noreply.github.com> --- crates/api_models/src/payment_methods.rs | 15 ++ crates/api_models/src/payouts.rs | 38 ++-- .../src/connector/adyen/transformers.rs | 85 +++++---- crates/router/src/core/locker_migration.rs | 1 + .../router/src/core/payment_methods/cards.rs | 175 ++++++++++++++---- .../src/core/payment_methods/transformers.rs | 24 +++ .../router/src/core/payment_methods/vault.rs | 13 +- crates/router/src/core/payments/helpers.rs | 2 + .../router/src/core/payments/tokenization.rs | 1 + crates/router/src/core/payouts/helpers.rs | 118 ++++++++---- .../router/src/types/api/payment_methods.rs | 27 ++- crates/router/tests/connectors/adyen.rs | 8 +- crates/router/tests/connectors/wise.rs | 6 +- openapi/openapi_spec.json | 72 +++++-- 14 files changed, 412 insertions(+), 173 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index a907fff60193..3467777da745 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -55,6 +55,11 @@ pub struct PaymentMethodCreate { /// The card network #[schema(example = "Visa")] pub card_network: Option, + + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -72,6 +77,11 @@ pub struct PaymentMethodUpdate { #[schema(value_type = Option,example = "Visa")] pub card_network: Option, + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, @@ -147,6 +157,11 @@ pub struct PaymentMethodResponse { #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, + + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, } #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index f7dba2446e91..9e771b471214 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -181,7 +181,7 @@ pub struct Card { /// The card holder's name #[schema(value_type = String, example = "John Doe")] - pub card_holder_name: Secret, + pub card_holder_name: Option>, } #[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] @@ -195,16 +195,16 @@ pub enum Bank { #[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct AchBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// Bank account number is an unique identifier assigned by a bank to a customer. #[schema(value_type = String, example = "000123456")] @@ -218,16 +218,16 @@ pub struct AchBankTransfer { #[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct BacsBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// Bank account number is an unique identifier assigned by a bank to a customer. #[schema(value_type = String, example = "000123456")] @@ -242,16 +242,16 @@ pub struct BacsBankTransfer { // The SEPA (Single Euro Payments Area) is a pan-European network that allows you to send and receive payments in euros between two cross-border bank accounts in the eurozone. pub struct SepaBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer. #[schema(value_type = String, example = "DE89370400440532013000")] diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index e00b829f2834..1e1cfa8fe50c 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -9,9 +9,7 @@ use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime, PrimitiveDateTime}; #[cfg(feature = "payouts")] -use crate::connector::utils::AddressDetailsData; -#[cfg(feature = "payouts")] -use crate::types::api::payouts; +use crate::{connector::utils::AddressDetailsData, types::api::payouts, utils::OptionExt}; use crate::{ connector::utils::{ self, BrowserInformationData, CardData, MandateReferenceData, PaymentsAuthorizeRequestData, @@ -1707,20 +1705,6 @@ fn get_country_code( address.and_then(|billing| billing.address.as_ref().and_then(|address| address.country)) } -#[cfg(feature = "payouts")] -fn get_payout_card_details(payout_method_data: &PayoutMethodData) -> Option { - match payout_method_data { - PayoutMethodData::Card(card) => Some(PayoutCardDetails { - _type: "scheme".to_string(), // FIXME: Remove hardcoding - number: card.card_number.peek().to_string(), - expiry_month: card.expiry_month.peek().to_string(), - expiry_year: card.expiry_year.peek().to_string(), - holder_name: card.card_holder_name.peek().to_string(), - }), - _ => None, - } -} - fn get_social_security_number( voucher_data: &api_models::payments::VoucherData, ) -> Option> { @@ -3980,12 +3964,12 @@ pub struct AdyenPayoutCreateRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct PayoutBankDetails { - bank_name: String, + iban: Secret, + owner_name: Secret, + bank_city: Option, + bank_name: Option, bic: Option>, - country_code: storage_enums::CountryAlpha2, - iban: Option>, - owner_name: Option>, - bank_city: String, + country_code: Option, tax_id: Option>, } @@ -4036,11 +4020,11 @@ pub struct AdyenPayoutEligibilityRequest { #[serde(rename_all = "camelCase")] pub struct PayoutCardDetails { #[serde(rename = "type")] - _type: String, - number: String, - expiry_month: String, - expiry_year: String, - holder_name: String, + payment_method_type: String, + number: CardNumber, + expiry_month: Secret, + expiry_year: Secret, + holder_name: Secret, } #[cfg(feature = "payouts")] @@ -4095,6 +4079,31 @@ pub struct AdyenPayoutCancelRequest { merchant_account: Secret, } +#[cfg(feature = "payouts")] +impl TryFrom<&PayoutMethodData> for PayoutCardDetails { + type Error = Error; + fn try_from(item: &PayoutMethodData) -> Result { + match item { + PayoutMethodData::Card(card) => Ok(Self { + payment_method_type: "scheme".to_string(), // FIXME: Remove hardcoding + number: card.card_number.clone(), + expiry_month: card.expiry_month.clone(), + expiry_year: card.expiry_year.clone(), + holder_name: card + .card_holder_name + .clone() + .get_required_value("card_holder_name") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.card.holder_name", + })?, + }), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.card", + })?, + } + } +} + // Payouts eligibility request transform #[cfg(feature = "payouts")] impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutEligibilityRequest { @@ -4102,12 +4111,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutE fn try_from(item: &AdyenRouterData<&types::PayoutsRouterData>) -> Result { let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; let payout_method_data = - get_payout_card_details(&item.router_data.get_payout_method_data()?).map_or( - Err(errors::ConnectorError::MissingRequiredField { - field_name: "payout_method_data", - }), - Ok, - )?; + PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?; Ok(Self { amount: Amount { currency: item.router_data.request.destination_currency, @@ -4155,6 +4159,11 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC .customer_details .to_owned() .map_or((None, None), |c| (c.name, c.email)); + let owner_name = owner_name.get_required_value("owner_name").change_context( + errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.bank.owner_name", + }, + )?; match item.router_data.get_payout_method_data()? { PayoutMethodData::Card(_) => Err(errors::ConnectorError::NotSupported { @@ -4169,7 +4178,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC bank_city: b.bank_city, owner_name, bic: b.bic, - iban: Some(b.iban), + iban: b.iban, tax_id: None, }, payouts::BankPayout::Ach(..) => Err(errors::ConnectorError::NotSupported { @@ -4234,13 +4243,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutF value: item.amount.to_owned(), currency: item.router_data.request.destination_currency, }, - card: get_payout_card_details(&item.router_data.get_payout_method_data()?) - .map_or( - Err(errors::ConnectorError::MissingRequiredField { - field_name: "payout_method_data", - }), - Ok, - )?, + card: PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?, billing_address: get_address_info(item.router_data.get_billing().ok()), merchant_account, reference: item.router_data.request.payout_id.clone(), diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index 3f56cddee126..e3e308a8a01c 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -109,6 +109,7 @@ pub async fn call_to_locker( payment_method_issuer: pm.payment_method_issuer, payment_method_issuer_code: pm.payment_method_issuer_code, card: Some(card_details.clone()), + bank_transfer: None, metadata: pm.metadata, customer_id: Some(pm.customer_id), card_network: card.card_brand, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 39bc54fa1578..51f543536350 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -105,6 +105,29 @@ pub async fn create_payment_method( Ok(response) } +pub fn store_default_payment_method( + req: &api::PaymentMethodCreate, + customer_id: &str, + merchant_id: &String, +) -> (api::PaymentMethodResponse, bool) { + let pm_id = generate_id(consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.to_string(), + customer_id: Some(customer_id.to_owned()), + payment_method_id: pm_id, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + bank_transfer: None, + card: None, + metadata: req.metadata.clone(), + created: Some(common_utils::date_time::now()), + recurring_enabled: false, //[#219] + installment_payment_enabled: false, //[#219] + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219] + }; + (payment_method_response, false) +} + #[instrument(skip_all)] pub async fn add_payment_method( state: routes::AppState, @@ -115,30 +138,44 @@ pub async fn add_payment_method( req.validate()?; let merchant_id = &merchant_account.merchant_id; let customer_id = req.customer_id.clone().get_required_value("customer_id")?; - let response = match req.card.clone() { - Some(card) => { - add_card_to_locker(&state, req.clone(), &card, &customer_id, merchant_account) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed") - } - None => { - let pm_id = generate_id(consts::ID_LENGTH, "pm"); - let payment_method_response = api::PaymentMethodResponse { - merchant_id: merchant_id.to_string(), - customer_id: Some(customer_id.clone()), - payment_method_id: pm_id, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - card: None, - metadata: req.metadata.clone(), - created: Some(common_utils::date_time::now()), - recurring_enabled: false, //[#219] - installment_payment_enabled: false, //[#219] - payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219] - }; - Ok((payment_method_response, false)) - } + + let response = match req.payment_method { + api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() { + Some(bank) => add_bank_to_locker( + &state, + req.clone(), + merchant_account, + key_store, + &bank, + &customer_id, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add PaymentMethod Failed"), + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + api_enums::PaymentMethod::Card => match req.card.clone() { + Some(card) => { + add_card_to_locker(&state, req.clone(), &card, &customer_id, merchant_account) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed") + } + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), }; let (resp, is_duplicate) = response?; @@ -199,6 +236,7 @@ pub async fn update_customer_payment_method( payment_method_type: pm.payment_method_type, payment_method_issuer: pm.payment_method_issuer, payment_method_issuer_code: pm.payment_method_issuer_code, + bank_transfer: req.bank_transfer, card: req.card, metadata: req.metadata, customer_id: Some(pm.customer_id), @@ -212,6 +250,64 @@ pub async fn update_customer_payment_method( // Wrapper function to switch lockers +pub async fn add_bank_to_locker( + state: &routes::AppState, + req: api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + bank: &api::BankPayout, + customer_id: &String, +) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> { + let key = key_store.key.get_inner().peek(); + let payout_method_data = api::PayoutMethodData::Bank(bank.clone()); + let enc_data = async { + serde_json::to_value(payout_method_data.to_owned()) + .map_err(|err| { + logger::error!("Error while encoding payout method data: {}", err); + errors::VaultError::SavePaymentMethodFailed + }) + .into_report() + .change_context(errors::VaultError::SavePaymentMethodFailed) + .attach_printable("Unable to encode payout method data") + .ok() + .map(|v| { + let secret: Secret = Secret::new(v.to_string()); + secret + }) + .async_lift(|inner| encrypt_optional(inner, key)) + .await + } + .await + .change_context(errors::VaultError::SavePaymentMethodFailed) + .attach_printable("Failed to encrypt payout method data")? + .map(Encryption::from) + .map(|e| e.into_inner()) + .map_or(Err(errors::VaultError::SavePaymentMethodFailed), |e| { + Ok(hex::encode(e.peek())) + })?; + + let payload = + payment_methods::StoreLockerReq::LockerGeneric(payment_methods::StoreGenericReq { + merchant_id: &merchant_account.merchant_id, + merchant_customer_id: customer_id.to_owned(), + enc_data, + }); + let store_resp = call_to_locker_hs( + state, + &payload, + customer_id, + api_enums::LockerChoice::Basilisk, + ) + .await?; + let payment_method_resp = payment_methods::mk_add_bank_response_hs( + bank.clone(), + store_resp.card_reference, + req, + &merchant_account.merchant_id, + ); + Ok((payment_method_resp, store_resp.duplicate.unwrap_or(false))) +} + /// The response will be the tuple of PaymentMethodResponse and the duplication check of payment_method pub async fn add_card_to_locker( state: &routes::AppState, @@ -2424,7 +2520,17 @@ pub async fn list_customer_payment_method( let token_data = PaymentTokenData::temporary_generic(token.clone()); ( None, - Some(get_lookup_key_for_payout_method(state, &key_store, &token, &pm).await?), + Some( + get_bank_from_hs_locker( + state, + &key_store, + &token, + &pm.customer_id, + &pm.customer_id, + &pm.payment_method_id, + ) + .await?, + ), token_data, ) } @@ -2738,18 +2844,20 @@ async fn get_bank_account_connector_details( } #[cfg(feature = "payouts")] -pub async fn get_lookup_key_for_payout_method( +pub async fn get_bank_from_hs_locker( state: &routes::AppState, key_store: &domain::MerchantKeyStore, - payout_token: &str, - pm: &storage::PaymentMethod, + temp_token: &str, + customer_id: &str, + merchant_id: &str, + token_ref: &str, ) -> errors::RouterResult { let payment_method = get_payment_method_from_hs_locker( state, key_store, - &pm.customer_id, - &pm.merchant_id, - &pm.payment_method_id, + customer_id, + merchant_id, + token_ref, None, ) .await @@ -2764,9 +2872,9 @@ pub async fn get_lookup_key_for_payout_method( api::PayoutMethodData::Bank(bank) => { vault::Vault::store_payout_method_data_in_locker( state, - Some(payout_token.to_string()), + Some(temp_token.to_string()), &pm_parsed, - Some(pm.customer_id.to_owned()), + Some(customer_id.to_owned()), key_store, ) .await @@ -2893,6 +3001,7 @@ pub async fn retrieve_payment_method( payment_method_id: pm.payment_method_id, payment_method: pm.payment_method, payment_method_type: pm.payment_method_type, + bank_transfer: None, card, metadata: pm.metadata, created: Some(pm.created_at), diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 5506dc7eb9ac..da4f03b49c1e 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -324,6 +324,28 @@ pub async fn mk_add_locker_request_hs<'a>( Ok(request) } +pub fn mk_add_bank_response_hs( + bank: api::BankPayout, + bank_reference: String, + req: api::PaymentMethodCreate, + merchant_id: &str, +) -> api::PaymentMethodResponse { + api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: req.customer_id, + payment_method_id: bank_reference, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + bank_transfer: Some(bank), + card: None, + metadata: req.metadata, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, // [#256] + installment_payment_enabled: false, // #[#256] + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), // [#256] + } +} + pub fn mk_add_card_response_hs( card: api::CardDetail, card_reference: String, @@ -349,6 +371,7 @@ pub fn mk_add_card_response_hs( payment_method_id: card_reference, payment_method: req.payment_method, payment_method_type: req.payment_method_type, + bank_transfer: None, card: Some(card), metadata: req.metadata, created: Some(common_utils::date_time::now()), @@ -383,6 +406,7 @@ pub fn mk_add_card_response( payment_method_id: response.card_id, payment_method: req.payment_method, payment_method_type: req.payment_method_type, + bank_transfer: None, card: Some(card), metadata: req.metadata, created: Some(common_utils::date_time::now()), diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 070bca234c8e..063b69687577 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -351,7 +351,7 @@ impl Vaultable for api::CardPayout { card_number: self.card_number.peek().clone(), exp_year: self.expiry_year.peek().clone(), exp_month: self.expiry_month.peek().clone(), - name_on_card: Some(self.card_holder_name.peek().clone()), + name_on_card: self.card_holder_name.clone().map(|n| n.peek().to_string()), nickname: None, card_last_four: None, card_token: None, @@ -397,7 +397,7 @@ impl Vaultable for api::CardPayout { .map_err(|_| errors::VaultError::FetchCardFailed)?, expiry_month: value1.exp_month.into(), expiry_year: value1.exp_year.into(), - card_holder_name: value1.name_on_card.unwrap_or_default().into(), + card_holder_name: value1.name_on_card.map(masking::Secret::new), }; let supp_data = SupplementaryVaultData { @@ -421,9 +421,9 @@ pub struct TokenizedBankSensitiveValues { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TokenizedBankInsensitiveValues { pub customer_id: Option, - pub bank_name: String, - pub bank_country_code: api::enums::CountryAlpha2, - pub bank_city: String, + pub bank_name: Option, + pub bank_country_code: Option, + pub bank_city: Option, } #[cfg(feature = "payouts")] @@ -702,7 +702,8 @@ impl Vault { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error getting Value2 for locker")?; - let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token")); + let lookup_key = + token_id.unwrap_or_else(|| generate_id_with_default_len("temporary_token")); let lookup_key = create_tokenize( state, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7230d74e9a98..92dc1bf5f4b3 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1041,6 +1041,7 @@ pub(crate) async fn get_payment_method_create_request( payment_method_type, payment_method_issuer: card.card_issuer.clone(), payment_method_issuer_code: None, + bank_transfer: None, card: Some(card_detail), metadata: None, customer_id: Some(customer_id), @@ -1057,6 +1058,7 @@ pub(crate) async fn get_payment_method_create_request( payment_method_type, payment_method_issuer: None, payment_method_issuer_code: None, + bank_transfer: None, card: None, metadata: None, customer_id: Some(customer.customer_id.to_owned()), diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 551d1c8abb9a..f884cb79e7e1 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -198,6 +198,7 @@ pub async fn save_in_locker( payment_method_id: pm_id, payment_method: payment_method_request.payment_method, payment_method_type: payment_method_request.payment_method_type, + bank_transfer: None, card: None, metadata: None, created: Some(common_utils::date_time::now()), diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 9ddc8395738e..56e3a6faf537 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -1,4 +1,7 @@ -use common_utils::{errors::CustomResult, ext_traits::ValueExt}; +use common_utils::{ + errors::CustomResult, + ext_traits::{StringExt, ValueExt}, +}; use diesel_models::encryption::Encryption; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -40,28 +43,50 @@ pub async fn make_payout_method_data<'a>( merchant_key_store: &domain::MerchantKeyStore, ) -> RouterResult> { let db = &*state.store; + let certain_payout_type = payout_type.get_required_value("payout_type")?.to_owned(); let hyperswitch_token = if let Some(payout_token) = payout_token { - let key = format!( - "pm_token_{}_{}_hyperswitch", - payout_token, - api_enums::PaymentMethod::foreign_from( - payout_type.get_required_value("payout_type")?.to_owned() - ) - ); - - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; - - let hyperswitch_token_option = redis_conn - .get_key::>(&key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch the token from redis")?; + if payout_token.starts_with("temporary_token_") { + Some(payout_token.to_string()) + } else { + let key = format!( + "pm_token_{}_{}_hyperswitch", + payout_token, + api_enums::PaymentMethod::foreign_from(certain_payout_type) + ); + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; - hyperswitch_token_option.or(Some(payout_token.to_string())) + let hyperswitch_token = redis_conn + .get_key::>(&key) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch the token from redis")? + .ok_or(error_stack::Report::new( + errors::ApiErrorResponse::UnprocessableEntity { + message: "Token is invalid or expired".to_owned(), + }, + ))?; + let payment_token_data = hyperswitch_token + .clone() + .parse_struct("PaymentTokenData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to deserialize hyperswitch token data")?; + + let payment_token = match payment_token_data { + storage::PaymentTokenData::PermanentCard(storage::CardTokenData { token }) => { + Some(token) + } + storage::PaymentTokenData::TemporaryGeneric(storage::GenericTokenData { + token, + }) => Some(token), + _ => None, + }; + payment_token.or(Some(payout_token.to_string())) + } } else { None }; @@ -69,8 +94,10 @@ pub async fn make_payout_method_data<'a>( match (payout_method_data.to_owned(), hyperswitch_token) { // Get operation (None, Some(payout_token)) => { - let (pm, supplementary_data) = - vault::Vault::get_payout_method_data_from_temporary_locker( + if payout_token.starts_with("temporary_token_") + || certain_payout_type == api_enums::PayoutType::Bank + { + let (pm, supplementary_data) = vault::Vault::get_payout_method_data_from_temporary_locker( state, &payout_token, merchant_key_store, @@ -79,15 +106,33 @@ pub async fn make_payout_method_data<'a>( .attach_printable( "Payout method for given token not found or there was a problem fetching it", )?; - utils::when( - supplementary_data - .customer_id - .ne(&Some(customer_id.to_owned())), - || { - Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payout method and customer passed in payout are not same".into() }) - }, - )?; - Ok(pm) + utils::when( + supplementary_data + .customer_id + .ne(&Some(customer_id.to_owned())), + || { + Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payout method and customer passed in payout are not same".into() }) + }, + )?; + Ok(pm) + } else { + let resp = cards::get_card_from_locker( + state, + customer_id, + merchant_id, + payout_token.as_ref(), + ) + .await + .attach_printable("Payout method [card] could not be fetched from HS locker")?; + Ok(Some({ + api::PayoutMethodData::Card(api::CardPayout { + card_number: resp.card_number, + expiry_month: resp.card_exp_month, + expiry_year: resp.card_exp_year, + card_holder_name: resp.name_on_card, + }) + })) + } } // Create / Update operation @@ -131,11 +176,11 @@ pub async fn save_payout_data_to_locker( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { - let (locker_req, card_details, payment_method_type) = match payout_method_data { + let (locker_req, card_details, bank_details, payment_method_type) = match payout_method_data { api_models::payouts::PayoutMethodData::Card(card) => { let card_detail = api::CardDetail { card_number: card.card_number.to_owned(), - card_holder_name: Some(card.card_holder_name.to_owned()), + card_holder_name: card.card_holder_name.to_owned(), card_exp_month: card.expiry_month.to_owned(), card_exp_year: card.expiry_year.to_owned(), nick_name: None, @@ -145,7 +190,7 @@ pub async fn save_payout_data_to_locker( merchant_customer_id: payout_attempt.customer_id.to_owned(), card: transformers::Card { card_number: card.card_number.to_owned(), - name_on_card: Some(card.card_holder_name.to_owned()), + name_on_card: card.card_holder_name.to_owned(), card_exp_month: card.expiry_month.to_owned(), card_exp_year: card.expiry_year.to_owned(), card_brand: None, @@ -157,6 +202,7 @@ pub async fn save_payout_data_to_locker( ( payload, Some(card_detail), + None, api_enums::PaymentMethodType::Debit, ) } @@ -191,6 +237,7 @@ pub async fn save_payout_data_to_locker( ( payload, None, + Some(bank.to_owned()), api_enums::PaymentMethodType::foreign_from(bank.to_owned()), ) } @@ -244,6 +291,7 @@ pub async fn save_payout_data_to_locker( payment_method_type: Some(payment_method_type), payment_method_issuer: None, payment_method_issuer_code: None, + bank_transfer: bank_details, card: card_details, metadata: None, customer_id: Some(payout_attempt.customer_id.to_owned()), diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 5acb66b5068e..ca852f832ee8 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,4 +1,3 @@ -use api_models::enums as api_enums; pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, @@ -9,9 +8,9 @@ pub use api_models::payment_methods::{ }; use error_stack::report; -use crate::{ - core::errors::{self, RouterResult}, - types::transformers::ForeignFrom, +use crate::core::{ + errors::{self, RouterResult}, + payments::helpers::validate_payment_method_type_against_payment_method, }; pub(crate) trait PaymentMethodCreateExt { @@ -21,16 +20,16 @@ pub(crate) trait PaymentMethodCreateExt { // convert self.payment_method_type to payment_method and compare it against self.payment_method impl PaymentMethodCreateExt for PaymentMethodCreate { fn validate(&self) -> RouterResult<()> { - let payment_method: Option = - self.payment_method_type.map(ForeignFrom::foreign_from); - if payment_method - .map(|payment_method| payment_method != self.payment_method) - .unwrap_or(false) - { - return Err(report!(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid 'payment_method_type' provided".to_string() - }) - .attach_printable("Invalid payment method type")); + if let Some(payment_method_type) = self.payment_method_type { + if !validate_payment_method_type_against_payment_method( + self.payment_method, + payment_method_type, + ) { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid 'payment_method_type' provided".to_string() + }) + .attach_printable("Invalid payment method type")); + } } Ok(()) } diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 97dca3baa52b..490750805062 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -95,16 +95,16 @@ impl AdyenTest { card_number: cards::CardNumber::from_str("4111111111111111").unwrap(), expiry_month: Secret::new("3".to_string()), expiry_year: Secret::new("2030".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(Secret::new("John Doe".to_string())), })) } enums::PayoutType::Bank => Some(api::PayoutMethodData::Bank( api::payouts::BankPayout::Sepa(api::SepaBankTransfer { iban: "NL46TEST0136169112".to_string().into(), bic: Some("ABNANL2A".to_string().into()), - bank_name: "Deutsche Bank".to_string(), - bank_country_code: enums::CountryAlpha2::NL, - bank_city: "Amsterdam".to_string(), + bank_name: Some("Deutsche Bank".to_string()), + bank_country_code: Some(enums::CountryAlpha2::NL), + bank_city: Some("Amsterdam".to_string()), }), )), }, diff --git a/crates/router/tests/connectors/wise.rs b/crates/router/tests/connectors/wise.rs index fb65397e1a22..de303523040e 100644 --- a/crates/router/tests/connectors/wise.rs +++ b/crates/router/tests/connectors/wise.rs @@ -73,9 +73,9 @@ impl WiseTest { api::BacsBankTransfer { bank_sort_code: "231470".to_string().into(), bank_account_number: "28821822".to_string().into(), - bank_name: "Deutsche Bank".to_string(), - bank_country_code: enums::CountryAlpha2::NL, - bank_city: "Amsterdam".to_string(), + bank_name: Some("Deutsche Bank".to_string()), + bank_country_code: Some(enums::CountryAlpha2::NL), + bank_city: Some("Amsterdam".to_string()), }, ))), ..Default::default() diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 466489e2f9f9..b2f5d3ea52c3 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -2541,9 +2541,6 @@ "AchBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "bank_account_number", "bank_routing_number" ], @@ -2551,15 +2548,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "bank_account_number": { "type": "string", @@ -2993,9 +2997,6 @@ "BacsBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "bank_account_number", "bank_sort_code" ], @@ -3003,15 +3004,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "bank_account_number": { "type": "string", @@ -9139,6 +9147,14 @@ "description": "The card network", "example": "Visa", "nullable": true + }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true } } }, @@ -9475,6 +9491,14 @@ "description": "A timestamp (ISO 8601 code) that determines when the customer was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true + }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true } } }, @@ -9585,6 +9609,14 @@ ], "nullable": true }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true + }, "metadata": { "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", @@ -12294,9 +12326,6 @@ "SepaBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "iban", "bic" ], @@ -12304,15 +12333,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "iban": { "type": "string", From 68a3a280676c8309f9becffae545b134b5e1f2ea Mon Sep 17 00:00:00 2001 From: ivor-juspay <138492857+ivor-juspay@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:07:41 +0530 Subject: [PATCH 08/48] feat(connector_events): added api to fetch connector event logs (#3319) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: harsh-sharma-juspay <125131007+harsh-sharma-juspay@users.noreply.github.com> --- crates/analytics/src/clickhouse.rs | 16 +++++ crates/analytics/src/connector_events.rs | 5 ++ crates/analytics/src/connector_events/core.rs | 27 ++++++++ .../analytics/src/connector_events/events.rs | 63 +++++++++++++++++++ crates/analytics/src/lib.rs | 1 + crates/analytics/src/sqlx.rs | 2 + crates/analytics/src/types.rs | 1 + crates/api_models/src/analytics.rs | 1 + .../src/analytics/connector_events.rs | 11 ++++ crates/api_models/src/events.rs | 5 +- crates/router/src/analytics.rs | 29 ++++++++- crates/router_env/src/lib.rs | 1 + 12 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 crates/analytics/src/connector_events.rs create mode 100644 crates/analytics/src/connector_events/core.rs create mode 100644 crates/analytics/src/connector_events/events.rs create mode 100644 crates/api_models/src/analytics/connector_events.rs diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index b8fd5e6a35d0..f81c29c801c0 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -21,6 +21,7 @@ use crate::{ filters::ApiEventFilter, metrics::{latency::LatencyAvg, ApiEventMetricRow}, }, + connector_events::events::ConnectorEventsResult, outgoing_webhook_event::events::OutgoingWebhookLogsResult, sdk_events::events::SdkEventsResult, types::TableEngine, @@ -121,6 +122,7 @@ impl AnalyticsDataSource for ClickhouseClient { } AnalyticsCollection::SdkEvents => TableEngine::BasicTree, AnalyticsCollection::ApiEvents => TableEngine::BasicTree, + AnalyticsCollection::ConnectorEvents => TableEngine::BasicTree, AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree, } } @@ -147,6 +149,7 @@ impl super::sdk_events::events::SdkEventsFilterAnalytics for ClickhouseClient {} impl super::api_event::events::ApiLogsFilterAnalytics for ClickhouseClient {} impl super::api_event::filters::ApiEventFilterAnalytics for ClickhouseClient {} impl super::api_event::metrics::ApiEventMetricAnalytics for ClickhouseClient {} +impl super::connector_events::events::ConnectorEventLogAnalytics for ClickhouseClient {} impl super::outgoing_webhook_event::events::OutgoingWebhookLogsFilterAnalytics for ClickhouseClient { @@ -188,6 +191,18 @@ impl TryInto for serde_json::Value { } } +impl TryInto for serde_json::Value { + type Error = Report; + + fn try_into(self) -> Result { + serde_json::from_value(self) + .into_report() + .change_context(ParsingError::StructParseFailure( + "Failed to parse ConnectorEventsResult in clickhouse results", + )) + } +} + impl TryInto for serde_json::Value { type Error = Report; @@ -344,6 +359,7 @@ impl ToSql for AnalyticsCollection { Self::SdkEvents => Ok("sdk_events_dist".to_string()), Self::ApiEvents => Ok("api_audit_log".to_string()), Self::PaymentIntent => Ok("payment_intents_dist".to_string()), + Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), } } diff --git a/crates/analytics/src/connector_events.rs b/crates/analytics/src/connector_events.rs new file mode 100644 index 000000000000..c7c31306a2c7 --- /dev/null +++ b/crates/analytics/src/connector_events.rs @@ -0,0 +1,5 @@ +mod core; +pub mod events; +pub trait ConnectorEventAnalytics: events::ConnectorEventLogAnalytics {} + +pub use self::core::connector_events_core; diff --git a/crates/analytics/src/connector_events/core.rs b/crates/analytics/src/connector_events/core.rs new file mode 100644 index 000000000000..15f841af5f82 --- /dev/null +++ b/crates/analytics/src/connector_events/core.rs @@ -0,0 +1,27 @@ +use api_models::analytics::connector_events::ConnectorEventsRequest; +use common_utils::errors::ReportSwitchExt; +use error_stack::{IntoReport, ResultExt}; + +use super::events::{get_connector_events, ConnectorEventsResult}; +use crate::{errors::AnalyticsResult, types::FiltersError, AnalyticsProvider}; + +pub async fn connector_events_core( + pool: &AnalyticsProvider, + req: ConnectorEventsRequest, + merchant_id: String, +) -> AnalyticsResult> { + let data = match pool { + AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented( + "Connector Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for Connector Events"), + AnalyticsProvider::Clickhouse(ckh_pool) + | AnalyticsProvider::CombinedSqlx(_, ckh_pool) + | AnalyticsProvider::CombinedCkh(_, ckh_pool) => { + get_connector_events(&merchant_id, req, ckh_pool).await + } + } + .switch()?; + Ok(data) +} diff --git a/crates/analytics/src/connector_events/events.rs b/crates/analytics/src/connector_events/events.rs new file mode 100644 index 000000000000..096520777eeb --- /dev/null +++ b/crates/analytics/src/connector_events/events.rs @@ -0,0 +1,63 @@ +use api_models::analytics::{ + connector_events::{ConnectorEventsRequest, QueryType}, + Granularity, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow}, +}; +pub trait ConnectorEventLogAnalytics: LoadRow {} + +pub async fn get_connector_events( + merchant_id: &String, + query_param: ConnectorEventsRequest, + pool: &T, +) -> FiltersResult> +where + T: AnalyticsDataSource + ConnectorEventLogAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::ConnectorEvents); + query_builder.add_select_column("*").switch()?; + + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; + match query_param.query_param { + QueryType::Payment { payment_id } => query_builder + .add_filter_clause("payment_id", payment_id) + .switch()?, + } + //TODO!: update the execute_query function to return reports instead of plain errors... + query_builder + .execute_query::(pool) + .await + .change_context(FiltersError::QueryBuildingError)? + .change_context(FiltersError::QueryExecutionFailure) +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ConnectorEventsResult { + pub merchant_id: String, + pub payment_id: String, + pub connector_name: Option, + pub request_id: Option, + pub flow: String, + pub request: String, + pub response: Option, + pub error: Option, + pub status_code: u16, + pub latency: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + pub method: Option, +} diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index 8529807a1a16..501bd58527c3 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -7,6 +7,7 @@ mod query; pub mod refunds; pub mod api_event; +pub mod connector_events; pub mod outgoing_webhook_event; pub mod sdk_events; mod sqlx; diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index e32b85a53672..7ab8a2aa4bc5 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -429,6 +429,8 @@ impl ToSql for AnalyticsCollection { Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::PaymentIntent => Ok("payment_intent".to_string()), + Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ConnectorEvents table is not implemented for Sqlx"))?, Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, } diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index 8da4655e255b..18e9e9f4334b 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -26,6 +26,7 @@ pub enum AnalyticsCollection { SdkEvents, ApiEvents, PaymentIntent, + ConnectorEvents, OutgoingWebhookEvent, } diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index e0d3fa671b60..c6ca215f9f7c 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -12,6 +12,7 @@ use self::{ pub use crate::payments::TimeRange; pub mod api_event; +pub mod connector_events; pub mod outgoing_webhook_event; pub mod payments; pub mod refunds; diff --git a/crates/api_models/src/analytics/connector_events.rs b/crates/api_models/src/analytics/connector_events.rs new file mode 100644 index 000000000000..b2974b0a3392 --- /dev/null +++ b/crates/api_models/src/analytics/connector_events.rs @@ -0,0 +1,11 @@ +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(tag = "type")] +pub enum QueryType { + Payment { payment_id: String }, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ConnectorEventsRequest { + #[serde(flatten)] + pub query_param: QueryType, +} diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 26a9d222d6b9..43a72b7e3922 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -17,10 +17,12 @@ use common_utils::{ impl_misc_api_event_type, }; +#[allow(unused_imports)] use crate::{ admin::*, analytics::{ - api_event::*, outgoing_webhook_event::OutgoingWebhookLogsRequest, sdk_events::*, *, + api_event::*, connector_events::ConnectorEventsRequest, + outgoing_webhook_event::OutgoingWebhookLogsRequest, sdk_events::*, *, }, api_keys::*, cards_info::*, @@ -94,6 +96,7 @@ impl_misc_api_event_type!( GetApiEventMetricRequest, SdkEventsRequest, ReportRequest, + ConnectorEventsRequest, OutgoingWebhookLogsRequest ); diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index c62de5bd29ab..3f0febcc592c 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -3,7 +3,8 @@ pub use analytics::*; pub mod routes { use actix_web::{web, Responder, Scope}; use analytics::{ - api_event::api_events_core, errors::AnalyticsError, lambda_utils::invoke_lambda, + api_event::api_events_core, connector_events::connector_events_core, + errors::AnalyticsError, lambda_utils::invoke_lambda, outgoing_webhook_event::outgoing_webhook_events_core, sdk_events::sdk_events_core, }; use api_models::analytics::{ @@ -71,6 +72,10 @@ pub mod routes { ) .service(web::resource("api_event_logs").route(web::get().to(get_api_events))) .service(web::resource("sdk_event_logs").route(web::post().to(get_sdk_events))) + .service( + web::resource("connector_event_logs") + .route(web::get().to(get_connector_events)), + ) .service( web::resource("outgoing_webhook_event_logs") .route(web::get().to(get_outgoing_webhook_events)), @@ -585,4 +590,26 @@ pub mod routes { )) .await } + + pub async fn get_connector_events( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Query, + ) -> impl Responder { + let flow = AnalyticsFlow::GetConnectorEvents; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: AuthenticationData, req| async move { + connector_events_core(&state.pool, req, auth.merchant_account.merchant_id) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth(Permission::Analytics), + api_locking::LockAction::NotApplicable, + )) + .await + } } diff --git a/crates/router_env/src/lib.rs b/crates/router_env/src/lib.rs index 0127d07170fd..9139b5eed417 100644 --- a/crates/router_env/src/lib.rs +++ b/crates/router_env/src/lib.rs @@ -52,6 +52,7 @@ pub enum AnalyticsFlow { GenerateRefundReport, GetApiEventMetrics, GetApiEventFilters, + GetConnectorEvents, GetOutgoingWebhookEvents, } From 387c1c491bdc413ae361d04f0be25eaa58e72fa9 Mon Sep 17 00:00:00 2001 From: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:28:49 +0530 Subject: [PATCH 09/48] refactor(connector): [cybersource] recurring mandate flow (#3354) --- crates/router/src/connector/cybersource.rs | 15 +- .../src/connector/cybersource/transformers.rs | 281 ++++++++++-------- crates/router/src/services/api.rs | 6 +- 3 files changed, 170 insertions(+), 132 deletions(-) diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index b300e97b44a9..ac2d16c9610e 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -785,7 +785,10 @@ impl ConnectorIntegration CustomResult { - if req.is_three_ds() && req.request.is_card() { + if req.is_three_ds() + && req.request.is_card() + && req.request.connector_mandate_id().is_none() + { Ok(format!( "{}risk/v1/authentication-setups", api::ConnectorCommon::base_url(self, connectors) @@ -809,7 +812,10 @@ impl ConnectorIntegration CustomResult { - if data.is_three_ds() && data.request.is_card() { + if data.is_three_ds() + && data.request.is_card() + && data.request.connector_mandate_id().is_none() + { let response: cybersource::CybersourceAuthSetupResponse = res .response .parse_struct("Cybersource AuthSetupResponse") diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index e83b23603e9b..8beb81d92368 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -342,7 +342,7 @@ pub struct ApplePayPaymentInformation { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct MandatePaymentInformation { - payment_instrument: Option, + payment_instrument: CybersoucrePaymentInstrument, } #[derive(Debug, Serialize)] @@ -482,7 +482,7 @@ impl ), ) -> Self { let (action_list, action_token_types, authorization_options) = - if item.router_data.request.setup_future_usage.is_some() { + if item.router_data.request.setup_mandate_details.is_some() { ( Some(vec![CybersourceActionsList::TokenCreate]), Some(vec![CybersourceActionsTokenType::PaymentInstrument]), @@ -871,139 +871,168 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> fn try_from( item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - payments::WalletData::ApplePay(apple_pay_data) => { - match item.router_data.payment_method_token.clone() { - Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - Self::try_from((item, decrypt_data)) - } - types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } - }, - None => { - let email = item.router_data.request.get_email()?; - let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); - let processing_information = ProcessingInformation::from(( - item, - Some(PaymentSolution::ApplePay), - )); - let client_reference_information = - ClientReferenceInformation::from(item); - let payment_information = PaymentInformation::ApplePayToken( - ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, - }, + match item.router_data.request.connector_mandate_id() { + Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), + None => { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(apple_pay_data) => { + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } }, - ); - let merchant_defined_information = - item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from( - metadata.peek().to_owned(), - ) - }); - - Ok(Self { - processing_information, - payment_information, - order_information, - client_reference_information, - merchant_defined_information, - consumer_authentication_information: None, - }) + None => { + let email = item.router_data.request.get_email()?; + let bill_to = + build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = + OrderInformationWithBill::from((item, bill_to)); + let processing_information = ProcessingInformation::from(( + item, + Some(PaymentSolution::ApplePay), + )); + let client_reference_information = + ClientReferenceInformation::from(item); + let payment_information = PaymentInformation::ApplePayToken( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + ); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from( + metadata.peek().to_owned(), + ) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } + } + } + payments::WalletData::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) } + payments::WalletData::AliPayQr(_) + | payments::WalletData::AliPayRedirect(_) + | payments::WalletData::AliPayHkRedirect(_) + | payments::WalletData::MomoRedirect(_) + | payments::WalletData::KakaoPayRedirect(_) + | payments::WalletData::GoPayRedirect(_) + | payments::WalletData::GcashRedirect(_) + | payments::WalletData::ApplePayRedirect(_) + | payments::WalletData::ApplePayThirdPartySdk(_) + | payments::WalletData::DanaRedirect {} + | payments::WalletData::GooglePayRedirect(_) + | payments::WalletData::GooglePayThirdPartySdk(_) + | payments::WalletData::MbWayRedirect(_) + | payments::WalletData::MobilePayRedirect(_) + | payments::WalletData::PaypalRedirect(_) + | payments::WalletData::PaypalSdk(_) + | payments::WalletData::SamsungPay(_) + | payments::WalletData::TwintRedirect {} + | payments::WalletData::VippsRedirect {} + | payments::WalletData::TouchNGoRedirect(_) + | payments::WalletData::WeChatPayRedirect(_) + | payments::WalletData::WeChatPayQr(_) + | payments::WalletData::CashappQr(_) + | payments::WalletData::SwishQr(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "Cybersource", + ), + ) + .into()) + } + }, + // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. + // This is a fallback implementation in the event of catastrophe. + payments::PaymentMethodData::MandatePayment => { + let connector_mandate_id = + item.router_data.request.connector_mandate_id().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_id", + }, + )?; + Self::try_from((item, connector_mandate_id)) + } + payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ) + .into()) } } - payments::WalletData::GooglePay(google_pay_data) => { - Self::try_from((item, google_pay_data)) - } - payments::WalletData::AliPayQr(_) - | payments::WalletData::AliPayRedirect(_) - | payments::WalletData::AliPayHkRedirect(_) - | payments::WalletData::MomoRedirect(_) - | payments::WalletData::KakaoPayRedirect(_) - | payments::WalletData::GoPayRedirect(_) - | payments::WalletData::GcashRedirect(_) - | payments::WalletData::ApplePayRedirect(_) - | payments::WalletData::ApplePayThirdPartySdk(_) - | payments::WalletData::DanaRedirect {} - | payments::WalletData::GooglePayRedirect(_) - | payments::WalletData::GooglePayThirdPartySdk(_) - | payments::WalletData::MbWayRedirect(_) - | payments::WalletData::MobilePayRedirect(_) - | payments::WalletData::PaypalRedirect(_) - | payments::WalletData::PaypalSdk(_) - | payments::WalletData::SamsungPay(_) - | payments::WalletData::TwintRedirect {} - | payments::WalletData::VippsRedirect {} - | payments::WalletData::TouchNGoRedirect(_) - | payments::WalletData::WeChatPayRedirect(_) - | payments::WalletData::WeChatPayQr(_) - | payments::WalletData::CashappQr(_) - | payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Cybersource"), - ) - .into()), - }, - payments::PaymentMethodData::MandatePayment => { - let processing_information = ProcessingInformation::from((item, None)); - let payment_instrument = - item.router_data - .request - .connector_mandate_id() - .map(|mandate_token_id| CybersoucrePaymentInstrument { - id: mandate_token_id, - }); - - let email = item.router_data.request.get_email()?; - let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; - let order_information = OrderInformationWithBill::from((item, bill_to)); - let payment_information = - PaymentInformation::MandatePayment(MandatePaymentInformation { - payment_instrument, - }); - let client_reference_information = ClientReferenceInformation::from(item); - let merchant_defined_information = - item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from(metadata.peek().to_owned()) - }); - Ok(Self { - processing_information, - payment_information, - order_information, - client_reference_information, - merchant_defined_information, - consumer_authentication_information: None, - }) - } - payments::PaymentMethodData::CardRedirect(_) - | payments::PaymentMethodData::PayLater(_) - | payments::PaymentMethodData::BankRedirect(_) - | payments::PaymentMethodData::BankDebit(_) - | payments::PaymentMethodData::BankTransfer(_) - | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::Reward - | payments::PaymentMethodData::Upi(_) - | payments::PaymentMethodData::Voucher(_) - | payments::PaymentMethodData::GiftCard(_) - | payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Cybersource"), - ) - .into()) } } } } +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + String, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, connector_mandate_id): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + String, + ), + ) -> Result { + let processing_information = ProcessingInformation::from((item, None)); + let payment_instrument = CybersoucrePaymentInstrument { + id: connector_mandate_id, + }; + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let payment_information = + PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument }); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceAuthSetupRequest { diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 9eb06d675a07..ad463fcf2b92 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1524,12 +1524,12 @@ pub fn build_redirection_form( // This is the iframe recommended by cybersource but the redirection happens inside this iframe once otp // is received and we lose control of the redirection on user client browser, so to avoid that we have removed this iframe and directly consumed it. // (PreEscaped(r#""#)) - (PreEscaped(format!("
- + (PreEscaped(format!(" +
"))) (PreEscaped(r#""#)) }} From 52f38d3d5a7d035e8211e1f51c8f982232e2d7ab Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:39:25 +0530 Subject: [PATCH 10/48] fix(core): add validation for authtype and metadata in update payment connector (#3305) --- crates/router/src/core/admin.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index e8593581126a..fd4cae3a2b9b 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1172,6 +1172,34 @@ pub async fn update_payment_connector( field_name: "connector_account_details".to_string(), expected_format: "auth_type and api_key".to_string(), })?; + let connector_name = mca.connector_name.as_ref(); + let connector_enum = api_models::enums::Connector::from_str(connector_name) + .into_report() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector", + }) + .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?; + validate_auth_and_metadata_type(connector_enum, &auth, &req.metadata).map_err( + |err| match *err.current_context() { + errors::ConnectorError::InvalidConnectorName => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The connector name is invalid".to_string(), + }) + } + errors::ConnectorError::InvalidConnectorConfig { config: field_name } => err + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: format!("The {} is invalid", field_name), + }), + errors::ConnectorError::FailedToObtainAuthType => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The auth type is invalid for the connector".to_string(), + }) + } + _ => err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The request body is invalid".to_string(), + }), + }, + )?; let (connector_status, disabled) = validate_status_and_disabled(req.status, req.disabled, auth, mca.status)?; From 928beecdd7fe9e09b38ffe750627ca4af94ffc93 Mon Sep 17 00:00:00 2001 From: Kashif <46213975+kashif-m@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:03:46 +0530 Subject: [PATCH 11/48] chore(router): remove recon from default features (#3370) Co-authored-by: Kashif --- crates/api_models/Cargo.toml | 2 +- crates/router/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index 45702a4ecb0a..d1f603f188eb 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -default = ["payouts", "frm", "recon"] +default = ["payouts", "frm"] business_profile_routing = [] connector_choice_bcompat = [] errors = ["dep:actix-web", "dep:reqwest"] diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 0a544e0bd090..8897fdac2c22 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,13 +9,13 @@ readme = "README.md" license.workspace = true [features] -default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm", "recon"] +default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"] s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config", "olap"] frm = [] stripe = ["dep:serde_qs"] -release = ["kms", "stripe", "s3", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen"] +release = ["kms", "stripe", "s3", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen", "recon"] olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap", "dep:analytics"] oltp = ["storage_impl/oltp"] kv_store = ["scheduler/kv_store"] @@ -30,7 +30,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect external_access_dc = ["dummy_connector"] detailed_errors = ["api_models/detailed_errors", "error-stack/serde"] payouts = [] -recon = ["email"] +recon = ["email", "api_models/recon"] retry = [] [dependencies] From ac8d81b32b3d91b875113d32782a8c62e39ba2a8 Mon Sep 17 00:00:00 2001 From: Sampras Lopes Date: Wed, 17 Jan 2024 16:31:22 +0530 Subject: [PATCH 12/48] fix(events): fix event generation for paymentmethods list (#3337) --- .github/CODEOWNERS | 11 +++++++++++ crates/api_models/src/events.rs | 1 - crates/api_models/src/events/payment.rs | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a911d26d8650..0eb3d95bfc69 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,17 @@ postman/ @juspay/hyperswitch-framework Cargo.toml @juspay/hyperswitch-framework Cargo.lock @juspay/hyperswitch-framework +crates/api_models/src/events/ @juspay/hyperswitch-analytics +crates/api_models/src/events.rs @juspay/hyperswitch-analytics +crates/api_models/src/analytics/ @juspay/hyperswitch-analytics +crates/api_models/src/analytics.rs @juspay/hyperswitch-analytics +crates/router/src/analytics.rs @juspay/hyperswitch-analytics +crates/router/src/events/ @juspay/hyperswitch-analytics +crates/router/src/events.rs @juspay/hyperswitch-analytics +crates/common_utils/src/events/ @juspay/hyperswitch-analytics +crates/common_utils/src/events.rs @juspay/hyperswitch-analytics +crates/analytics/ @juspay/hyperswitch-analytics + connector-template/ @juspay/hyperswitch-connector crates/router/src/connector/ @juspay/hyperswitch-connector crates/router/tests/connectors/ @juspay/hyperswitch-connector diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 43a72b7e3922..a8185d2d241c 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -39,7 +39,6 @@ impl ApiEventMetric for TimeRange {} impl_misc_api_event_type!( PaymentMethodId, PaymentsSessionResponse, - PaymentMethodListResponse, PaymentMethodCreate, PaymentLinkInitiateRequest, RetrievePaymentLinkResponse, diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index f718dc1ca4dd..32d3dc30bd8d 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -3,7 +3,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::{ payment_methods::{ CustomerPaymentMethodsListResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, - PaymentMethodResponse, PaymentMethodUpdate, + PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, @@ -119,6 +119,8 @@ impl ApiEventMetric for PaymentMethodListRequest { } } +impl ApiEventMetric for PaymentMethodListResponse {} + impl ApiEventMetric for PaymentListFilterConstraints { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) From eb2a61d8597995838f21b8233653c691118b2191 Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:22:27 +0530 Subject: [PATCH 13/48] refactor: [Noon] adding new field max_amount to mandate request (#3209) Co-authored-by: AkshayaFoiger Co-authored-by: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> --- .../router/src/connector/noon/transformers.rs | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index bbf284848b59..81f3ab33e2f3 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -1,5 +1,5 @@ use common_utils::pii; -use error_stack::ResultExt; +use error_stack::{IntoReport, ResultExt}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -7,7 +7,7 @@ use crate::{ connector::utils::{ self as conn_utils, CardData, PaymentsAuthorizeRequestData, RouterData, WalletData, }, - core::errors, + core::{errors, mandate::MandateBehaviour}, services, types::{self, api, storage::enums, transformers::ForeignFrom, ErrorResponse}, utils, @@ -30,11 +30,13 @@ pub enum NoonSubscriptionType { } #[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct NoonSubscriptionData { #[serde(rename = "type")] subscription_type: NoonSubscriptionType, //Short description about the subscription. name: String, + max_amount: String, } #[derive(Debug, Serialize)] @@ -91,7 +93,7 @@ pub struct NoonSubscription { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct NoonCard { - name_on_card: Secret, + name_on_card: Option>, number_plain: cards::CardNumber, expiry_month: Secret, expiry_year: Secret, @@ -158,7 +160,7 @@ pub struct NoonPayPal { } #[derive(Debug, Serialize)] -#[serde(tag = "type", content = "data")] +#[serde(tag = "type", content = "data", rename_all = "UPPERCASE")] pub enum NoonPaymentData { Card(NoonCard), Subscription(NoonSubscription), @@ -200,10 +202,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { _ => ( match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(req_card) => Ok(NoonPaymentData::Card(NoonCard { - name_on_card: req_card - .card_holder_name - .clone() - .unwrap_or(Secret::new("".to_string())), + name_on_card: req_card.card_holder_name.clone(), number_plain: req_card.card_number.clone(), expiry_month: req_card.card_exp_month.clone(), expiry_year: req_card.get_expiry_year_4_digit(), @@ -296,7 +295,11 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { } }?, Some(item.request.currency), - item.request.order_category.clone(), + Some(item.request.order_category.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_category", + }, + )?), ), }; @@ -330,17 +333,33 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { }, }); - let (subscription, tokenize_c_c) = - match item.request.setup_future_usage.is_some().then_some(( - NoonSubscriptionData { - subscription_type: NoonSubscriptionType::Unscheduled, - name: name.clone(), - }, - true, - )) { - Some((a, b)) => (Some(a), Some(b)), - None => (None, None), - }; + let subscription = item + .request + .get_setup_mandate_details() + .map(|mandate_data| { + let max_amount = match &mandate_data.mandate_type { + Some(data_models::mandates::MandateDataType::SingleUse(mandate)) + | Some(data_models::mandates::MandateDataType::MultiUse(Some(mandate))) => { + conn_utils::to_currency_base_unit(mandate.amount, mandate.currency) + } + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "setup_future_usage.mandate_data.mandate_type", + }) + .into_report(), + }?; + + Ok::>( + NoonSubscriptionData { + subscription_type: NoonSubscriptionType::Unscheduled, + name: name.clone(), + max_amount, + }, + ) + }) + .transpose()?; + + let tokenize_c_c = subscription.is_some().then_some(true); + let order = NoonOrder { amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?, currency, From ee0461e9a57b2ff77d26b4a543bd1e407f3fcc1c Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 00:20:02 +0000 Subject: [PATCH 14/48] chore(version): 2024.01.18.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b01fe4e915..b17ee4964b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.18.0 + +### Features + +- **connector_events:** Added api to fetch connector event logs ([#3319](https://github.com/juspay/hyperswitch/pull/3319)) ([`68a3a28`](https://github.com/juspay/hyperswitch/commit/68a3a280676c8309f9becffae545b134b5e1f2ea)) +- **payment_method:** Add capability to store bank details using /payment_methods endpoint ([#3113](https://github.com/juspay/hyperswitch/pull/3113)) ([`01c2de2`](https://github.com/juspay/hyperswitch/commit/01c2de223f60595d77c06a59a40dfe041e02cfee)) + +### Bug Fixes + +- **core:** Add validation for authtype and metadata in update payment connector ([#3305](https://github.com/juspay/hyperswitch/pull/3305)) ([`52f38d3`](https://github.com/juspay/hyperswitch/commit/52f38d3d5a7d035e8211e1f51c8f982232e2d7ab)) +- **events:** Fix event generation for paymentmethods list ([#3337](https://github.com/juspay/hyperswitch/pull/3337)) ([`ac8d81b`](https://github.com/juspay/hyperswitch/commit/ac8d81b32b3d91b875113d32782a8c62e39ba2a8)) + +### Refactors + +- **connector:** [cybersource] recurring mandate flow ([#3354](https://github.com/juspay/hyperswitch/pull/3354)) ([`387c1c4`](https://github.com/juspay/hyperswitch/commit/387c1c491bdc413ae361d04f0be25eaa58e72fa9)) +- [Noon] adding new field max_amount to mandate request ([#3209](https://github.com/juspay/hyperswitch/pull/3209)) ([`eb2a61d`](https://github.com/juspay/hyperswitch/commit/eb2a61d8597995838f21b8233653c691118b2191)) + +### Miscellaneous Tasks + +- **router:** Remove recon from default features ([#3370](https://github.com/juspay/hyperswitch/pull/3370)) ([`928beec`](https://github.com/juspay/hyperswitch/commit/928beecdd7fe9e09b38ffe750627ca4af94ffc93)) + +**Full Changelog:** [`2024.01.17.0...2024.01.18.0`](https://github.com/juspay/hyperswitch/compare/2024.01.17.0...2024.01.18.0) + +- - - + ## 2024.01.17.0 ### Features From acb329672297cd7337d0b0239e4c662257812e8a Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:27:32 +0530 Subject: [PATCH 15/48] refactor(connector): [Volt] Refactor Payments and Refunds Webhooks (#3377) --- crates/router/src/connector/volt.rs | 71 +++++++++---------- .../router/src/connector/volt/transformers.rs | 10 ++- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 39296bb64340..f125f90d93aa 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -635,43 +635,40 @@ impl api::IncomingWebhook for Volt { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - let x_volt_type = - utils::get_header_key_value(webhook_headers::X_VOLT_TYPE, request.headers)?; - if x_volt_type == "refund_confirmed" || x_volt_type == "refund_failed" { - let refund_webhook_body: volt::VoltRefundWebhookBodyReference = request - .body - .parse_struct("VoltRefundWebhookBodyReference") - .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - - let refund_reference = match refund_webhook_body.external_reference { - Some(external_reference) => { - api_models::webhooks::RefundIdType::RefundId(external_reference) - } - None => api_models::webhooks::RefundIdType::ConnectorRefundId( - refund_webhook_body.refund, - ), - }; - Ok(api_models::webhooks::ObjectReferenceId::RefundId( - refund_reference, - )) - } else { - let webhook_body: volt::VoltPaymentWebhookBodyReference = request - .body - .parse_struct("VoltPaymentWebhookBodyReference") - .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - let reference = match webhook_body.merchant_internal_reference { - Some(merchant_internal_reference) => { - api_models::payments::PaymentIdType::PaymentAttemptId( - merchant_internal_reference, - ) - } - None => api_models::payments::PaymentIdType::ConnectorTransactionId( - webhook_body.payment, - ), - }; - Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - reference, - )) + let parsed_webhook_response = request + .body + .parse_struct::("VoltRefundWebhookBodyReference") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + + match parsed_webhook_response { + volt::WebhookResponse::Payment(payment_response) => { + let reference = match payment_response.merchant_internal_reference { + Some(merchant_internal_reference) => { + api_models::payments::PaymentIdType::PaymentAttemptId( + merchant_internal_reference, + ) + } + None => api_models::payments::PaymentIdType::ConnectorTransactionId( + payment_response.payment, + ), + }; + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + reference, + )) + } + volt::WebhookResponse::Refund(refund_response) => { + let refund_reference = match refund_response.external_reference { + Some(external_reference) => { + api_models::webhooks::RefundIdType::RefundId(external_reference) + } + None => api_models::webhooks::RefundIdType::ConnectorRefundId( + refund_response.refund, + ), + }; + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + refund_reference, + )) + } } } diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs index 8b9bbecb0889..b1d77f3416ff 100644 --- a/crates/router/src/connector/volt/transformers.rs +++ b/crates/router/src/connector/volt/transformers.rs @@ -46,7 +46,6 @@ pub mod webhook_headers { pub const X_VOLT_SIGNED: &str = "X-Volt-Signed"; pub const X_VOLT_TIMED: &str = "X-Volt-Timed"; pub const USER_AGENT: &str = "User-Agent"; - pub const X_VOLT_TYPE: &str = "X-Volt-Type"; } #[derive(Debug, Serialize)] @@ -488,6 +487,15 @@ pub struct VoltRefundWebhookBodyReference { pub external_reference: Option, } +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum WebhookResponse { + // the enum order shouldn't be changed as this is being used during serialization and deserialization + Refund(VoltRefundWebhookBodyReference), + Payment(VoltPaymentWebhookBodyReference), +} + #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum VoltWebhookBodyEventType { From 2f693ad1fd857280ef30c6cc0297fb926f0e79e8 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit <64925866+apoorvdixit88@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:51:47 +0530 Subject: [PATCH 16/48] fix(user): fetch profile_id for sample data (#3358) --- crates/router/src/utils/user/sample_data.rs | 23 ++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index dcf635595e0f..3fa2a10629e5 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -52,7 +52,7 @@ pub async fn generate_sample_data( let business_label_default = merchant_parsed_details.first().map(|x| x.business.clone()); - let profile_id = crate::core::utils::get_profile_id_from_business_details( + let profile_id = match crate::core::utils::get_profile_id_from_business_details( business_country_default, business_label_default.as_ref(), &merchant_from_db, @@ -61,8 +61,25 @@ pub async fn generate_sample_data( false, ) .await - .change_context(SampleDataError::InternalServerError) - .attach_printable("Failed to get business profile")?; + { + Ok(id) => id.clone(), + Err(error) => { + router_env::logger::error!( + "Profile ID not found in business details. Attempting to fetch from the database {error:?}" + ); + + state + .store + .list_business_profile_by_merchant_id(&merchant_id) + .await + .change_context(SampleDataError::InternalServerError) + .attach_printable("Failed to get business profile")? + .first() + .ok_or(SampleDataError::InternalServerError)? + .profile_id + .clone() + } + }; // 10 percent payments should be failed #[allow(clippy::as_conversions)] From e816ccfbdd7b0e24464aa93421e399d63f23b17c Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Thu, 18 Jan 2024 14:24:10 +0530 Subject: [PATCH 17/48] fix(connector): Trustpay zen error mapping (#3255) Co-authored-by: Prasunna Soppa Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/connector/trustpay/transformers.rs | 48 ++++++- .../router/src/connector/zen/transformers.rs | 127 ++++++++++++++---- 2 files changed, 140 insertions(+), 35 deletions(-) diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 87d98c1b1bee..4d8e47ab0dc3 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -813,7 +813,7 @@ fn handle_bank_redirects_sync_response( errors::ConnectorError, > { let status = enums::AttemptStatus::from(response.payment_information.status); - let error = if status == enums::AttemptStatus::AuthorizationFailed { + let error = if utils::is_payment_failure(status) { let reason_info = response .payment_information .status_reason_information @@ -856,6 +856,7 @@ fn handle_bank_redirects_sync_response( pub fn handle_webhook_response( payment_information: WebhookPaymentInformation, + status_code: u16, ) -> CustomResult< ( enums::AttemptStatus, @@ -865,6 +866,22 @@ pub fn handle_webhook_response( errors::ConnectorError, > { let status = enums::AttemptStatus::try_from(payment_information.status)?; + let error = if utils::is_payment_failure(status) { + let reason_info = payment_information + .status_reason_information + .unwrap_or_default(); + Some(types::ErrorResponse { + code: reason_info.reason.code.clone(), + // message vary for the same code, so relying on code alone as it is unique + message: reason_info.reason.code, + reason: reason_info.reason.reject_reason, + status_code, + attempt_status: None, + connector_transaction_id: payment_information.references.payment_request_id.clone(), + }) + } else { + None + }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, redirection_data: None, @@ -874,7 +891,7 @@ pub fn handle_webhook_response( connector_response_reference_id: None, incremental_authorization_allowed: None, }; - Ok((status, None, payment_response_data)) + Ok((status, error, payment_response_data)) } pub fn get_trustpay_response( @@ -901,7 +918,9 @@ pub fn get_trustpay_response( TrustpayPaymentsResponse::BankRedirectError(response) => { handle_bank_redirects_error_response(*response, status_code) } - TrustpayPaymentsResponse::WebhookResponse(response) => handle_webhook_response(*response), + TrustpayPaymentsResponse::WebhookResponse(response) => { + handle_webhook_response(*response, status_code) + } } } @@ -1452,9 +1471,24 @@ fn handle_cards_refund_response( fn handle_webhooks_refund_response( response: WebhookPaymentInformation, + status_code: u16, ) -> CustomResult<(Option, types::RefundsResponseData), errors::ConnectorError> { let refund_status = diesel_models::enums::RefundStatus::try_from(response.status)?; + 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(), + // message vary for the same code, so relying on code alone as it is unique + message: reason_info.reason.code, + reason: reason_info.reason.reject_reason, + status_code, + attempt_status: None, + connector_transaction_id: response.references.payment_request_id.clone(), + }) + } else { + None + }; let refund_response_data = types::RefundsResponseData { connector_refund_id: response .references @@ -1462,7 +1496,7 @@ fn handle_webhooks_refund_response( .ok_or(errors::ConnectorError::MissingConnectorRefundID)?, refund_status, }; - Ok((None, refund_response_data)) + Ok((error, refund_response_data)) } fn handle_bank_redirects_refund_response( @@ -1495,7 +1529,7 @@ fn handle_bank_redirects_refund_sync_response( status_code: u16, ) -> (Option, types::RefundsResponseData) { let refund_status = enums::RefundStatus::from(response.payment_information.status); - let error = if refund_status == enums::RefundStatus::Failure { + let error = if utils::is_refund_failure(refund_status) { let reason_info = response .payment_information .status_reason_information @@ -1551,7 +1585,9 @@ impl TryFrom> RefundResponse::CardsRefund(response) => { handle_cards_refund_response(*response, item.http_code)? } - RefundResponse::WebhookRefund(response) => handle_webhooks_refund_response(*response)?, + RefundResponse::WebhookRefund(response) => { + handle_webhooks_refund_response(*response, item.http_code)? + } RefundResponse::BankRedirectRefund(response) => { handle_bank_redirects_refund_response(*response, item.http_code) } diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index 7ea6953a3f2e..0adae0d00bdb 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -11,6 +11,7 @@ use crate::{ connector::utils::{ self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData, }, + consts, core::errors::{self, CustomResult}, services::{self, Method}, types::{self, api, storage::enums, transformers::ForeignTryFrom}, @@ -848,12 +849,15 @@ impl ForeignTryFrom<(ZenPaymentStatus, Option)> for enums::AttemptSt } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApiResponse { status: ZenPaymentStatus, id: String, + // merchant_transaction_id: Option, merchant_action: Option, + reject_code: Option, + reject_reason: Option, } #[derive(Debug, Deserialize)] @@ -869,18 +873,18 @@ pub struct CheckoutResponse { redirect_url: url::Url, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ZenMerchantAction { action: ZenActions, data: ZenMerchantActionData, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum ZenActions { Redirect, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ZenMerchantActionData { redirect_url: url::Url, @@ -913,6 +917,57 @@ impl } } +fn get_zen_response( + response: ApiResponse, + status_code: u16, +) -> CustomResult< + ( + enums::AttemptStatus, + Option, + types::PaymentsResponseData, + ), + errors::ConnectorError, +> { + let redirection_data_action = response.merchant_action.map(|merchant_action| { + ( + services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), + merchant_action.action, + ) + }); + let (redirection_data, action) = match redirection_data_action { + Some((redirect_form, action)) => (Some(redirect_form), Some(action)), + None => (None, None), + }; + let status = enums::AttemptStatus::foreign_try_from((response.status, action))?; + let error = if utils::is_payment_failure(status) { + Some(types::ErrorResponse { + code: response + .reject_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .reject_reason + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.reject_reason, + status_code, + attempt_status: Some(status), + connector_transaction_id: Some(response.id.clone()), + }) + } else { + None + }; + let payment_response_data = types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(response.id.clone()), + redirection_data, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + }; + Ok((status, error, payment_response_data)) +} + impl TryFrom> for types::RouterData { @@ -920,28 +975,12 @@ impl TryFrom, ) -> Result { - let redirection_data_action = value.response.merchant_action.map(|merchant_action| { - ( - services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), - merchant_action.action, - ) - }); - let (redirection_data, action) = match redirection_data_action { - Some((redirect_form, action)) => (Some(redirect_form), Some(action)), - None => (None, None), - }; + let (status, error, payment_response_data) = + get_zen_response(value.response.clone(), value.http_code)?; Ok(Self { - status: enums::AttemptStatus::foreign_try_from((value.response.status, action))?, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(value.response.id), - redirection_data, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - }), + status, + response: error.map_or_else(|| Ok(payment_response_data), Err), ..value.data }) } @@ -1016,9 +1055,12 @@ impl From for enums::RefundStatus { } #[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { id: String, status: RefundStatus, + reject_code: Option, + reject_reason: Option, } impl TryFrom> @@ -1028,17 +1070,44 @@ impl TryFrom> fn try_from( item: types::RefundsResponseRouterData, ) -> Result { - let refund_status = enums::RefundStatus::from(item.response.status); + let (error, refund_response_data) = get_zen_refund_response(item.response, item.http_code)?; Ok(Self { - response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id, - refund_status, - }), + response: error.map_or_else(|| Ok(refund_response_data), Err), ..item.data }) } } +fn get_zen_refund_response( + response: RefundResponse, + status_code: u16, +) -> CustomResult<(Option, types::RefundsResponseData), errors::ConnectorError> +{ + let refund_status = enums::RefundStatus::from(response.status); + let error = if utils::is_refund_failure(refund_status) { + Some(types::ErrorResponse { + code: response + .reject_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .reject_reason + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.reject_reason, + status_code, + attempt_status: None, + connector_transaction_id: Some(response.id.clone()), + }) + } else { + None + }; + let refund_response_data = types::RefundsResponseData { + connector_refund_id: response.id, + refund_status, + }; + Ok((error, refund_response_data)) +} + impl TryFrom> for types::RefundsRouterData { From b4df40db25f6ea743c7a25db47e8f1d8e0d544e3 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:44:59 +0530 Subject: [PATCH 18/48] fix(metrics): Add TASKS_ADDED_COUNT and TASKS_RESET_COUNT metrics in router scheduler flow (#3189) --- crates/router/src/core/api_keys.rs | 5 +++++ .../router/src/core/payment_methods/cards.rs | 8 ++++++++ .../router/src/core/payment_methods/vault.rs | 13 +++++++++++- crates/router/src/core/payments/helpers.rs | 20 +++++++++++++++++-- crates/router/src/core/refunds.rs | 16 ++++++++++++++- crates/router/src/routes/metrics.rs | 3 +++ crates/router/src/workflows/api_key_expiry.rs | 6 ++++++ crates/scheduler/src/metrics.rs | 2 -- 8 files changed, 67 insertions(+), 6 deletions(-) diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs index c1ddc43cd65d..78d4e801e8f2 100644 --- a/crates/router/src/core/api_keys.rs +++ b/crates/router/src/core/api_keys.rs @@ -270,6 +270,11 @@ pub async fn add_api_key_expiry_task( api_key_expiry_tracker.key_id ) })?; + metrics::TASKS_ADDED_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes("flow", "ApiKeyExpiry")], + ); Ok(()) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 51f543536350..0dbf0680d14b 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2963,6 +2963,14 @@ impl TempLockerCardSupport { ) .await?; metrics::TOKENIZED_DATA_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::TASKS_ADDED_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "flow", + "DeleteTokenizeData", + )], + ); Ok(card) } } diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 063b69687577..5b783f1c5d4e 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1027,7 +1027,18 @@ pub async fn retry_delete_tokenize( let schedule_time = get_delete_tokenize_schedule_time(db, pm, pt.retry_count).await; match schedule_time { - Some(s_time) => pt.retry(db.as_scheduler(), s_time).await, + Some(s_time) => { + let retry_schedule = pt.retry(db.as_scheduler(), s_time).await; + metrics::TASKS_RESET_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "flow", + "DeleteTokenizeData", + )], + ); + retry_schedule + } None => { pt.finish_with_status(db.as_scheduler(), "RETRIES_EXCEEDED".to_string()) .await diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 92dc1bf5f4b3..46e1e15fe717 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -986,14 +986,30 @@ where match schedule_time { Some(stime) => { if !requeue { - // scheduler_metrics::TASKS_ADDED_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics + // Here, increment the count of added tasks every time a payment has been confirmed or PSync has been called + metrics::TASKS_ADDED_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "flow", + format!("{:#?}", operation), + )], + ); super::add_process_sync_task(&*state.store, payment_attempt, stime) .await .into_report() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while adding task to process tracker") } else { - // scheduler_metrics::TASKS_RESET_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics + // When the requeue is true, we reset the tasks count as we reset the task every time it is requeued + metrics::TASKS_RESET_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "flow", + format!("{:#?}", operation), + )], + ); super::reset_process_sync_task(&*state.store, payment_attempt, stime) .await .into_report() diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index e60c341dedcf..4b1c33296e65 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -1088,6 +1088,12 @@ pub async fn add_refund_sync_task( refund.refund_id ) })?; + metrics::TASKS_ADDED_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes("flow", "Refund")], + ); + Ok(response) } @@ -1170,7 +1176,15 @@ pub async fn retry_refund_sync_task( get_refund_sync_process_schedule_time(db, &connector, &merchant_id, pt.retry_count).await?; match schedule_time { - Some(s_time) => pt.retry(db.as_scheduler(), s_time).await, + Some(s_time) => { + let retry_schedule = pt.retry(db.as_scheduler(), s_time).await; + metrics::TASKS_RESET_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes("flow", "Refund")], + ); + retry_schedule + } None => { pt.finish_with_status(db.as_scheduler(), "RETRIES_EXCEEDED".to_string()) .await diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index b3629ab7d52b..6c3293dba9d0 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -113,5 +113,8 @@ counter_metric!(AUTO_RETRY_GSM_MATCH_COUNT, GLOBAL_METER); counter_metric!(AUTO_RETRY_EXHAUSTED_COUNT, GLOBAL_METER); counter_metric!(AUTO_RETRY_PAYMENT_COUNT, GLOBAL_METER); +counter_metric!(TASKS_ADDED_COUNT, GLOBAL_METER); // Tasks added to process tracker +counter_metric!(TASKS_RESET_COUNT, GLOBAL_METER); // Tasks reset in process tracker for requeue flow + pub mod request; pub mod utils; diff --git a/crates/router/src/workflows/api_key_expiry.rs b/crates/router/src/workflows/api_key_expiry.rs index 62d8a54c4024..eb3c1d9c1ce9 100644 --- a/crates/router/src/workflows/api_key_expiry.rs +++ b/crates/router/src/workflows/api_key_expiry.rs @@ -115,6 +115,12 @@ Team Hyperswitch"), let task_ids = vec![task_id]; db.process_tracker_update_process_status_by_ids(task_ids, updated_process_tracker_data) .await?; + // Remaining tasks are re-scheduled, so will be resetting the added count + metrics::TASKS_RESET_COUNT.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes("flow", "ApiKeyExpiry")], + ); } Ok(()) diff --git a/crates/scheduler/src/metrics.rs b/crates/scheduler/src/metrics.rs index 134f5599b31d..ca4fb9ec2424 100644 --- a/crates/scheduler/src/metrics.rs +++ b/crates/scheduler/src/metrics.rs @@ -6,8 +6,6 @@ global_meter!(PT_METER, "PROCESS_TRACKER"); histogram_metric!(CONSUMER_STATS, PT_METER, "CONSUMER_OPS"); counter_metric!(PAYMENT_COUNT, PT_METER); // No. of payments created -counter_metric!(TASKS_ADDED_COUNT, PT_METER); // Tasks added to process tracker -counter_metric!(TASKS_RESET_COUNT, PT_METER); // Tasks reset in process tracker for requeue flow counter_metric!(TASKS_PICKED_COUNT, PT_METER); // Tasks picked by counter_metric!(BATCHES_CREATED, PT_METER); // Batches added to stream counter_metric!(BATCHES_CONSUMED, PT_METER); // Batches consumed by consumer From 059e86607dc271c25bb3d23f5adfc7d5f21f62fb Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:54:27 +0530 Subject: [PATCH 19/48] fix(connector): [Cashtocode] update amount from i64 to f64 in webhook payload (#3382) --- crates/router/src/connector/cashtocode/transformers.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/router/src/connector/cashtocode/transformers.rs b/crates/router/src/connector/cashtocode/transformers.rs index 9aa6286a963f..8a92956756a8 100644 --- a/crates/router/src/connector/cashtocode/transformers.rs +++ b/crates/router/src/connector/cashtocode/transformers.rs @@ -193,7 +193,7 @@ pub struct CashtocodePaymentsResponseData { #[serde(rename_all = "camelCase")] pub struct CashtocodePaymentsSyncResponse { pub transaction_id: String, - pub amount: i64, + pub amount: f64, } fn get_redirect_form_data( @@ -314,7 +314,6 @@ impl connector_response_reference_id: None, incremental_authorization_allowed: None, }), - amount_captured: Some(item.response.amount), ..item.data }) } @@ -330,7 +329,7 @@ pub struct CashtocodeErrorResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CashtocodeIncomingWebhook { - pub amount: i64, + pub amount: f64, pub currency: String, pub foreign_transaction_id: String, #[serde(rename = "type")] From bd5356e7e7cf61f9d07fe9b67c9c5bb38fddf9c7 Mon Sep 17 00:00:00 2001 From: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:10:21 +0530 Subject: [PATCH 20/48] refactor(core): add locker config to enable or disable locker (#3352) 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 | 1 + crates/api_models/src/mandates.rs | 11 ++ crates/api_models/src/payment_methods.rs | 41 +++++++ crates/router/src/configs/defaults.rs | 2 + crates/router/src/configs/settings.rs | 1 + crates/router/src/core/locker_migration.rs | 4 + crates/router/src/core/mandate.rs | 18 ++-- .../router/src/core/payment_methods/cards.rs | 82 +++++++++++--- .../src/core/payment_methods/transformers.rs | 40 +++++-- .../src/core/payments/flows/authorize_flow.rs | 4 +- .../core/payments/flows/setup_mandate_flow.rs | 1 + crates/router/src/core/payments/helpers.rs | 52 +++++---- .../router/src/core/payments/tokenization.rs | 102 ++++++++++++++++-- crates/router/src/core/payouts/helpers.rs | 80 +++++++++++--- crates/router/src/core/webhooks.rs | 10 +- crates/router/src/routes/customers.rs | 7 +- crates/router/src/routes/mandates.rs | 6 +- crates/router/src/routes/payment_methods.rs | 2 +- crates/router/src/types/api/mandates.rs | 58 +++++++--- loadtest/config/development.toml | 1 + openapi/openapi_spec.json | 77 +++++++++++++ 23 files changed, 505 insertions(+), 98 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index d4e119641922..cf25ef195a24 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -131,6 +131,7 @@ host_rs = "" # Rust Locker host mock_locker = true # Emulate a locker locally using Postgres basilisk_host = "" # Basilisk host locker_signing_key_id = "1" # Key_id to sign basilisk hs locker +locker_enabled = true # Boolean to enable or disable saving cards in locker [delayed_session_response] connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which has delayed session response diff --git a/config/development.toml b/config/development.toml index 91269005a0f0..b23f68680e64 100644 --- a/config/development.toml +++ b/config/development.toml @@ -69,6 +69,8 @@ host = "" host_rs = "" mock_locker = true basilisk_host = "" +locker_enabled = true + [forex_api] call_delay = 21600 diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 450fe106a31f..8af1528e1771 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -56,6 +56,7 @@ host = "" host_rs = "" mock_locker = true basilisk_host = "" +locker_enabled = true [jwekey] vault_encryption_key = "" diff --git a/crates/api_models/src/mandates.rs b/crates/api_models/src/mandates.rs index 5c0810dc21be..b29f4e0d0c34 100644 --- a/crates/api_models/src/mandates.rs +++ b/crates/api_models/src/mandates.rs @@ -36,6 +36,8 @@ pub struct MandateResponse { pub payment_method_id: String, /// The payment method pub payment_method: String, + /// The payment method type + pub payment_method_type: Option, /// The card details for mandate pub card: Option, /// Details about the customer’s acceptance @@ -66,6 +68,15 @@ pub struct MandateCardDetails { #[schema(value_type = Option)] /// A unique identifier alias to identify a particular card pub card_fingerprint: Option>, + /// The first 6 digits of card + pub card_isin: Option, + /// The bank that issued the card + pub card_issuer: Option, + /// The network that facilitates payment card transactions + #[schema(value_type = Option)] + pub card_network: Option, + /// The type of the payment card + pub card_type: Option, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 3467777da745..984e6dbffff9 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -109,6 +109,19 @@ pub struct CardDetail { /// Card Holder's Nick Name #[schema(value_type = Option,example = "John Doe")] pub nick_name: Option>, + + /// Card Issuing Country + pub card_issuing_country: Option, + + /// Card's Network + #[schema(value_type = Option)] + pub card_network: Option, + + /// Issuer Bank for Card + pub card_issuer: Option, + + /// Card Type + pub card_type: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -177,6 +190,12 @@ pub struct CardDetailsPaymentMethod { pub expiry_year: Option>, pub nick_name: Option>, pub card_holder_name: Option>, + pub card_isin: Option, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + #[serde(default = "saved_in_locker_default")] + pub saved_to_locker: bool, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -227,6 +246,18 @@ pub struct CardDetailFromLocker { #[schema(value_type=Option)] pub nick_name: Option>, + + #[schema(value_type = Option)] + pub card_network: Option, + + pub card_isin: Option, + pub card_issuer: Option, + pub card_type: Option, + pub saved_to_locker: bool, +} + +fn saved_in_locker_default() -> bool { + true } impl From for CardDetailFromLocker { @@ -242,6 +273,11 @@ impl From for CardDetailFromLocker { card_holder_name: item.card_holder_name, card_fingerprint: None, nick_name: item.nick_name, + card_isin: item.card_isin, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type, + saved_to_locker: item.saved_to_locker, } } } @@ -255,6 +291,11 @@ impl From for CardDetailsPaymentMethod { expiry_year: item.expiry_year, nick_name: item.nick_name, card_holder_name: item.card_holder_name, + card_isin: item.card_isin, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type, + saved_to_locker: item.saved_to_locker, } } } diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 42839bf35131..e4a470d0da35 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -54,6 +54,8 @@ impl Default for super::settings::Locker { mock_locker: true, basilisk_host: "localhost".into(), locker_signing_key_id: "1".into(), + //true or false + locker_enabled: true, } } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 3d93c2f188b7..bcf26d63ae8d 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -490,6 +490,7 @@ pub struct Locker { pub mock_locker: bool, pub basilisk_host: String, pub locker_signing_key_id: String, + pub locker_enabled: bool, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index e3e308a8a01c..4bd2555792a2 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -101,6 +101,10 @@ pub async fn call_to_locker( card_exp_year: card.card_exp_year, card_holder_name: card.name_on_card, nick_name: card.nick_name.map(masking::Secret::new), + card_issuing_country: None, + card_network: None, + card_issuer: None, + card_type: None, }; let pm_create = api::PaymentMethodCreate { diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index aabd846660ca..b6837d14f829 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -33,6 +33,7 @@ use crate::{ pub async fn get_mandate( state: AppState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, req: mandates::MandateId, ) -> RouterResponse { let mandate = state @@ -42,7 +43,7 @@ pub async fn get_mandate( .await .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; Ok(services::ApplicationResponse::Json( - mandates::MandateResponse::from_db_mandate(&state, mandate).await?, + mandates::MandateResponse::from_db_mandate(&state, key_store, mandate).await?, )) } @@ -202,6 +203,7 @@ pub async fn update_connector_mandate_id( pub async fn get_customer_mandates( state: AppState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, req: customers::CustomerId, ) -> RouterResponse> { let mandates = state @@ -221,7 +223,10 @@ pub async fn get_customer_mandates( } else { let mut response_vec = Vec::with_capacity(mandates.len()); for mandate in mandates { - response_vec.push(mandates::MandateResponse::from_db_mandate(&state, mandate).await?); + response_vec.push( + mandates::MandateResponse::from_db_mandate(&state, key_store.clone(), mandate) + .await?, + ); } Ok(services::ApplicationResponse::Json(response_vec)) } @@ -383,6 +388,7 @@ where pub async fn retrieve_mandates_list( state: AppState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, constraints: api_models::mandates::MandateListConstraints, ) -> RouterResponse> { let mandates = state @@ -392,11 +398,9 @@ pub async fn retrieve_mandates_list( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to retrieve mandates")?; - let mandates_list = future::try_join_all( - mandates - .into_iter() - .map(|mandate| mandates::MandateResponse::from_db_mandate(&state, mandate)), - ) + let mandates_list = future::try_join_all(mandates.into_iter().map(|mandate| { + mandates::MandateResponse::from_db_mandate(&state, key_store.clone(), mandate) + })) .await?; Ok(services::ApplicationResponse::Json(mandates_list)) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 0dbf0680d14b..712ae9e4035e 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -558,6 +558,7 @@ pub async fn add_card_hs( req, &merchant_account.merchant_id, ); + Ok(( payment_method_resp, store_card_payload.duplicate.unwrap_or(false), @@ -2508,11 +2509,19 @@ pub async fn list_customer_payment_method( let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token"); let (card, pmd, hyperswitch_token_data) = match pm.payment_method { - enums::PaymentMethod::Card => ( - Some(get_card_details(&pm, key, state).await?), - None, - PaymentTokenData::permanent_card(pm.payment_method_id.clone()), - ), + enums::PaymentMethod::Card => { + let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?; + + if card_details.is_some() { + ( + card_details, + None, + PaymentTokenData::permanent_card(pm.payment_method_id.clone()), + ) + } else { + continue; + } + } #[cfg(feature = "payouts")] enums::PaymentMethod::BankTransfer => { @@ -2571,6 +2580,7 @@ pub async fn list_customer_payment_method( }; //Need validation for enabled payment method ,querying MCA + let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.to_owned(), customer_id: pm.customer_id, @@ -2700,7 +2710,38 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } -async fn get_card_details( +pub async fn get_card_details_with_locker_fallback( + pm: &payment_method::PaymentMethod, + key: &[u8], + state: &routes::AppState, +) -> errors::RouterResult> { + let card_decrypted = + decrypt::(pm.payment_method_data.clone(), key) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("unable to decrypt card details") + .ok() + .flatten() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), + _ => None, + }); + + Ok(if let Some(mut crd) = card_decrypted { + if crd.saved_to_locker { + crd.scheme = pm.scheme.clone(); + Some(crd) + } else { + None + } + } else { + Some(get_card_details_from_locker(state, pm).await?) + }) +} + +pub async fn get_card_details_without_locker_fallback( pm: &payment_method::PaymentMethod, key: &[u8], state: &routes::AppState, @@ -2979,25 +3020,32 @@ impl TempLockerCardSupport { pub async fn retrieve_payment_method( state: routes::AppState, pm: api::PaymentMethodId, + key_store: domain::MerchantKeyStore, ) -> errors::RouterResponse { let db = state.store.as_ref(); let pm = db .find_payment_method(&pm.payment_method_id) .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; + + let key = key_store.key.peek(); let card = if pm.payment_method == enums::PaymentMethod::Card { - let card = get_card_from_locker( - &state, - &pm.customer_id, - &pm.merchant_id, - &pm.payment_method_id, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting card from card vault")?; - let card_detail = payment_methods::get_card_detail(&pm, card) + let card_detail = if state.conf.locker.locker_enabled { + let card = get_card_from_locker( + &state, + &pm.customer_id, + &pm.merchant_id, + &pm.payment_method_id, + ) + .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while getting card details from locker")?; + .attach_printable("Error getting card from card vault")?; + payment_methods::get_card_detail(&pm, card) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting card details from locker")? + } else { + get_card_details_without_locker_fallback(&pm, key, &state).await? + }; Some(card_detail) } else { None diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index da4f03b49c1e..304091e42ac7 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -352,18 +352,26 @@ pub fn mk_add_card_response_hs( req: api::PaymentMethodCreate, merchant_id: &str, ) -> api::PaymentMethodResponse { - let mut card_number = card.card_number.peek().to_owned(); + let card_number = card.card_number.clone(); + let last4_digits = card_number.clone().get_last4(); + let card_isin = card_number.get_card_isin(); + let card = api::CardDetailFromLocker { scheme: None, - last4_digits: Some(card_number.split_off(card_number.len() - 4)), - issuer_country: None, // [#256] bin mapping - card_number: Some(card.card_number), - expiry_month: Some(card.card_exp_month), - expiry_year: Some(card.card_exp_year), - card_token: None, // [#256] - card_fingerprint: None, // fingerprint not send by basilisk-hs need to have this feature in case we need it in future - card_holder_name: card.card_holder_name, - nick_name: card.nick_name, + last4_digits: Some(last4_digits), + issuer_country: None, + card_number: Some(card.card_number.clone()), + expiry_month: Some(card.card_exp_month.clone()), + expiry_year: Some(card.card_exp_year.clone()), + card_token: None, + card_fingerprint: None, + card_holder_name: card.card_holder_name.clone(), + nick_name: card.nick_name.clone(), + card_isin: Some(card_isin), + card_issuer: card.card_issuer, + card_network: card.card_network, + card_type: card.card_type, + saved_to_locker: true, }; api::PaymentMethodResponse { merchant_id: merchant_id.to_owned(), @@ -399,6 +407,11 @@ pub fn mk_add_card_response( card_fingerprint: Some(response.card_fingerprint), card_holder_name: card.card_holder_name, nick_name: card.nick_name, + card_isin: None, + card_issuer: None, + card_network: None, + card_type: None, + saved_to_locker: true, }; api::PaymentMethodResponse { merchant_id: merchant_id.to_owned(), @@ -597,6 +610,8 @@ pub fn get_card_detail( ) -> CustomResult { let card_number = response.card_number; let mut last4_digits = card_number.peek().to_owned(); + //fetch form card bin + let card_detail = api::CardDetailFromLocker { scheme: pm.scheme.to_owned(), issuer_country: pm.issuer_country.clone(), @@ -608,6 +623,11 @@ pub fn get_card_detail( card_fingerprint: None, card_holder_name: response.name_on_card, nick_name: response.nick_name.map(masking::Secret::new), + card_isin: None, + card_issuer: None, + card_network: None, + card_type: None, + saved_to_locker: true, }; Ok(card_detail) } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 07af15a336d9..15c79f4b9d95 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -92,7 +92,9 @@ impl Feature for types::PaymentsAu metrics::PAYMENT_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics - if resp.request.setup_mandate_details.clone().is_some() { + let is_mandate = resp.request.setup_mandate_details.is_some(); + + if is_mandate { let payment_method_id = Box::pin(tokenization::save_payment_method( state, connector, diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index 0c03c8ce123b..d6343ed871b0 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -208,6 +208,7 @@ impl types::SetupMandateRouterData { .to_setup_mandate_failed_response()?; let payment_method_type = self.request.payment_method_type; + let pm_id = Box::pin(tokenization::save_payment_method( state, connector, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 46e1e15fe717..9d3da6c78e4a 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -509,16 +509,23 @@ pub async fn get_token_for_recurring_mandate( }; if let diesel_models::enums::PaymentMethod::Card = payment_method.payment_method { - let _ = - cards::get_lookup_key_from_locker(state, &token, &payment_method, merchant_key_store) - .await?; + if state.conf.locker.locker_enabled { + let _ = cards::get_lookup_key_from_locker( + state, + &token, + &payment_method, + merchant_key_store, + ) + .await?; + } + if let Some(payment_method_from_request) = req.payment_method { let pm: storage_enums::PaymentMethod = payment_method_from_request; if pm != payment_method.payment_method { Err(report!(errors::ApiErrorResponse::PreconditionFailed { message: "payment method in request does not match previously provided payment \ - method information" + method information" .into() }))? } @@ -971,7 +978,6 @@ pub fn payment_intent_status_fsm( None => storage_enums::IntentStatus::RequiresPaymentMethod, } } - pub async fn add_domain_task_to_pt( operation: &Op, state: &AppState, @@ -1050,6 +1056,10 @@ pub(crate) async fn get_payment_method_create_request( card_exp_year: card.card_exp_year.clone(), card_holder_name: card.card_holder_name.clone(), nick_name: card.nick_name.clone(), + card_issuing_country: card.card_issuing_country.clone(), + card_network: card.card_network.clone(), + card_issuer: card.card_issuer.clone(), + card_type: card.card_type.clone(), }; let customer_id = customer.customer_id.clone(); let payment_method_request = api::PaymentMethodCreate { @@ -3359,21 +3369,23 @@ pub async fn get_additional_payment_data( }, )) }); - card_info.unwrap_or(api_models::payments::AdditionalPaymentData::Card(Box::new( - api_models::payments::AdditionalCardInfo { - card_issuer: None, - card_network: None, - bank_code: None, - card_type: None, - card_issuing_country: None, - last4, - card_isin, - card_extended_bin, - card_exp_month: Some(card_data.card_exp_month.clone()), - card_exp_year: Some(card_data.card_exp_year.clone()), - card_holder_name: card_data.card_holder_name.clone(), - }, - ))) + card_info.unwrap_or_else(|| { + api_models::payments::AdditionalPaymentData::Card(Box::new( + api_models::payments::AdditionalCardInfo { + card_issuer: None, + card_network: None, + bank_code: None, + card_type: None, + card_issuing_country: None, + last4, + card_isin, + card_extended_bin, + card_exp_month: Some(card_data.card_exp_month.clone()), + card_exp_year: Some(card_data.card_exp_year.clone()), + card_holder_name: card_data.card_holder_name.clone(), + }, + )) + }) } } api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index f884cb79e7e1..15d88c94660c 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -14,7 +14,7 @@ use crate::{ services, types::{ self, - api::{self, CardDetailsPaymentMethod, PaymentMethodCreateExt}, + api::{self, CardDetailFromLocker, CardDetailsPaymentMethod, PaymentMethodCreateExt}, domain, storage::enums as storage_enums, }, @@ -74,12 +74,21 @@ where .await?; let merchant_id = &merchant_account.merchant_id; - let locker_response = save_in_locker( - state, - merchant_account, - payment_method_create_request.to_owned(), - ) - .await?; + let locker_response = if !state.conf.locker.locker_enabled { + skip_saving_card_in_locker( + merchant_account, + payment_method_create_request.to_owned(), + ) + .await? + } else { + save_in_locker( + state, + merchant_account, + payment_method_create_request.to_owned(), + ) + .await? + }; + let is_duplicate = locker_response.1; let pm_card_details = locker_response.0.card.as_ref().map(|card| { @@ -168,6 +177,85 @@ where } } +async fn skip_saving_card_in_locker( + merchant_account: &domain::MerchantAccount, + payment_method_request: api::PaymentMethodCreate, +) -> RouterResult<(api_models::payment_methods::PaymentMethodResponse, bool)> { + let merchant_id = &merchant_account.merchant_id; + let customer_id = payment_method_request + .clone() + .customer_id + .clone() + .get_required_value("customer_id")?; + let payment_method_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm"); + + let last4_digits = payment_method_request + .card + .clone() + .map(|c| c.card_number.get_last4()); + + let card_isin = payment_method_request + .card + .clone() + .map(|c: api_models::payment_methods::CardDetail| c.card_number.get_card_isin()); + + match payment_method_request.card.clone() { + Some(card) => { + let card_detail = CardDetailFromLocker { + scheme: None, + issuer_country: card.card_issuing_country.clone(), + last4_digits: last4_digits.clone(), + card_number: None, + expiry_month: Some(card.card_exp_month.clone()), + expiry_year: Some(card.card_exp_year), + card_token: None, + card_holder_name: card.card_holder_name.clone(), + card_fingerprint: None, + nick_name: None, + card_isin: card_isin.clone(), + card_issuer: card.card_issuer.clone(), + card_network: card.card_network.clone(), + card_type: card.card_type.clone(), + saved_to_locker: false, + }; + let pm_resp = api::PaymentMethodResponse { + merchant_id: merchant_id.to_string(), + customer_id: Some(customer_id), + payment_method_id, + payment_method: payment_method_request.payment_method, + payment_method_type: payment_method_request.payment_method_type, + card: Some(card_detail), + recurring_enabled: false, + installment_payment_enabled: false, + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), + metadata: None, + created: Some(common_utils::date_time::now()), + bank_transfer: None, + }; + + Ok((pm_resp, false)) + } + None => { + let pm_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.to_string(), + customer_id: Some(customer_id), + payment_method_id: pm_id, + payment_method: payment_method_request.payment_method, + payment_method_type: payment_method_request.payment_method_type, + card: None, + metadata: None, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, + installment_payment_enabled: false, + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), + bank_transfer: None, + }; + Ok((payment_method_response, false)) + } + } +} + pub async fn save_in_locker( state: &AppState, merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 56e3a6faf537..1ab24023bdb8 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -1,6 +1,6 @@ use common_utils::{ errors::CustomResult, - ext_traits::{StringExt, ValueExt}, + ext_traits::{AsyncExt, StringExt, ValueExt}, }; use diesel_models::encryption::Encryption; use error_stack::{IntoReport, ResultExt}; @@ -19,6 +19,7 @@ use crate::{ }, db::StorageInterface, routes::AppState, + services, types::{ api::{self, enums as api_enums}, domain::{ @@ -184,6 +185,10 @@ pub async fn save_payout_data_to_locker( card_exp_month: card.expiry_month.to_owned(), card_exp_year: card.expiry_year.to_owned(), nick_name: None, + card_issuing_country: None, + card_network: None, + card_issuer: None, + card_type: None, }; let payload = StoreLockerReq::LockerCard(StoreCardReq { merchant_id: &merchant_account.merchant_id, @@ -267,20 +272,65 @@ pub async fn save_payout_data_to_locker( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error updating payouts in saved payout method")?; - let pm_data = api::payment_methods::PaymentMethodsData::Card( - api::payment_methods::CardDetailsPaymentMethod { - last4_digits: card_details - .as_ref() - .map(|c| c.card_number.clone().get_last4()), - issuer_country: None, - expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), - expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), - nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), - card_holder_name: card_details - .as_ref() - .and_then(|c| c.card_holder_name.clone()), - }, - ); + // fetch card info from db + let card_isin = card_details + .as_ref() + .map(|c| c.card_number.clone().get_card_isin()); + + let pm_data = card_isin + .clone() + .async_and_then(|card_isin| async move { + db.get_card_info(&card_isin) + .await + .map_err(|error| services::logger::warn!(card_info_error=?error)) + .ok() + }) + .await + .flatten() + .map(|card_info| { + api::payment_methods::PaymentMethodsData::Card( + api::payment_methods::CardDetailsPaymentMethod { + last4_digits: card_details + .as_ref() + .map(|c| c.card_number.clone().get_last4()), + issuer_country: card_info.card_issuing_country, + expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), + expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), + nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), + card_holder_name: card_details + .as_ref() + .and_then(|c| c.card_holder_name.clone()), + + card_isin: card_isin.clone(), + card_issuer: card_info.card_issuer, + card_network: card_info.card_network, + card_type: card_info.card_type, + saved_to_locker: true, + }, + ) + }) + .unwrap_or_else(|| { + api::payment_methods::PaymentMethodsData::Card( + api::payment_methods::CardDetailsPaymentMethod { + last4_digits: card_details + .as_ref() + .map(|c| c.card_number.clone().get_last4()), + issuer_country: None, + expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), + expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), + nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), + card_holder_name: card_details + .as_ref() + .and_then(|c| c.card_holder_name.clone()), + + card_isin: card_isin.clone(), + card_issuer: None, + card_network: None, + card_type: None, + saved_to_locker: true, + }, + ) + }); let card_details_encrypted = cards::create_encrypted_payment_method_data(key_store, Some(pm_data)).await; diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 4354a3ee1959..f291d1cd2e80 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -421,6 +421,7 @@ pub async fn mandates_incoming_webhook_flow( state: AppState, merchant_account: domain::MerchantAccount, business_profile: diesel_models::business_profile::BusinessProfile, + key_store: domain::MerchantKeyStore, webhook_details: api::IncomingWebhookDetails, source_verified: bool, event_type: api_models::webhooks::IncomingWebhookEvent, @@ -464,8 +465,12 @@ pub async fn mandates_incoming_webhook_flow( .await .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; let mandates_response = Box::new( - api::mandates::MandateResponse::from_db_mandate(&state, updated_mandate.clone()) - .await?, + api::mandates::MandateResponse::from_db_mandate( + &state, + key_store, + updated_mandate.clone(), + ) + .await?, ); let event_type: Option = updated_mandate.mandate_status.foreign_into(); if let Some(outgoing_event_type) = event_type { @@ -1237,6 +1242,7 @@ pub async fn webhooks_core RouterResult; + async fn from_db_mandate( + state: &AppState, + key_store: domain::MerchantKeyStore, + mandate: storage::Mandate, + ) -> RouterResult; } #[async_trait::async_trait] impl MandateResponseExt for MandateResponse { - async fn from_db_mandate(state: &AppState, mandate: storage::Mandate) -> RouterResult { + async fn from_db_mandate( + state: &AppState, + key_store: domain::MerchantKeyStore, + mandate: storage::Mandate, + ) -> RouterResult { let db = &*state.store; let payment_method = db .find_payment_method(&mandate.payment_method_id) @@ -36,21 +45,35 @@ impl MandateResponseExt for MandateResponse { .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; let card = if payment_method.payment_method == storage_enums::PaymentMethod::Card { - let card = payment_methods::cards::get_card_from_locker( - state, - &payment_method.customer_id, - &payment_method.merchant_id, - &payment_method.payment_method_id, - ) - .await?; - let card_detail = payment_methods::transformers::get_card_detail(&payment_method, card) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while getting card details")?; - Some(MandateCardDetails::from(card_detail).into_inner()) + // if locker is disabled , decrypt the payment method data + let card_details = if state.conf.locker.locker_enabled { + let card = payment_methods::cards::get_card_from_locker( + state, + &payment_method.customer_id, + &payment_method.merchant_id, + &payment_method.payment_method_id, + ) + .await?; + + payment_methods::transformers::get_card_detail(&payment_method, card) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting card details")? + } else { + payment_methods::cards::get_card_details_without_locker_fallback( + &payment_method, + key_store.key.get_inner().peek(), + state, + ) + .await? + }; + + Some(MandateCardDetails::from(card_details).into_inner()) } else { None }; - + let payment_method_type = payment_method + .payment_method_type + .map(|pmt| pmt.to_string()); Ok(Self { mandate_id: mandate.mandate_id, customer_acceptance: Some(api::payments::CustomerAcceptance { @@ -68,6 +91,7 @@ impl MandateResponseExt for MandateResponse { card, status: mandate.mandate_status, payment_method: payment_method.payment_method.to_string(), + payment_method_type, payment_method_id: mandate.payment_method_id, }) } @@ -84,6 +108,10 @@ impl From for MandateCardDetails { scheme: card_details_from_locker.scheme, issuer_country: card_details_from_locker.issuer_country, card_fingerprint: card_details_from_locker.card_fingerprint, + card_isin: card_details_from_locker.card_isin, + card_issuer: card_details_from_locker.card_issuer, + card_network: card_details_from_locker.card_network, + card_type: card_details_from_locker.card_type, } .into() } diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 358a591a6678..268ebd1d3ac9 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -33,6 +33,7 @@ host = "" host_rs = "" mock_locker = true basilisk_host = "" +locker_enabled = true [forex_api] call_delay = 21600 diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index b2f5d3ea52c3..02df6324a06d 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4421,11 +4421,37 @@ "description": "Card Holder's Nick Name", "example": "John Doe", "nullable": true + }, + "card_issuing_country": { + "type": "string", + "description": "Card Issuing Country", + "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_issuer": { + "type": "string", + "description": "Issuer Bank for Card", + "nullable": true + }, + "card_type": { + "type": "string", + "description": "Card Type", + "nullable": true } } }, "CardDetailFromLocker": { "type": "object", + "required": [ + "saved_to_locker" + ], "properties": { "scheme": { "type": "string", @@ -4462,6 +4488,29 @@ "nick_name": { "type": "string", "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_isin": { + "type": "string", + "nullable": true + }, + "card_issuer": { + "type": "string", + "nullable": true + }, + "card_type": { + "type": "string", + "nullable": true + }, + "saved_to_locker": { + "type": "boolean" } } }, @@ -6884,6 +6933,29 @@ "type": "string", "description": "A unique identifier alias to identify a particular card", "nullable": true + }, + "card_isin": { + "type": "string", + "description": "The first 6 digits of card", + "nullable": true + }, + "card_issuer": { + "type": "string", + "description": "The bank that issued the card", + "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_type": { + "type": "string", + "description": "The type of the payment card", + "nullable": true } } }, @@ -6932,6 +7004,11 @@ "type": "string", "description": "The payment method" }, + "payment_method_type": { + "type": "string", + "description": "The payment method type", + "nullable": true + }, "card": { "allOf": [ { From 975986d9666cc4dc67aabc3f6576edf6804bb11a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:11:30 +0000 Subject: [PATCH 21/48] chore(version): 2024.01.18.1 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b17ee4964b41..fbc63ebaf975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.18.1 + +### Bug Fixes + +- **connector:** + - Trustpay zen error mapping ([#3255](https://github.com/juspay/hyperswitch/pull/3255)) ([`e816ccf`](https://github.com/juspay/hyperswitch/commit/e816ccfbdd7b0e24464aa93421e399d63f23b17c)) + - [Cashtocode] update amount from i64 to f64 in webhook payload ([#3382](https://github.com/juspay/hyperswitch/pull/3382)) ([`059e866`](https://github.com/juspay/hyperswitch/commit/059e86607dc271c25bb3d23f5adfc7d5f21f62fb)) +- **metrics:** Add TASKS_ADDED_COUNT and TASKS_RESET_COUNT metrics in router scheduler flow ([#3189](https://github.com/juspay/hyperswitch/pull/3189)) ([`b4df40d`](https://github.com/juspay/hyperswitch/commit/b4df40db25f6ea743c7a25db47e8f1d8e0d544e3)) +- **user:** Fetch profile_id for sample data ([#3358](https://github.com/juspay/hyperswitch/pull/3358)) ([`2f693ad`](https://github.com/juspay/hyperswitch/commit/2f693ad1fd857280ef30c6cc0297fb926f0e79e8)) + +### Refactors + +- **connector:** [Volt] Refactor Payments and Refunds Webhooks ([#3377](https://github.com/juspay/hyperswitch/pull/3377)) ([`acb3296`](https://github.com/juspay/hyperswitch/commit/acb329672297cd7337d0b0239e4c662257812e8a)) +- **core:** Add locker config to enable or disable locker ([#3352](https://github.com/juspay/hyperswitch/pull/3352)) ([`bd5356e`](https://github.com/juspay/hyperswitch/commit/bd5356e7e7cf61f9d07fe9b67c9c5bb38fddf9c7)) + +**Full Changelog:** [`2024.01.18.0...2024.01.18.1`](https://github.com/juspay/hyperswitch/compare/2024.01.18.0...2024.01.18.1) + +- - - + ## 2024.01.18.0 ### Features From 862a1b5303ff304cca41d3553f652fd1091aab9b Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:44:20 +0530 Subject: [PATCH 22/48] feat(users): Add `preferred_merchant_id` column and update user details API (#3373) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/events/user.rs | 6 ++- crates/api_models/src/user.rs | 6 +++ crates/diesel_models/src/query/user_role.rs | 14 ++++++ crates/diesel_models/src/schema.rs | 2 + crates/diesel_models/src/user.rs | 7 +++ crates/router/src/core/user.rs | 46 +++++++++++++++++++ crates/router/src/db/kafka_store.rs | 14 ++++++ crates/router/src/db/user.rs | 5 ++ crates/router/src/db/user_role.rs | 43 +++++++++++++++++ crates/router/src/routes/app.rs | 1 + crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/user.rs | 18 ++++++++ crates/router_env/src/logger/types.rs | 2 + .../down.sql | 2 + .../up.sql | 2 + 15 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 migrations/2024-01-02-111223_users_preferred_merchant_column/down.sql create mode 100644 migrations/2024-01-02-111223_users_preferred_merchant_column/up.sql diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index c0743c8b8fc0..40d082d1cade 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -13,7 +13,8 @@ use crate::user::{ AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest, InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignUpRequest, - SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest, + SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UpdateUserAccountDetailsRequest, + UserMerchantCreate, VerifyEmailRequest, }; impl ApiEventMetric for DashboardEntryResponse { @@ -54,7 +55,8 @@ common_utils::impl_misc_api_event_type!( InviteUserRequest, InviteUserResponse, VerifyEmailRequest, - SendVerifyEmailRequest + SendVerifyEmailRequest, + UpdateUserAccountDetailsRequest ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index a04c4fef6601..8de6a3c0b4fa 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -147,3 +147,9 @@ pub struct VerifyTokenResponse { pub merchant_id: String, pub user_email: pii::Email, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct UpdateUserAccountDetailsRequest { + pub name: Option>, + pub preferred_merchant_id: Option, +} diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs index d2f9564a5309..6b408038ef55 100644 --- a/crates/diesel_models/src/query/user_role.rs +++ b/crates/diesel_models/src/query/user_role.rs @@ -19,6 +19,20 @@ impl UserRole { .await } + pub async fn find_by_user_id_merchant_id( + conn: &PgPooledConn, + user_id: String, + merchant_id: String, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::user_id + .eq(user_id) + .and(dsl::merchant_id.eq(merchant_id)), + ) + .await + } + pub async fn update_by_user_id_merchant_id( conn: &PgPooledConn, user_id: String, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 131d2b182661..c9887e1770fc 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -1056,6 +1056,8 @@ diesel::table! { is_verified -> Bool, created_at -> Timestamp, last_modified_at -> Timestamp, + #[max_length = 64] + preferred_merchant_id -> Nullable, } } diff --git a/crates/diesel_models/src/user.rs b/crates/diesel_models/src/user.rs index c608f2654c6a..84fe8710060e 100644 --- a/crates/diesel_models/src/user.rs +++ b/crates/diesel_models/src/user.rs @@ -19,6 +19,7 @@ pub struct User { pub is_verified: bool, pub created_at: PrimitiveDateTime, pub last_modified_at: PrimitiveDateTime, + pub preferred_merchant_id: Option, } #[derive( @@ -33,6 +34,7 @@ pub struct UserNew { pub is_verified: bool, pub created_at: Option, pub last_modified_at: Option, + pub preferred_merchant_id: Option, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] @@ -42,6 +44,7 @@ pub struct UserUpdateInternal { password: Option>, is_verified: Option, last_modified_at: PrimitiveDateTime, + preferred_merchant_id: Option, } #[derive(Debug)] @@ -51,6 +54,7 @@ pub enum UserUpdate { name: Option, password: Option>, is_verified: Option, + preferred_merchant_id: Option, }, } @@ -63,16 +67,19 @@ impl From for UserUpdateInternal { password: None, is_verified: Some(true), last_modified_at, + preferred_merchant_id: None, }, UserUpdate::AccountUpdate { name, password, is_verified, + preferred_merchant_id, } => Self { name, password, is_verified, last_modified_at, + preferred_merchant_id, }, } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 27a4f67618e4..729cef65c20a 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -253,6 +253,7 @@ pub async fn change_password( name: None, password: Some(new_password_hash), is_verified: None, + preferred_merchant_id: None, }, ) .await @@ -330,6 +331,7 @@ pub async fn reset_password( name: None, password: Some(hash_password), is_verified: Some(true), + preferred_merchant_id: None, }, ) .await @@ -786,3 +788,47 @@ pub async fn verify_token( user_email: user.email, })) } + +pub async fn update_user_details( + state: AppState, + user_token: auth::UserFromToken, + req: user_api::UpdateUserAccountDetailsRequest, +) -> UserResponse<()> { + let user: domain::UserFromStorage = state + .store + .find_user_by_id(&user_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + let name = req.name.map(domain::UserName::new).transpose()?; + + if let Some(ref preferred_merchant_id) = req.preferred_merchant_id { + let _ = state + .store + .find_user_role_by_user_id_merchant_id(user.get_user_id(), preferred_merchant_id) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::MerchantIdNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + } + + let user_update = storage_user::UserUpdate::AccountUpdate { + name: name.map(|x| x.get_secret().expose()), + password: None, + is_verified: None, + preferred_merchant_id: req.preferred_merchant_id, + }; + + state + .store + .update_user_by_user_id(user.get_user_id(), user_update) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 19a83088a06f..8398c153156d 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1927,12 +1927,24 @@ impl UserRoleInterface for KafkaStore { ) -> CustomResult { self.diesel_store.insert_user_role(user_role).await } + async fn find_user_role_by_user_id( &self, user_id: &str, ) -> CustomResult { self.diesel_store.find_user_role_by_user_id(user_id).await } + + async fn find_user_role_by_user_id_merchant_id( + &self, + user_id: &str, + merchant_id: &str, + ) -> CustomResult { + self.diesel_store + .find_user_role_by_user_id_merchant_id(user_id, merchant_id) + .await + } + async fn update_user_role_by_user_id_merchant_id( &self, user_id: &str, @@ -1943,9 +1955,11 @@ impl UserRoleInterface for KafkaStore { .update_user_role_by_user_id_merchant_id(user_id, merchant_id, update) .await } + async fn delete_user_role(&self, user_id: &str) -> CustomResult { self.diesel_store.delete_user_role(user_id).await } + async fn list_user_roles_by_user_id( &self, user_id: &str, diff --git a/crates/router/src/db/user.rs b/crates/router/src/db/user.rs index e3dda965f9c9..ecd71f7e2c9b 100644 --- a/crates/router/src/db/user.rs +++ b/crates/router/src/db/user.rs @@ -145,6 +145,7 @@ impl UserInterface for MockDb { is_verified: user_data.is_verified, created_at: user_data.created_at.unwrap_or(time_now), last_modified_at: user_data.created_at.unwrap_or(time_now), + preferred_merchant_id: user_data.preferred_merchant_id, }; users.push(user.clone()); Ok(user) @@ -207,10 +208,14 @@ impl UserInterface for MockDb { name, password, is_verified, + preferred_merchant_id, } => storage::User { name: name.clone().map(Secret::new).unwrap_or(user.name.clone()), password: password.clone().unwrap_or(user.password.clone()), is_verified: is_verified.unwrap_or(user.is_verified), + preferred_merchant_id: preferred_merchant_id + .clone() + .or(user.preferred_merchant_id.clone()), ..user.to_owned() }, }; diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index bf84ae134ea7..d8938f9683da 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -14,16 +14,25 @@ pub trait UserRoleInterface { &self, user_role: storage::UserRoleNew, ) -> CustomResult; + async fn find_user_role_by_user_id( &self, user_id: &str, ) -> CustomResult; + + async fn find_user_role_by_user_id_merchant_id( + &self, + user_id: &str, + merchant_id: &str, + ) -> CustomResult; + async fn update_user_role_by_user_id_merchant_id( &self, user_id: &str, merchant_id: &str, update: storage::UserRoleUpdate, ) -> CustomResult; + async fn delete_user_role(&self, user_id: &str) -> CustomResult; async fn list_user_roles_by_user_id( @@ -57,6 +66,22 @@ impl UserRoleInterface for Store { .into_report() } + async fn find_user_role_by_user_id_merchant_id( + &self, + user_id: &str, + merchant_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::UserRole::find_by_user_id_merchant_id( + &conn, + user_id.to_owned(), + merchant_id.to_owned(), + ) + .await + .map_err(Into::into) + .into_report() + } + async fn update_user_role_by_user_id_merchant_id( &self, user_id: &str, @@ -148,6 +173,24 @@ impl UserRoleInterface for MockDb { ) } + async fn find_user_role_by_user_id_merchant_id( + &self, + user_id: &str, + merchant_id: &str, + ) -> CustomResult { + let user_roles = self.user_roles.lock().await; + user_roles + .iter() + .find(|user_role| user_role.user_id == user_id && user_role.merchant_id == merchant_id) + .cloned() + .ok_or( + errors::StorageError::ValueNotFound(format!( + "No user role available for user_id = {user_id} and merchant_id = {merchant_id}" + )) + .into(), + ) + } + async fn update_user_role_by_user_id_merchant_id( &self, user_id: &str, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0c489dbe63a7..0807fb0800e1 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -921,6 +921,7 @@ impl User { .service(web::resource("/role/list").route(web::get().to(list_roles))) .service(web::resource("/role/{role_id}").route(web::get().to(get_role))) .service(web::resource("/user/invite").route(web::post().to(invite_user))) + .service(web::resource("/update").route(web::post().to(update_user_account_details))) .service( web::resource("/data") .route(web::get().to(get_multiple_dashboard_metadata)) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 12cf76be4759..805fb1152647 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -178,7 +178,8 @@ impl From for ApiIdentifier { | Flow::InviteUser | Flow::UserSignUpWithMerchantId | Flow::VerifyEmail - | Flow::VerifyEmailRequest => Self::User, + | Flow::VerifyEmailRequest + | Flow::UpdateUserAccountDetails => Self::User, Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => { Self::UserRole diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 976fd5c9f564..eca32318adf6 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -403,3 +403,21 @@ pub async fn verify_recon_token(state: web::Data, http_req: HttpReques )) .await } + +pub async fn update_user_account_details( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UpdateUserAccountDetails; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + json_payload.into_inner(), + user_core::update_user_details, + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 0d6636e567da..c4e0aa3f3ea2 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -331,6 +331,8 @@ pub enum Flow { VerifyEmail, /// Send verify email VerifyEmailRequest, + /// Update user account details + UpdateUserAccountDetails, } /// diff --git a/migrations/2024-01-02-111223_users_preferred_merchant_column/down.sql b/migrations/2024-01-02-111223_users_preferred_merchant_column/down.sql new file mode 100644 index 000000000000..b9160b6f1052 --- /dev/null +++ b/migrations/2024-01-02-111223_users_preferred_merchant_column/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE users DROP COLUMN preferred_merchant_id; diff --git a/migrations/2024-01-02-111223_users_preferred_merchant_column/up.sql b/migrations/2024-01-02-111223_users_preferred_merchant_column/up.sql new file mode 100644 index 000000000000..77567ce93faf --- /dev/null +++ b/migrations/2024-01-02-111223_users_preferred_merchant_column/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE users ADD COLUMN preferred_merchant_id VARCHAR(64); From 7516a16763877c03ecc35fda19388bbd021c5cc7 Mon Sep 17 00:00:00 2001 From: Rachit Naithani <81706961+racnan@users.noreply.github.com> Date: Thu, 18 Jan 2024 20:18:44 +0530 Subject: [PATCH 23/48] feat(users): Added get role from jwt api (#3385) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/user_role.rs | 2 + crates/router/src/core/user_role.rs | 18 +++- crates/router/src/routes/app.rs | 1 + crates/router/src/routes/lock_utils.rs | 8 +- crates/router/src/routes/user_role.rs | 16 +++- .../router/src/services/authorization/info.rs | 26 +++--- .../src/services/authorization/permissions.rs | 64 +++++++------- crates/router/src/types/domain/user.rs | 29 +++---- crates/router/src/utils/user_role.rs | 85 +++++++++---------- crates/router_env/src/logger/types.rs | 2 + 10 files changed, 140 insertions(+), 111 deletions(-) diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index 72fca2b2f084..b057f8ca8bce 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -43,6 +43,7 @@ pub enum Permission { SurchargeDecisionManagerRead, UsersRead, UsersWrite, + MerchantAccountCreate, } #[derive(Debug, serde::Serialize)] @@ -60,6 +61,7 @@ pub enum PermissionModule { Files, ThreeDsDecisionManager, SurchargeDecisionManager, + AccountCreate, } #[derive(Debug, serde::Serialize)] diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 2b7752d1904b..d8ff836e1f88 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -20,7 +20,7 @@ pub async fn get_authorization_info( user_role_api::AuthorizationInfoResponse( info::get_authorization_info() .into_iter() - .filter_map(|module| module.try_into().ok()) + .map(Into::into) .collect(), ), )) @@ -63,6 +63,22 @@ pub async fn get_role( Ok(ApplicationResponse::Json(info)) } +pub async fn get_role_from_token( + _state: AppState, + user: auth::UserFromToken, +) -> UserResponse> { + Ok(ApplicationResponse::Json( + predefined_permissions::PREDEFINED_PERMISSIONS + .get(user.role_id.as_str()) + .ok_or(UserErrors::InternalServerError.into()) + .attach_printable("Invalid Role Id in JWT")? + .get_permissions() + .iter() + .map(|&per| per.into()) + .collect(), + )) +} + pub async fn update_user_role( state: AppState, user_from_token: auth::UserFromToken, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0807fb0800e1..3d63df2fe800 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -919,6 +919,7 @@ impl User { .service(web::resource("/permission_info").route(web::get().to(get_authorization_info))) .service(web::resource("/user/update_role").route(web::post().to(update_user_role))) .service(web::resource("/role/list").route(web::get().to(list_roles))) + .service(web::resource("/role").route(web::get().to(get_role_from_token))) .service(web::resource("/role/{role_id}").route(web::get().to(get_role))) .service(web::resource("/user/invite").route(web::post().to(invite_user))) .service(web::resource("/update").route(web::post().to(update_user_account_details))) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 805fb1152647..d3a2e1af9a71 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -181,9 +181,11 @@ impl From for ApiIdentifier { | Flow::VerifyEmailRequest | Flow::UpdateUserAccountDetails => Self::User, - Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => { - Self::UserRole - } + Flow::ListRoles + | Flow::GetRole + | Flow::GetRoleFromToken + | Flow::UpdateUserRole + | Flow::GetAuthorizationInfo => Self::UserRole, Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => { Self::ConnectorOnboarding diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs index c96e099ab163..fe305942d034 100644 --- a/crates/router/src/routes/user_role.rs +++ b/crates/router/src/routes/user_role.rs @@ -7,7 +7,7 @@ use crate::{ core::{api_locking, user_role as user_role_core}, services::{ api, - authentication::{self as auth}, + authentication::{self as auth, UserFromToken}, authorization::permissions::Permission, }, }; @@ -64,6 +64,20 @@ pub async fn get_role( .await } +pub async fn get_role_from_token(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::GetRoleFromToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + (), + |state, user: UserFromToken, _| user_role_core::get_role_from_token(state, user), + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + pub async fn update_user_role( state: web::Data, req: HttpRequest, diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index cef93f82739d..99e4f1b6c096 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -15,16 +15,13 @@ pub struct PermissionInfo { impl PermissionInfo { pub fn new(permissions: &[Permission]) -> Vec { - let mut permission_infos = Vec::with_capacity(permissions.len()); - for permission in permissions { - if let Some(description) = Permission::get_permission_description(permission) { - permission_infos.push(Self { - enum_name: permission.clone(), - description, - }) - } - } - permission_infos + permissions + .iter() + .map(|&per| Self { + description: Permission::get_permission_description(&per), + enum_name: per, + }) + .collect() } } @@ -43,6 +40,7 @@ pub enum PermissionModule { Files, ThreeDsDecisionManager, SurchargeDecisionManager, + AccountCreate, } impl PermissionModule { @@ -60,7 +58,8 @@ impl PermissionModule { Self::Disputes => "Everything related to disputes - like creating and viewing dispute related information are within this module", Self::Files => "Permissions for uploading, deleting and viewing files for disputes", Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", - Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant" + Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant", + Self::AccountCreate => "Create new account within your organization" } } } @@ -173,6 +172,11 @@ impl ModuleInfo { Permission::SurchargeDecisionManagerRead, ]), }, + PermissionModule::AccountCreate => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[Permission::MerchantAccountCreate]), + }, } } } diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 426b048e88b7..5c5e3ecce300 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -1,6 +1,6 @@ use strum::Display; -#[derive(PartialEq, Display, Clone, Debug)] +#[derive(PartialEq, Display, Clone, Debug, Copy)] pub enum Permission { PaymentRead, PaymentWrite, @@ -34,45 +34,43 @@ pub enum Permission { } impl Permission { - pub fn get_permission_description(&self) -> Option<&'static str> { + pub fn get_permission_description(&self) -> &'static str { match self { - Self::PaymentRead => Some("View all payments"), - Self::PaymentWrite => Some("Create payment, download payments data"), - Self::RefundRead => Some("View all refunds"), - Self::RefundWrite => Some("Create refund, download refunds data"), - Self::ApiKeyRead => Some("View API keys (masked generated for the system"), - Self::ApiKeyWrite => Some("Create and update API keys"), - Self::MerchantAccountRead => Some("View merchant account details"), + Self::PaymentRead => "View all payments", + Self::PaymentWrite => "Create payment, download payments data", + Self::RefundRead => "View all refunds", + Self::RefundWrite => "Create refund, download refunds data", + Self::ApiKeyRead => "View API keys (masked generated for the system", + Self::ApiKeyWrite => "Create and update API keys", + Self::MerchantAccountRead => "View merchant account details", Self::MerchantAccountWrite => { - Some("Update merchant account details, configure webhooks, manage api keys") + "Update merchant account details, configure webhooks, manage api keys" } - Self::MerchantConnectorAccountRead => Some("View connectors configured"), + Self::MerchantConnectorAccountRead => "View connectors configured", Self::MerchantConnectorAccountWrite => { - Some("Create, update, verify and delete connector configurations") + "Create, update, verify and delete connector configurations" } - Self::ForexRead => Some("Query Forex data"), - Self::RoutingRead => Some("View routing configuration"), - Self::RoutingWrite => Some("Create and activate routing configurations"), - Self::DisputeRead => Some("View disputes"), - Self::DisputeWrite => Some("Create and update disputes"), - Self::MandateRead => Some("View mandates"), - Self::MandateWrite => Some("Create and update mandates"), - Self::CustomerRead => Some("View customers"), - Self::CustomerWrite => Some("Create, update and delete customers"), - Self::FileRead => Some("View files"), - Self::FileWrite => Some("Create, update and delete files"), - Self::Analytics => Some("Access to analytics module"), - Self::ThreeDsDecisionManagerWrite => Some("Create and update 3DS decision rules"), + Self::ForexRead => "Query Forex data", + Self::RoutingRead => "View routing configuration", + Self::RoutingWrite => "Create and activate routing configurations", + Self::DisputeRead => "View disputes", + Self::DisputeWrite => "Create and update disputes", + Self::MandateRead => "View mandates", + Self::MandateWrite => "Create and update mandates", + Self::CustomerRead => "View customers", + Self::CustomerWrite => "Create, update and delete customers", + Self::FileRead => "View files", + Self::FileWrite => "Create, update and delete files", + Self::Analytics => "Access to analytics module", + Self::ThreeDsDecisionManagerWrite => "Create and update 3DS decision rules", Self::ThreeDsDecisionManagerRead => { - Some("View all 3DS decision rules configured for a merchant") + "View all 3DS decision rules configured for a merchant" } - Self::SurchargeDecisionManagerWrite => { - Some("Create and update the surcharge decision rules") - } - Self::SurchargeDecisionManagerRead => Some("View all the surcharge decision rules"), - Self::UsersRead => Some("View all the users for a merchant"), - Self::UsersWrite => Some("Invite users, assign and update roles"), - Self::MerchantAccountCreate => None, + Self::SurchargeDecisionManagerWrite => "Create and update the surcharge decision rules", + Self::SurchargeDecisionManagerRead => "View all the surcharge decision rules", + Self::UsersRead => "View all the users for a merchant", + Self::UsersWrite => "Invite users, assign and update roles", + Self::MerchantAccountCreate => "Create merchant account", } } } diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index d271ed5e29d1..53c88f8aea12 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -762,19 +762,13 @@ impl UserFromStorage { } } -impl TryFrom for user_role_api::ModuleInfo { - type Error = (); - fn try_from(value: info::ModuleInfo) -> Result { - let mut permissions = Vec::with_capacity(value.permissions.len()); - for permission in value.permissions { - let permission = permission.try_into()?; - permissions.push(permission); - } - Ok(Self { +impl From for user_role_api::ModuleInfo { + fn from(value: info::ModuleInfo) -> Self { + Self { module: value.module.into(), description: value.description, - permissions, - }) + permissions: value.permissions.into_iter().map(Into::into).collect(), + } } } @@ -794,18 +788,17 @@ impl From for user_role_api::PermissionModule { info::PermissionModule::Files => Self::Files, info::PermissionModule::ThreeDsDecisionManager => Self::ThreeDsDecisionManager, info::PermissionModule::SurchargeDecisionManager => Self::SurchargeDecisionManager, + info::PermissionModule::AccountCreate => Self::AccountCreate, } } } -impl TryFrom for user_role_api::PermissionInfo { - type Error = (); - fn try_from(value: info::PermissionInfo) -> Result { - let enum_name = (&value.enum_name).try_into()?; - Ok(Self { - enum_name, +impl From for user_role_api::PermissionInfo { + fn from(value: info::PermissionInfo) -> Self { + Self { + enum_name: value.enum_name.into(), description: value.description, - }) + } } } diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index c474a82981b1..65ead92ad347 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -1,7 +1,6 @@ use api_models::user_role as user_role_api; use diesel_models::enums::UserStatus; use error_stack::ResultExt; -use router_env::logger; use crate::{ consts, @@ -44,52 +43,50 @@ pub fn validate_role_id(role_id: &str) -> UserResult<()> { pub fn get_role_name_and_permission_response( role_info: &RoleInfo, ) -> Option<(Vec, &'static str)> { - role_info - .get_permissions() - .iter() - .map(TryInto::try_into) - .collect::, _>>() - .ok() - .zip(role_info.get_name()) + role_info.get_name().map(|name| { + ( + role_info + .get_permissions() + .iter() + .map(|&per| per.into()) + .collect::>(), + name, + ) + }) } -impl TryFrom<&Permission> for user_role_api::Permission { - type Error = (); - fn try_from(value: &Permission) -> Result { +impl From for user_role_api::Permission { + fn from(value: Permission) -> Self { match value { - Permission::PaymentRead => Ok(Self::PaymentRead), - Permission::PaymentWrite => Ok(Self::PaymentWrite), - Permission::RefundRead => Ok(Self::RefundRead), - Permission::RefundWrite => Ok(Self::RefundWrite), - Permission::ApiKeyRead => Ok(Self::ApiKeyRead), - Permission::ApiKeyWrite => Ok(Self::ApiKeyWrite), - Permission::MerchantAccountRead => Ok(Self::MerchantAccountRead), - Permission::MerchantAccountWrite => Ok(Self::MerchantAccountWrite), - Permission::MerchantConnectorAccountRead => Ok(Self::MerchantConnectorAccountRead), - Permission::MerchantConnectorAccountWrite => Ok(Self::MerchantConnectorAccountWrite), - Permission::ForexRead => Ok(Self::ForexRead), - Permission::RoutingRead => Ok(Self::RoutingRead), - Permission::RoutingWrite => Ok(Self::RoutingWrite), - Permission::DisputeRead => Ok(Self::DisputeRead), - Permission::DisputeWrite => Ok(Self::DisputeWrite), - Permission::MandateRead => Ok(Self::MandateRead), - Permission::MandateWrite => Ok(Self::MandateWrite), - Permission::CustomerRead => Ok(Self::CustomerRead), - Permission::CustomerWrite => Ok(Self::CustomerWrite), - Permission::FileRead => Ok(Self::FileRead), - Permission::FileWrite => Ok(Self::FileWrite), - Permission::Analytics => Ok(Self::Analytics), - Permission::ThreeDsDecisionManagerWrite => Ok(Self::ThreeDsDecisionManagerWrite), - Permission::ThreeDsDecisionManagerRead => Ok(Self::ThreeDsDecisionManagerRead), - Permission::SurchargeDecisionManagerWrite => Ok(Self::SurchargeDecisionManagerWrite), - Permission::SurchargeDecisionManagerRead => Ok(Self::SurchargeDecisionManagerRead), - Permission::UsersRead => Ok(Self::UsersRead), - Permission::UsersWrite => Ok(Self::UsersWrite), - - Permission::MerchantAccountCreate => { - logger::error!("Invalid use of internal permission"); - Err(()) - } + Permission::PaymentRead => Self::PaymentRead, + Permission::PaymentWrite => Self::PaymentWrite, + Permission::RefundRead => Self::RefundRead, + Permission::RefundWrite => Self::RefundWrite, + Permission::ApiKeyRead => Self::ApiKeyRead, + Permission::ApiKeyWrite => Self::ApiKeyWrite, + Permission::MerchantAccountRead => Self::MerchantAccountRead, + Permission::MerchantAccountWrite => Self::MerchantAccountWrite, + Permission::MerchantConnectorAccountRead => Self::MerchantConnectorAccountRead, + Permission::MerchantConnectorAccountWrite => Self::MerchantConnectorAccountWrite, + Permission::ForexRead => Self::ForexRead, + Permission::RoutingRead => Self::RoutingRead, + Permission::RoutingWrite => Self::RoutingWrite, + Permission::DisputeRead => Self::DisputeRead, + Permission::DisputeWrite => Self::DisputeWrite, + Permission::MandateRead => Self::MandateRead, + Permission::MandateWrite => Self::MandateWrite, + Permission::CustomerRead => Self::CustomerRead, + Permission::CustomerWrite => Self::CustomerWrite, + Permission::FileRead => Self::FileRead, + Permission::FileWrite => Self::FileWrite, + Permission::Analytics => Self::Analytics, + Permission::ThreeDsDecisionManagerWrite => Self::ThreeDsDecisionManagerWrite, + Permission::ThreeDsDecisionManagerRead => Self::ThreeDsDecisionManagerRead, + Permission::SurchargeDecisionManagerWrite => Self::SurchargeDecisionManagerWrite, + Permission::SurchargeDecisionManagerRead => Self::SurchargeDecisionManagerRead, + Permission::UsersRead => Self::UsersRead, + Permission::UsersWrite => Self::UsersWrite, + Permission::MerchantAccountCreate => Self::MerchantAccountCreate, } } } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index c4e0aa3f3ea2..7e3a692517f1 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -297,6 +297,8 @@ pub enum Flow { ListRoles, /// Get role GetRole, + /// Get role from token + GetRoleFromToken, /// Update user role UpdateUserRole, /// Create merchant account for user in a org From 5a791aaf4dc05e8ffdb60464a03b6fc41f860581 Mon Sep 17 00:00:00 2001 From: Kashif <46213975+kashif-m@users.noreply.github.com> Date: Thu, 18 Jan 2024 22:36:40 +0530 Subject: [PATCH 24/48] refactor(recon): update recipient email and mail body for ProFeatureRequest (#3381) --- crates/router/src/routes/recon.rs | 6 +++--- crates/router/src/services/email/types.rs | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/router/src/routes/recon.rs b/crates/router/src/routes/recon.rs index d34e30237ddc..22c886e13581 100644 --- a/crates/router/src/routes/recon.rs +++ b/crates/router/src/routes/recon.rs @@ -102,9 +102,9 @@ pub async fn send_recon_request( user_name: UserName::new(user_from_db.name) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to form username")?, - recipient_email: UserEmail::from_pii_email(user_from_db.email.clone()) + recipient_email: UserEmail::new(Secret::new("biz@hyperswitch.io".to_string())) .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to convert to UserEmail from pii::Email")?, + .attach_printable("Failed to convert recipient's email to UserEmail")?, settings: state.conf.clone(), subject: format!( "Dashboard Pro Feature Request by {}", @@ -187,7 +187,7 @@ pub async fn recon_merchant_account_update( let email_contents = email_types::ReconActivation { recipient_email: UserEmail::from_pii_email(user_email.clone()) .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to convert to UserEmail from pii::Email")?, + .attach_printable("Failed to convert recipient's email to UserEmail from pii::Email")?, user_name: UserName::new(Secret::new("HyperSwitch User".to_string())) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to form username")?, diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 0ef15eaa40d2..d5aa9926130e 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -74,19 +74,17 @@ pub mod html { merchant_id, user_name, user_email, - } => { - format!( - "Dear Hyperswitch Support Team, - - Dashboard Pro Feature Request, - Feature name : {feature_name} - Merchant ID : {merchant_id} - Merchant Name : {user_name} - Email : {user_email} - - (note: This is an auto generated email. use merchant email for any further comunications)", - ) - } + } => format!( + "Dear Hyperswitch Support Team, + +Dashboard Pro Feature Request, +Feature name : {feature_name} +Merchant ID : {merchant_id} +Merchant Name : {user_name} +Email : {user_email} + +(note: This is an auto generated email. Use merchant email for any further communications)", + ), } } } From 8e36fe7348eeb3ceaf31395b2993b3893d4202c3 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 00:20:18 +0000 Subject: [PATCH 25/48] chore(version): 2024.01.19.0 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc63ebaf975..abb03c28492e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.19.0 + +### Features + +- **users:** + - Add `preferred_merchant_id` column and update user details API ([#3373](https://github.com/juspay/hyperswitch/pull/3373)) ([`862a1b5`](https://github.com/juspay/hyperswitch/commit/862a1b5303ff304cca41d3553f652fd1091aab9b)) + - Added get role from jwt api ([#3385](https://github.com/juspay/hyperswitch/pull/3385)) ([`7516a16`](https://github.com/juspay/hyperswitch/commit/7516a16763877c03ecc35fda19388bbd021c5cc7)) + +### Refactors + +- **recon:** Update recipient email and mail body for ProFeatureRequest ([#3381](https://github.com/juspay/hyperswitch/pull/3381)) ([`5a791aa`](https://github.com/juspay/hyperswitch/commit/5a791aaf4dc05e8ffdb60464a03b6fc41f860581)) + +**Full Changelog:** [`2024.01.18.1...2024.01.19.0`](https://github.com/juspay/hyperswitch/compare/2024.01.18.1...2024.01.19.0) + +- - - + ## 2024.01.18.1 ### Bug Fixes From 7a3d8d08423ce2ec6377d2277e727ed12ce4ccd8 Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:09:57 +0530 Subject: [PATCH 26/48] ci(pr-convention-checks): add job to check for linked issues for pull requests (#3376) --- .../workflows/conventional-commit-check.yml | 86 ------------ .github/workflows/pr-convention-checks.yml | 128 ++++++++++++++++++ 2 files changed, 128 insertions(+), 86 deletions(-) delete mode 100644 .github/workflows/conventional-commit-check.yml create mode 100644 .github/workflows/pr-convention-checks.yml diff --git a/.github/workflows/conventional-commit-check.yml b/.github/workflows/conventional-commit-check.yml deleted file mode 100644 index ad01642068b5..000000000000 --- a/.github/workflows/conventional-commit-check.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Conventional Commit Message Check - -on: - # This is a dangerous event trigger as it causes the workflow to run in the - # context of the target repository. - # Avoid checking out the head of the pull request or building code from the - # pull request whenever this trigger is used. - # Since we only label pull requests, do not have a checkout step in this - # workflow, and restrict permissions on the token, this is an acceptable - # use of this trigger. - pull_request_target: - types: - - opened - - edited - - reopened - - ready_for_review - - synchronize - - merge_group: - types: - - checks_requested - -permissions: - # Reference: https://github.com/cli/cli/issues/6274 - repository-projects: read - pull-requests: write - -env: - # Allow more retries for network requests in cargo (downloading crates) and - # rustup (installing toolchains). This should help to reduce flaky CI failures - # from transient network timeouts or other issues. - CARGO_NET_RETRY: 10 - RUSTUP_MAX_RETRIES: 10 - # Use cargo's sparse index protocol - CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse - -jobs: - pr_title_check: - name: Verify PR title follows conventional commit standards - runs-on: ubuntu-latest - - steps: - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable 2 weeks ago - - - uses: baptiste0928/cargo-install@v2.2.0 - with: - crate: cocogitto - - - name: Verify PR title follows conventional commit standards - id: pr_title_check - if: ${{ github.event_name == 'pull_request_target' }} - shell: bash - env: - TITLE: ${{ github.event.pull_request.title }} - continue-on-error: true - run: cog verify "$TITLE" - - - name: Verify commit message follows conventional commit standards - id: commit_message_check - if: ${{ github.event_name == 'merge_group' }} - shell: bash - # Fail on error, we don't have context about PR information to update labels - continue-on-error: false - run: cog verify '${{ github.event.merge_group.head_commit.message }}' - - # GitHub CLI returns a successful error code even if the PR has the label already attached - - name: Attach 'S-conventions-not-followed' label if PR title check failed - if: ${{ github.event_name == 'pull_request_target' && steps.pr_title_check.outcome == 'failure' }} - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - gh --repo ${{ github.event.repository.full_name }} pr edit --add-label 'S-conventions-not-followed' ${{ github.event.pull_request.number }} - echo "::error::PR title does not follow conventional commit standards" - exit 1 - - # GitHub CLI returns a successful error code even if the PR does not have the label attached - - name: Remove 'S-conventions-not-followed' label if PR title check succeeded - if: ${{ github.event_name == 'pull_request_target' && steps.pr_title_check.outcome == 'success' }} - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: gh --repo ${{ github.event.repository.full_name }} pr edit --remove-label 'S-conventions-not-followed' ${{ github.event.pull_request.number }} diff --git a/.github/workflows/pr-convention-checks.yml b/.github/workflows/pr-convention-checks.yml new file mode 100644 index 000000000000..37732e7c548c --- /dev/null +++ b/.github/workflows/pr-convention-checks.yml @@ -0,0 +1,128 @@ +name: Pull Request Convention Checks + +on: + # This is a dangerous event trigger as it causes the workflow to run in the + # context of the target repository. + # Avoid checking out the head of the pull request or building code from the + # pull request whenever this trigger is used. + # Since we do not have a checkout step in this workflow, this is an + # acceptable use of this trigger. + pull_request_target: + types: + - opened + - edited + - reopened + - ready_for_review + - synchronize + + merge_group: + types: + - checks_requested + +env: + # Allow more retries for network requests in cargo (downloading crates) and + # rustup (installing toolchains). This should help to reduce flaky CI failures + # from transient network timeouts or other issues. + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + +jobs: + pr_title_conventional_commit_check: + name: Verify PR title follows conventional commit standards + runs-on: ubuntu-latest + + steps: + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: cocogitto + + - name: Verify PR title follows conventional commit standards + if: ${{ github.event_name == 'pull_request_target' }} + shell: bash + env: + TITLE: ${{ github.event.pull_request.title }} + run: cog verify "$TITLE" + + - name: Verify commit message follows conventional commit standards + if: ${{ github.event_name == 'merge_group' }} + shell: bash + run: cog verify '${{ github.event.merge_group.head_commit.message }}' + + pr_linked_issues_check: + name: Verify PR contains one or more linked issues + runs-on: ubuntu-latest + + steps: + - name: Skip check for merge queue + if: ${{ github.event_name == 'merge_group' }} + shell: bash + run: echo "Skipping PR linked issues check for merge queue" + + - name: Generate GitHub app token + id: generate_app_token + if: ${{ github.event_name == 'pull_request_target' }} + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + owner: ${{ github.event.repository.owner.login }} + + - name: Verify PR contains one or more linked issues + if: ${{ github.event_name == 'pull_request_target' }} + shell: bash + env: + GH_TOKEN: ${{ steps.generate_app_token.outputs.token }} + run: | + # GitHub does not provide information about linked issues for a pull request via the REST API. + # This information is available only within the GraphQL API. + + # Obtain issue number and repository name with owner (in the `owner/repo` format) for all linked issues + query='query ($owner: String!, $repository: String!, $prNumber: Int!) { + repository(owner: $owner, name: $repository) { + pullRequest(number: $prNumber) { + closingIssuesReferences(first: 10) { + nodes { + number + repository { + nameWithOwner + } + } + } + } + } + }' + + # Obtain linked issues in the `owner/repo#issue_number` format, one issue per line. + # The variable contains an empty string if the pull request has no linked issues. + linked_issues="$( + gh api graphql \ + --raw-field "query=${query}" \ + --field 'owner=${{ github.event.repository.owner.login }}' \ + --field 'repository=${{ github.event.repository.name }}' \ + --field 'prNumber=${{ github.event.pull_request.number }}' \ + --jq '.data.repository.pullRequest.closingIssuesReferences.nodes[] | "\(.repository.nameWithOwner)#\(.number)"' + )" + + if [[ -z "${linked_issues}" ]]; then + echo "::error::PR does not contain any linked issues" + exit 1 + else + echo "PR contains at least one linked issue" + fi + + while IFS= read -r issue; do + # Split `${issue}` by `#` to obtain repository with owner (in `owner/repository` format) and issue number + IFS='#' read -r repository_with_owner issue_number <<< "${issue}" + issue_state="$(gh issue view --repo "${repository_with_owner}" --json 'state' "${issue_number}" --jq '.state')" + + # Transform `${issue_state}` to lowercase for comparison + if [[ "${issue_state,,}" != 'open' ]]; then + echo "::error::At least one of the linked issues is not open" + exit 1 + fi + done <<< "${linked_issues}" From 5255ba9170c633899cd4c3bbe24a44b429546f15 Mon Sep 17 00:00:00 2001 From: Kashif <46213975+kashif-m@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:14:33 +0530 Subject: [PATCH 27/48] fix(frm): update FRM manual review flow (#3176) Co-authored-by: Kashif --- crates/router/src/core/fraud_check.rs | 10 +- crates/router/src/core/payments.rs | 36 ++- .../payments/operations/payment_approve.rs | 266 +++------------- .../payments/operations/payment_reject.rs | 21 +- .../payments/operations/payment_response.rs | 301 ++++++++++-------- crates/router/src/core/payments/retry.rs | 5 + crates/router/src/routes/payments.rs | 16 +- 7 files changed, 261 insertions(+), 394 deletions(-) diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index ad3a7638774e..0e3f67c051b8 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -431,6 +431,7 @@ pub async fn pre_payment_frm_core<'a, F>( frm_configs: FrmConfigsObject, customer: &Option, should_continue_transaction: &mut bool, + should_continue_capture: &mut bool, key_store: domain::MerchantKeyStore, ) -> RouterResult> where @@ -466,13 +467,12 @@ where .await?; let frm_fraud_check = frm_data_updated.fraud_check.clone(); payment_data.frm_message = Some(frm_fraud_check.clone()); - if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) - //DontTakeAction - { - *should_continue_transaction = false; + if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) { if matches!(frm_configs.frm_action, api_enums::FrmAction::CancelTxn) { + *should_continue_transaction = false; frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); } else if matches!(frm_configs.frm_action, api_enums::FrmAction::ManualReview) { + *should_continue_capture = false; frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview); } } @@ -582,6 +582,7 @@ pub async fn call_frm_before_connector_call<'a, F, Req, Ctx>( frm_info: &mut Option>, customer: &Option, should_continue_transaction: &mut bool, + should_continue_capture: &mut bool, key_store: domain::MerchantKeyStore, ) -> RouterResult> where @@ -615,6 +616,7 @@ where frm_configs, customer, should_continue_transaction, + should_continue_capture, key_store, ) .await?; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 49a9bcf66645..10aa00f5963c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -181,6 +181,8 @@ where #[allow(unused_variables, unused_mut)] let mut should_continue_transaction: bool = true; #[cfg(feature = "frm")] + let mut should_continue_capture: bool = true; + #[cfg(feature = "frm")] let frm_configs = if state.conf.frm.enabled { frm_core::call_frm_before_connector_call( db, @@ -191,6 +193,7 @@ where &mut frm_info, &customer, &mut should_continue_transaction, + &mut should_continue_capture, key_store.clone(), ) .await? @@ -199,12 +202,25 @@ where }; #[cfg(feature = "frm")] logger::debug!( - "should_cancel_transaction: {:?} {:?} ", + "frm_configs: {:?}\nshould_cancel_transaction: {:?}\nshould_continue_capture: {:?}", frm_configs, - should_continue_transaction + should_continue_transaction, + should_continue_capture, ); if should_continue_transaction { + #[cfg(feature = "frm")] + match ( + should_continue_capture, + payment_data.payment_attempt.capture_method, + ) { + (false, Some(storage_enums::CaptureMethod::Automatic)) + | (false, Some(storage_enums::CaptureMethod::Scheduled)) => { + payment_data.payment_attempt.capture_method = + Some(storage_enums::CaptureMethod::Manual); + } + _ => (), + }; payment_data = match connector_details { api::ConnectorCallType::PreDetermined(connector) => { let schedule_time = if should_add_task_to_process_tracker { @@ -233,6 +249,10 @@ where &validate_result, schedule_time, header_payload, + #[cfg(feature = "frm")] + frm_info.as_ref().and_then(|fi| fi.suggested_action), + #[cfg(not(feature = "frm"))] + None, ) .await?; let operation = Box::new(PaymentResponse); @@ -284,6 +304,10 @@ where &validate_result, schedule_time, header_payload, + #[cfg(feature = "frm")] + frm_info.as_ref().and_then(|fi| fi.suggested_action), + #[cfg(not(feature = "frm"))] + None, ) .await?; @@ -311,6 +335,10 @@ where &customer, &validate_result, schedule_time, + #[cfg(feature = "frm")] + frm_info.as_ref().and_then(|fi| fi.suggested_action), + #[cfg(not(feature = "frm"))] + None, ) .await?; }; @@ -996,6 +1024,7 @@ pub async fn call_connector_service( validate_result: &operations::ValidateResult<'_>, schedule_time: Option, header_payload: HeaderPayload, + frm_suggestion: Option, ) -> RouterResult> where F: Send + Clone + Sync, @@ -1172,7 +1201,7 @@ where merchant_account.storage_scheme, updated_customer, key_store, - None, + frm_suggestion, header_payload, ) .await?; @@ -2110,6 +2139,7 @@ pub fn should_call_connector( } "CompleteAuthorize" => true, "PaymentApprove" => true, + "PaymentReject" => true, "PaymentSession" => true, "PaymentIncrementalAuthorization" => matches!( payment_data.payment_intent.status, diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index cddbc89acff1..6d3697caabdf 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -2,54 +2,51 @@ use std::marker::PhantomData; use api_models::enums::FrmSuggestion; use async_trait::async_trait; -use data_models::mandates::MandateData; -use error_stack::{report, IntoReport, ResultExt}; +use error_stack::{IntoReport, ResultExt}; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; use crate::{ core::{ - errors::{self, CustomResult, RouterResult, StorageErrorExt}, + errors::{self, RouterResult, StorageErrorExt}, payment_methods::PaymentMethodRetrieve, - payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData}, + payments::{helpers, operations, PaymentAddress, PaymentData}, utils as core_utils, }, - db::StorageInterface, routes::AppState, services, types::{ - self, api::{self, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums}, }, - utils::{self, OptionExt}, + utils::OptionExt, }; #[derive(Debug, Clone, Copy, PaymentOperation)] -#[operation(operations = "all", flow = "authorize")] +#[operation(operations = "all", flow = "capture")] pub struct PaymentApprove; #[async_trait] impl - GetTracker, api::PaymentsRequest, Ctx> for PaymentApprove + GetTracker, api::PaymentsCaptureRequest, Ctx> for PaymentApprove { #[instrument(skip_all)] async fn get_trackers<'a>( &'a self, state: &'a AppState, payment_id: &api::PaymentIdType, - request: &api::PaymentsRequest, - mandate_type: Option, + _request: &api::PaymentsCaptureRequest, + _mandate_type: Option, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - ) -> RouterResult> { + ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; let storage_scheme = merchant_account.storage_scheme; - let (mut payment_intent, mut payment_attempt, currency, amount); + let (mut payment_intent, payment_attempt, currency, amount); let payment_id = payment_id .get_payment_intent_id() @@ -59,9 +56,6 @@ impl .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - payment_intent.setup_future_usage = request - .setup_future_usage - .or(payment_intent.setup_future_usage); helpers::validate_payment_status_against_not_allowed_statuses( &payment_intent.status, @@ -69,7 +63,7 @@ impl storage_enums::IntentStatus::Failed, storage_enums::IntentStatus::Succeeded, ], - "confirm", + "approve", )?; let profile_id = payment_intent @@ -87,31 +81,6 @@ impl id: profile_id.to_string(), })?; - let ( - token, - payment_method, - payment_method_type, - setup_mandate, - recurring_mandate_payment_data, - mandate_connector, - ) = helpers::get_token_pm_type_mandate_details( - state, - request, - mandate_type.clone(), - merchant_account, - key_store, - ) - .await?; - - let browser_info = request - .browser_info - .clone() - .map(|x| utils::Encode::::encode_to_value(&x)) - .transpose() - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "browser_info", - })?; - let attempt_id = payment_intent.active_attempt.get_id().clone(); payment_attempt = db .find_payment_attempt_by_payment_id_merchant_id_attempt_id( @@ -123,35 +92,12 @@ impl .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let token = token.or_else(|| payment_attempt.payment_token.clone()); - - helpers::validate_pm_or_token_given( - &request.payment_method, - &request.payment_method_data, - &request.payment_method_type, - &mandate_type, - &token, - )?; - - payment_attempt.payment_method = payment_method.or(payment_attempt.payment_method); - payment_attempt.browser_info = browser_info; - payment_attempt.payment_method_type = - payment_method_type.or(payment_attempt.payment_method_type); - payment_attempt.payment_experience = request.payment_experience; currency = payment_attempt.currency.get_required_value("currency")?; amount = payment_attempt.get_total_amount().into(); - helpers::validate_customer_id_mandatory_cases( - request.setup_future_usage.is_some(), - &payment_intent - .customer_id - .clone() - .or_else(|| request.customer_id.clone()), - )?; - let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, - request.shipping.as_ref(), + None, payment_intent.shipping_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), @@ -162,7 +108,7 @@ impl .await?; let billing_address = helpers::create_or_find_address_for_payment_by_request( db, - request.billing.as_ref(), + None, payment_intent.billing_address_id.as_deref(), merchant_id, payment_intent.customer_id.as_ref(), @@ -172,47 +118,8 @@ impl ) .await?; - let redirect_response = request - .feature_metadata - .as_ref() - .and_then(|fm| fm.redirect_response.clone()); - payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id); payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id); - payment_intent.return_url = request - .return_url - .as_ref() - .map(|a| a.to_string()) - .or(payment_intent.return_url); - - payment_intent.allowed_payment_method_types = request - .get_allowed_payment_method_types_as_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error converting allowed_payment_types to Value")? - .or(payment_intent.allowed_payment_method_types); - - payment_intent.connector_metadata = request - .get_connector_metadata_as_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error converting connector_metadata to Value")? - .or(payment_intent.connector_metadata); - - payment_intent.feature_metadata = request - .get_feature_metadata_as_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error converting feature_metadata to Value")? - .or(payment_intent.feature_metadata); - - payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); - - // The operation merges mandate data from both request and payment_attempt - let setup_mandate = setup_mandate.map(|mandate_data| MandateData { - customer_acceptance: mandate_data.customer_acceptance, - mandate_type: payment_attempt - .mandate_details - .clone() - .or(mandate_data.mandate_type), - }); let frm_response = db .find_fraud_check_by_payment_id(payment_intent.payment_id.clone(), merchant_account.merchant_id.clone()) @@ -228,49 +135,41 @@ impl payment_attempt, currency, amount, - email: request.email.clone(), + email: None, mandate_id: None, - mandate_connector, - setup_mandate, - token, + mandate_connector: None, + setup_mandate: None, + token: None, address: PaymentAddress { shipping: shipping_address.as_ref().map(|a| a.into()), billing: billing_address.as_ref().map(|a| a.into()), }, - confirm: request.confirm, - payment_method_data: request.payment_method_data.clone(), + confirm: None, + payment_method_data: None, force_sync: None, refunds: vec![], disputes: vec![], attempts: None, sessions_token: vec![], - card_cvc: request.card_cvc.clone(), + card_cvc: None, creds_identifier: None, pm_token: None, connector_customer_id: None, - recurring_mandate_payment_data, + recurring_mandate_payment_data: None, ephemeral_key: None, multiple_capture_data: None, - redirect_response, + redirect_response: None, surcharge_details: None, frm_message: frm_response.ok(), payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], - frm_metadata: request.frm_metadata.clone(), + frm_metadata: None, }; - let customer_details = Some(CustomerDetails { - customer_id: request.customer_id.clone(), - name: request.name.clone(), - email: request.email.clone(), - phone: request.phone.clone(), - phone_country_code: request.phone_country_code.clone(), - }); - let get_trackers_response = operations::GetTrackerResponse { operation: Box::new(self), - customer_details, + customer_details: None, payment_data, business_profile, }; @@ -279,91 +178,9 @@ impl } } -#[async_trait] -impl Domain - for PaymentApprove -{ - #[instrument(skip_all)] - async fn get_or_create_customer_details<'a>( - &'a self, - db: &dyn StorageInterface, - payment_data: &mut PaymentData, - request: Option, - key_store: &domain::MerchantKeyStore, - ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, - Option, - ), - errors::StorageError, - > { - helpers::create_customer_if_not_exist( - Box::new(self), - db, - payment_data, - request, - &key_store.merchant_id, - key_store, - ) - .await - } - - #[instrument(skip_all)] - async fn make_pm_data<'a>( - &'a self, - state: &'a AppState, - payment_data: &mut PaymentData, - _storage_scheme: storage_enums::MerchantStorageScheme, - merchant_key_store: &domain::MerchantKeyStore, - customer: &Option, - ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, - Option, - )> { - let (op, payment_method_data) = helpers::make_pm_data( - Box::new(self), - state, - payment_data, - merchant_key_store, - customer, - ) - .await?; - - utils::when(payment_method_data.is_none(), || { - Err(errors::ApiErrorResponse::PaymentMethodNotFound) - })?; - - Ok((op, payment_method_data)) - } - - #[instrument(skip_all)] - async fn add_task_to_process_tracker<'a>( - &'a self, - _state: &'a AppState, - _payment_attempt: &storage::PaymentAttempt, - _requeue: bool, - _schedule_time: Option, - ) -> CustomResult<(), errors::ApiErrorResponse> { - Ok(()) - } - - async fn get_connector<'a>( - &'a self, - _merchant_account: &domain::MerchantAccount, - state: &AppState, - request: &api::PaymentsRequest, - _payment_intent: &storage::PaymentIntent, - _key_store: &domain::MerchantKeyStore, - ) -> CustomResult { - // Use a new connector in the confirm call or use the same one which was passed when - // creating the payment or if none is passed then use the routing algorithm - helpers::get_connector_default(state, request.routing.clone()).await - } -} - #[async_trait] impl - UpdateTracker, api::PaymentsRequest, Ctx> for PaymentApprove + UpdateTracker, api::PaymentsCaptureRequest, Ctx> for PaymentApprove { #[instrument(skip_all)] async fn update_trackers<'b>( @@ -377,7 +194,7 @@ impl _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest, Ctx>, + BoxedOperation<'b, F, api::PaymentsCaptureRequest, Ctx>, PaymentData, )> where @@ -401,16 +218,16 @@ impl } } -impl ValidateRequest - for PaymentApprove +impl + ValidateRequest for PaymentApprove { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, - request: &api::PaymentsRequest, + request: &api::PaymentsCaptureRequest, merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest, Ctx>, + BoxedOperation<'b, F, api::PaymentsCaptureRequest, Ctx>, operations::ValidateResult<'a>, )> { let request_merchant_id = request.merchant_id.as_deref(); @@ -420,28 +237,17 @@ impl ValidateRequest - GetTracker, PaymentsRejectRequest, Ctx> for PaymentReject + GetTracker, PaymentsCancelRequest, Ctx> for PaymentReject { #[instrument(skip_all)] async fn get_trackers<'a>( &'a self, state: &'a AppState, payment_id: &api::PaymentIdType, - _request: &PaymentsRejectRequest, + _request: &PaymentsCancelRequest, _mandate_type: Option, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, - ) -> RouterResult> { + ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; let storage_scheme = merchant_account.storage_scheme; @@ -57,6 +57,7 @@ impl helpers::validate_payment_status_against_not_allowed_statuses( &payment_intent.status, &[ + enums::IntentStatus::Cancelled, enums::IntentStatus::Failed, enums::IntentStatus::Succeeded, enums::IntentStatus::Processing, @@ -176,7 +177,7 @@ impl #[async_trait] impl - UpdateTracker, PaymentsRejectRequest, Ctx> for PaymentReject + UpdateTracker, PaymentsCancelRequest, Ctx> for PaymentReject { #[instrument(skip_all)] async fn update_trackers<'b>( @@ -190,7 +191,7 @@ impl _should_decline_transaction: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( - BoxedOperation<'b, F, PaymentsRejectRequest, Ctx>, + BoxedOperation<'b, F, PaymentsCancelRequest, Ctx>, PaymentData, )> where @@ -242,16 +243,16 @@ impl } } -impl ValidateRequest +impl ValidateRequest for PaymentReject { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, - request: &PaymentsRejectRequest, + request: &PaymentsCancelRequest, merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( - BoxedOperation<'b, F, PaymentsRejectRequest, Ctx>, + BoxedOperation<'b, F, PaymentsCancelRequest, Ctx>, operations::ValidateResult<'a>, )> { Ok(( diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9ab0b4f817f5..e5552f0d156d 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -512,113 +512,132 @@ async fn payment_response_update_tracker( }; (capture_update, attempt_update) } - Ok(payments_response) => match payments_response { - types::PaymentsResponseData::PreProcessingResponse { - pre_processing_id, - connector_metadata, - connector_response_reference_id, - .. - } => { - let connector_transaction_id = match pre_processing_id.to_owned() { - types::PreprocessingResponseId::PreProcessingId(_) => None, - types::PreprocessingResponseId::ConnectorTransactionId(connector_txn_id) => { - Some(connector_txn_id) - } - }; - let preprocessing_step_id = match pre_processing_id { - types::PreprocessingResponseId::PreProcessingId(pre_processing_id) => { - Some(pre_processing_id) + Ok(payments_response) => { + let attempt_status = payment_data.payment_attempt.status.to_owned(); + let connector_status = router_data.status.to_owned(); + let updated_attempt_status = match ( + connector_status, + attempt_status, + payment_data.frm_message.to_owned(), + ) { + ( + enums::AttemptStatus::Authorized, + enums::AttemptStatus::Unresolved, + Some(frm_message), + ) => match frm_message.frm_status { + enums::FraudCheckStatus::Fraud | enums::FraudCheckStatus::ManualReview => { + attempt_status } - types::PreprocessingResponseId::ConnectorTransactionId(_) => None, - }; - let payment_attempt_update = storage::PaymentAttemptUpdate::PreprocessingUpdate { - status: router_data.get_attempt_status_for_db_update(&payment_data), - payment_method_id: Some(router_data.payment_method_id), + _ => router_data.get_attempt_status_for_db_update(&payment_data), + }, + _ => router_data.get_attempt_status_for_db_update(&payment_data), + }; + match payments_response { + types::PaymentsResponseData::PreProcessingResponse { + pre_processing_id, connector_metadata, - preprocessing_step_id, - connector_transaction_id, connector_response_reference_id, - updated_by: storage_scheme.to_string(), - }; - - (None, Some(payment_attempt_update)) - } - types::PaymentsResponseData::TransactionResponse { - resource_id, - redirection_data, - connector_metadata, - connector_response_reference_id, - incremental_authorization_allowed, - .. - } => { - payment_data - .payment_intent - .incremental_authorization_allowed = - core_utils::get_incremental_authorization_allowed_value( - incremental_authorization_allowed, - payment_data - .payment_intent - .request_incremental_authorization, - ); - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }; - - let encoded_data = payment_data.payment_attempt.encoded_data.clone(); - - let authentication_data = redirection_data - .map(|data| utils::Encode::::encode_to_value(&data)) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Could not parse the connector response")?; - - // incase of success, update error code and error message - let error_status = if router_data.status == enums::AttemptStatus::Charged { - Some(None) - } else { - None - }; + .. + } => { + let connector_transaction_id = match pre_processing_id.to_owned() { + types::PreprocessingResponseId::PreProcessingId(_) => None, + types::PreprocessingResponseId::ConnectorTransactionId( + connector_txn_id, + ) => Some(connector_txn_id), + }; + let preprocessing_step_id = match pre_processing_id { + types::PreprocessingResponseId::PreProcessingId(pre_processing_id) => { + Some(pre_processing_id) + } + types::PreprocessingResponseId::ConnectorTransactionId(_) => None, + }; + let payment_attempt_update = + storage::PaymentAttemptUpdate::PreprocessingUpdate { + status: updated_attempt_status, + payment_method_id: Some(router_data.payment_method_id), + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by: storage_scheme.to_string(), + }; - if router_data.status == enums::AttemptStatus::Charged { - metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]); + (None, Some(payment_attempt_update)) } + types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + connector_metadata, + connector_response_reference_id, + incremental_authorization_allowed, + .. + } => { + payment_data + .payment_intent + .incremental_authorization_allowed = + core_utils::get_incremental_authorization_allowed_value( + incremental_authorization_allowed, + payment_data + .payment_intent + .request_incremental_authorization, + ); + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; - utils::add_apple_pay_payment_status_metrics( - router_data.status, - router_data.apple_pay_flow.clone(), - payment_data.payment_attempt.connector.clone(), - payment_data.payment_attempt.merchant_id.clone(), - ); + let encoded_data = payment_data.payment_attempt.encoded_data.clone(); - let (capture_updates, payment_attempt_update) = match payment_data - .multiple_capture_data - { - Some(multiple_capture_data) => { - let capture_update = storage::CaptureUpdate::ResponseUpdate { - status: enums::CaptureStatus::foreign_try_from(router_data.status)?, - connector_capture_id: connector_transaction_id.clone(), - connector_response_reference_id, - }; - let capture_update_list = vec![( - multiple_capture_data.get_latest_capture().clone(), - capture_update, - )]; - (Some((multiple_capture_data, capture_update_list)), None) + let authentication_data = redirection_data + .map(|data| utils::Encode::::encode_to_value(&data)) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not parse the connector response")?; + + // incase of success, update error code and error message + let error_status = if router_data.status == enums::AttemptStatus::Charged { + Some(None) + } else { + None + }; + + if router_data.status == enums::AttemptStatus::Charged { + metrics::SUCCESSFUL_PAYMENT.add(&metrics::CONTEXT, 1, &[]); } - None => { - let status = router_data.get_attempt_status_for_db_update(&payment_data); - ( + + utils::add_apple_pay_payment_status_metrics( + router_data.status, + router_data.apple_pay_flow.clone(), + payment_data.payment_attempt.connector.clone(), + payment_data.payment_attempt.merchant_id.clone(), + ); + + let (capture_updates, payment_attempt_update) = match payment_data + .multiple_capture_data + { + Some(multiple_capture_data) => { + let capture_update = storage::CaptureUpdate::ResponseUpdate { + status: enums::CaptureStatus::foreign_try_from(router_data.status)?, + connector_capture_id: connector_transaction_id.clone(), + connector_response_reference_id, + }; + let capture_update_list = vec![( + multiple_capture_data.get_latest_capture().clone(), + capture_update, + )]; + (Some((multiple_capture_data, capture_update_list)), None) + } + None => ( None, Some(storage::PaymentAttemptUpdate::ResponseUpdate { - status, + status: updated_attempt_status, connector: None, connector_transaction_id: connector_transaction_id.clone(), authentication_type: None, amount_capturable: router_data .request - .get_amount_capturable(&payment_data, status), + .get_amount_capturable(&payment_data, updated_attempt_status), payment_method_id: Some(router_data.payment_method_id), mandate_id: payment_data .mandate_id @@ -636,56 +655,58 @@ async fn payment_response_update_tracker( authentication_data, encoded_data, }), - ) - } - }; + ), + }; - (capture_updates, payment_attempt_update) - } - types::PaymentsResponseData::TransactionUnresolvedResponse { - resource_id, - reason, - connector_response_reference_id, - } => { - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }; - ( - None, - Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { - status: router_data.get_attempt_status_for_db_update(&payment_data), - connector: None, - connector_transaction_id, - payment_method_id: Some(router_data.payment_method_id), - error_code: Some(reason.clone().map(|cd| cd.code)), - error_message: Some(reason.clone().map(|cd| cd.message)), - error_reason: Some(reason.map(|cd| cd.message)), - connector_response_reference_id, - updated_by: storage_scheme.to_string(), - }), - ) - } - types::PaymentsResponseData::SessionResponse { .. } => (None, None), - types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), - types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), - types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), - types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), - types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), - types::PaymentsResponseData::MultipleCaptureResponse { - capture_sync_response_list, - } => match payment_data.multiple_capture_data { - Some(multiple_capture_data) => { - let capture_update_list = response_to_capture_update( - &multiple_capture_data, - capture_sync_response_list, - )?; - (Some((multiple_capture_data, capture_update_list)), None) + (capture_updates, payment_attempt_update) } - None => (None, None), - }, - }, + types::PaymentsResponseData::TransactionUnresolvedResponse { + resource_id, + reason, + connector_response_reference_id, + } => { + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; + ( + None, + Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { + status: updated_attempt_status, + connector: None, + connector_transaction_id, + payment_method_id: Some(router_data.payment_method_id), + error_code: Some(reason.clone().map(|cd| cd.code)), + error_message: Some(reason.clone().map(|cd| cd.message)), + error_reason: Some(reason.map(|cd| cd.message)), + connector_response_reference_id, + updated_by: storage_scheme.to_string(), + }), + ) + } + types::PaymentsResponseData::SessionResponse { .. } => (None, None), + types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), + types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), + types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), + types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), + types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => { + (None, None) + } + types::PaymentsResponseData::MultipleCaptureResponse { + capture_sync_response_list, + } => match payment_data.multiple_capture_data { + Some(multiple_capture_data) => { + let capture_update_list = response_to_capture_update( + &multiple_capture_data, + capture_sync_response_list, + )?; + (Some((multiple_capture_data, capture_update_list)), None) + } + None => (None, None), + }, + } + } }; payment_data.multiple_capture_data = match capture_update { Some((mut multiple_capture_data, capture_updates)) => { diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 0fd45c5af3b5..8d74eb3fa961 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -40,6 +40,7 @@ pub async fn do_gsm_actions( customer: &Option, validate_result: &operations::ValidateResult<'_>, schedule_time: Option, + frm_suggestion: Option, ) -> RouterResult> where F: Clone + Send + Sync, @@ -90,6 +91,7 @@ where validate_result, schedule_time, true, + frm_suggestion, ) .await?; } @@ -133,6 +135,7 @@ where schedule_time, //this is an auto retry payment, but not step-up false, + frm_suggestion, ) .await?; @@ -275,6 +278,7 @@ pub async fn do_retry( validate_result: &operations::ValidateResult<'_>, schedule_time: Option, is_step_up: bool, + frm_suggestion: Option, ) -> RouterResult> where F: Clone + Send + Sync, @@ -310,6 +314,7 @@ where validate_result, schedule_time, api::HeaderPayload::default(), + frm_suggestion, ) .await } diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 34f41c49cddf..379cd4f2f1fc 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -968,7 +968,7 @@ pub async fn payments_approve( payload.clone(), |state, auth, req| { payments::payments_core::< - api_types::Authorize, + api_types::Capture, payment_types::PaymentsResponse, _, _, @@ -979,10 +979,8 @@ pub async fn payments_approve( auth.merchant_account, auth.key_store, payments::PaymentApprove, - payment_types::PaymentsRequest { - payment_id: Some(payment_types::PaymentIdType::PaymentIntentId( - req.payment_id, - )), + payment_types::PaymentsCaptureRequest { + payment_id: req.payment_id, ..Default::default() }, api::AuthFlow::Merchant, @@ -1030,7 +1028,7 @@ pub async fn payments_reject( payload.clone(), |state, auth, req| { payments::payments_core::< - api_types::Reject, + api_types::Void, payment_types::PaymentsResponse, _, _, @@ -1041,7 +1039,11 @@ pub async fn payments_reject( auth.merchant_account, auth.key_store, payments::PaymentReject, - req, + payment_types::PaymentsCancelRequest { + payment_id: req.payment_id, + cancellation_reason: Some("Rejected by merchant".to_string()), + ..Default::default() + }, api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, From 1c04ac751240f5c931df0f282af1e0ad745e9509 Mon Sep 17 00:00:00 2001 From: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:55:48 +0530 Subject: [PATCH 28/48] refactor: rename `s3` feature flag to `aws_s3` (#3341) --- crates/router/Cargo.toml | 4 ++-- crates/router/src/configs/settings.rs | 6 +++--- crates/router/src/configs/validations.rs | 2 +- crates/router/src/core/files.rs | 8 ++++---- crates/router/src/core/files/helpers.rs | 24 ++++++++++++------------ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 8897fdac2c22..88272033fb04 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -10,12 +10,12 @@ license.workspace = true [features] default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"] -s3 = ["dep:aws-sdk-s3", "dep:aws-config"] +aws_s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config", "olap"] frm = [] stripe = ["dep:serde_qs"] -release = ["kms", "stripe", "s3", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen", "recon"] +release = ["kms", "stripe", "aws_s3", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen", "recon"] olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap", "dep:analytics"] oltp = ["storage_impl/oltp"] kv_store = ["scheduler/kv_store"] diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index bcf26d63ae8d..cb4fdd70eb64 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -88,7 +88,7 @@ pub struct Settings { pub api_keys: ApiKeys, #[cfg(feature = "kms")] pub kms: kms::KmsConfig, - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] pub file_upload_config: FileUploadConfig, pub tokenization: TokenizationConfig, pub connector_customer: ConnectorCustomer, @@ -717,7 +717,7 @@ pub struct ApiKeys { pub expiry_reminder_days: Vec, } -#[cfg(feature = "s3")] +#[cfg(feature = "aws_s3")] #[derive(Debug, Deserialize, Clone, Default)] #[serde(default)] pub struct FileUploadConfig { @@ -849,7 +849,7 @@ impl Settings { self.kms .validate() .map_err(|error| ApplicationError::InvalidConfigurationValueError(error.into()))?; - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] self.file_upload_config.validate()?; self.lock_settings.validate()?; self.events.validate()?; diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index 569262d0d210..0b286ece8435 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -127,7 +127,7 @@ impl super::settings::DrainerSettings { } } -#[cfg(feature = "s3")] +#[cfg(feature = "aws_s3")] impl super::settings::FileUploadConfig { pub fn validate(&self) -> Result<(), ApplicationError> { use common_utils::fp_utils::when; diff --git a/crates/router/src/core/files.rs b/crates/router/src/core/files.rs index 13c4d3dfdf31..f3e564898061 100644 --- a/crates/router/src/core/files.rs +++ b/crates/router/src/core/files.rs @@ -1,8 +1,8 @@ pub mod helpers; -#[cfg(feature = "s3")] +#[cfg(feature = "aws_s3")] pub mod s3_utils; -#[cfg(not(feature = "s3"))] +#[cfg(not(feature = "aws_s3"))] pub mod fs_utils; use api_models::files; @@ -29,9 +29,9 @@ pub async fn files_create_core( ) .await?; let file_id = common_utils::generate_id(consts::ID_LENGTH, "file"); - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] let file_key = format!("{}/{}", merchant_account.merchant_id, file_id); - #[cfg(not(feature = "s3"))] + #[cfg(not(feature = "aws_s3"))] let file_key = format!("{}_{}", merchant_account.merchant_id, file_id); let file_new = diesel_models::file::FileMetadataNew { file_id: file_id.clone(), diff --git a/crates/router/src/core/files/helpers.rs b/crates/router/src/core/files/helpers.rs index 818067207f40..9205d42aeee7 100644 --- a/crates/router/src/core/files/helpers.rs +++ b/crates/router/src/core/files/helpers.rs @@ -31,33 +31,33 @@ pub async fn get_file_purpose(field: &mut Field) -> Option { } pub async fn upload_file( - #[cfg(feature = "s3")] state: &AppState, + #[cfg(feature = "aws_s3")] state: &AppState, file_key: String, file: Vec, ) -> CustomResult<(), errors::ApiErrorResponse> { - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] return files::s3_utils::upload_file_to_s3(state, file_key, file).await; - #[cfg(not(feature = "s3"))] + #[cfg(not(feature = "aws_s3"))] return files::fs_utils::save_file_to_fs(file_key, file); } pub async fn delete_file( - #[cfg(feature = "s3")] state: &AppState, + #[cfg(feature = "aws_s3")] state: &AppState, file_key: String, ) -> CustomResult<(), errors::ApiErrorResponse> { - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] return files::s3_utils::delete_file_from_s3(state, file_key).await; - #[cfg(not(feature = "s3"))] + #[cfg(not(feature = "aws_s3"))] return files::fs_utils::delete_file_from_fs(file_key); } pub async fn retrieve_file( - #[cfg(feature = "s3")] state: &AppState, + #[cfg(feature = "aws_s3")] state: &AppState, file_key: String, ) -> CustomResult, errors::ApiErrorResponse> { - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] return files::s3_utils::retrieve_file_from_s3(state, file_key).await; - #[cfg(not(feature = "s3"))] + #[cfg(not(feature = "aws_s3"))] return files::fs_utils::retrieve_file_from_fs(file_key); } @@ -134,7 +134,7 @@ pub async fn delete_file_using_file_id( match provider { diesel_models::enums::FileUploadProvider::Router => { delete_file( - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] state, provider_file_id, ) @@ -235,7 +235,7 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id( diesel_models::enums::FileUploadProvider::Router => Ok(( Some( retrieve_file( - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] state, provider_file_id.clone(), ) @@ -365,7 +365,7 @@ pub async fn upload_and_get_provider_provider_file_id_profile_id( )) } else { upload_file( - #[cfg(feature = "s3")] + #[cfg(feature = "aws_s3")] state, file_key.clone(), create_file_request.file.clone(), From ec16ed0f82f258c5699d54a386f67aff06c0d144 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:01:25 +0530 Subject: [PATCH 29/48] fix(connector): [CRYPTOPAY] Fix header generation for PSYNC (#3402) --- crates/router/src/connector/cryptopay.rs | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 95ea7ef0c7a9..8727461ba34c 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -69,14 +69,24 @@ where req: &types::RouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let api_method = self.get_http_method().to_string(); - let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?) - .peek() - .to_owned(); - let md5_payload = crypto::Md5 - .generate_digest(body.as_bytes()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - let payload = encode(md5_payload); + let method = self.get_http_method(); + let payload = match method { + common_utils::request::Method::Get => String::default(), + common_utils::request::Method::Post + | common_utils::request::Method::Put + | common_utils::request::Method::Delete + | common_utils::request::Method::Patch => { + let body = + types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?) + .peek() + .to_owned(); + let md5_payload = crypto::Md5 + .generate_digest(body.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + encode(md5_payload) + } + }; + let api_method = method.to_string(); let now = date_time::date_as_yyyymmddthhmmssmmmz() .into_report() From d134d9c82f299c5b5bbf75a729c1b9194f84b945 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:37:30 +0000 Subject: [PATCH 30/48] chore(version): 2024.01.19.1 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb03c28492e..d59aac3f7fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.19.1 + +### Bug Fixes + +- **connector:** [CRYPTOPAY] Fix header generation for PSYNC ([#3402](https://github.com/juspay/hyperswitch/pull/3402)) ([`ec16ed0`](https://github.com/juspay/hyperswitch/commit/ec16ed0f82f258c5699d54a386f67aff06c0d144)) +- **frm:** Update FRM manual review flow ([#3176](https://github.com/juspay/hyperswitch/pull/3176)) ([`5255ba9`](https://github.com/juspay/hyperswitch/commit/5255ba9170c633899cd4c3bbe24a44b429546f15)) + +### Refactors + +- Rename `s3` feature flag to `aws_s3` ([#3341](https://github.com/juspay/hyperswitch/pull/3341)) ([`1c04ac7`](https://github.com/juspay/hyperswitch/commit/1c04ac751240f5c931df0f282af1e0ad745e9509)) + +**Full Changelog:** [`2024.01.19.0...2024.01.19.1`](https://github.com/juspay/hyperswitch/compare/2024.01.19.0...2024.01.19.1) + +- - - + ## 2024.01.19.0 ### Features From a47372a451b60defda35fa212565b889ed5b2d2b Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:35:04 +0530 Subject: [PATCH 31/48] feat(user_roles): Add accept invitation API and `UserJWTAuth` (#3365) --- crates/api_models/src/events/user_role.rs | 7 +-- crates/api_models/src/user_role.rs | 10 ++++ crates/router/src/core/user.rs | 17 +++---- crates/router/src/core/user_role.rs | 48 +++++++++++++++++- crates/router/src/routes/app.rs | 1 + crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/user_role.rs | 19 +++++++ crates/router/src/services/authentication.rs | 53 +++++++++++++++++++- crates/router/src/types/domain/user.rs | 2 +- crates/router/src/utils/user.rs | 21 +++++--- crates/router_env/src/logger/types.rs | 2 + 11 files changed, 159 insertions(+), 24 deletions(-) diff --git a/crates/api_models/src/events/user_role.rs b/crates/api_models/src/events/user_role.rs index aa8d13dab6df..c8d8fd96a7a6 100644 --- a/crates/api_models/src/events/user_role.rs +++ b/crates/api_models/src/events/user_role.rs @@ -1,8 +1,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::user_role::{ - AuthorizationInfoResponse, GetRoleRequest, ListRolesResponse, RoleInfoResponse, - UpdateUserRoleRequest, + AcceptInvitationRequest, AuthorizationInfoResponse, GetRoleRequest, ListRolesResponse, + RoleInfoResponse, UpdateUserRoleRequest, }; common_utils::impl_misc_api_event_type!( @@ -10,5 +10,6 @@ common_utils::impl_misc_api_event_type!( RoleInfoResponse, GetRoleRequest, AuthorizationInfoResponse, - UpdateUserRoleRequest + UpdateUserRoleRequest, + AcceptInvitationRequest ); diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index b057f8ca8bce..d2548935f62a 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -1,3 +1,5 @@ +use crate::user::DashboardEntryResponse; + #[derive(Debug, serde::Serialize)] pub struct ListRolesResponse(pub Vec); @@ -91,3 +93,11 @@ pub enum UserStatus { Active, InvitationSent, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct AcceptInvitationRequest { + pub merchant_ids: Vec, + pub need_dashboard_entry_response: Option, +} + +pub type AcceptInvitationResponse = DashboardEntryResponse; diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 729cef65c20a..3384e2290097 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -90,11 +90,10 @@ pub async fn signup( UserStatus::Active, ) .await?; - let token = - utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?; Ok(ApplicationResponse::Json( - utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?, )) } @@ -118,11 +117,10 @@ pub async fn signin( user_from_db.compare_password(request.password)?; let user_role = user_from_db.get_role_from_db(state.clone()).await?; - let token = - utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?; Ok(ApplicationResponse::Json( - utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?, )) } @@ -600,7 +598,7 @@ pub async fn switch_merchant_id( .ok_or(UserErrors::InvalidRoleOperation.into()) .attach_printable("User doesn't have access to switch")?; - let token = utils::user::generate_jwt_auth_token(state, &user, user_role).await?; + let token = utils::user::generate_jwt_auth_token(&state, &user, user_role).await?; (token, user_role.role_id.clone()) }; @@ -712,11 +710,10 @@ pub async fn verify_email( let user_from_db: domain::UserFromStorage = user.into(); let user_role = user_from_db.get_role_from_db(state.clone()).await?; - let token = - utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?; Ok(ApplicationResponse::Json( - utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?, )) } diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index d8ff836e1f88..245f8d246d23 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -1,6 +1,7 @@ use api_models::user_role as user_role_api; -use diesel_models::user_role::UserRoleUpdate; +use diesel_models::{enums::UserStatus, user_role::UserRoleUpdate}; use error_stack::ResultExt; +use router_env::logger; use crate::{ core::errors::{UserErrors, UserResponse}, @@ -115,3 +116,48 @@ pub async fn update_user_role( Ok(ApplicationResponse::StatusOk) } + +pub async fn accept_invitation( + state: AppState, + user_token: auth::UserWithoutMerchantFromToken, + req: user_role_api::AcceptInvitationRequest, +) -> UserResponse { + let user_role = futures::future::join_all(req.merchant_ids.iter().map(|merchant_id| async { + state + .store + .update_user_role_by_user_id_merchant_id( + user_token.user_id.as_str(), + merchant_id, + UserRoleUpdate::UpdateStatus { + status: UserStatus::Active, + modified_by: user_token.user_id.clone(), + }, + ) + .await + .map_err(|e| { + logger::error!("Error while accepting invitation {}", e); + }) + .ok() + })) + .await + .into_iter() + .reduce(Option::or) + .flatten() + .ok_or(UserErrors::MerchantIdNotFound)?; + + if let Some(true) = req.need_dashboard_entry_response { + let user_from_db = state + .store + .find_user_by_id(user_token.user_id.as_str()) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?; + return Ok(ApplicationResponse::Json( + utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?, + )); + } + + Ok(ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 3d63df2fe800..4345109a6724 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -922,6 +922,7 @@ impl User { .service(web::resource("/role").route(web::get().to(get_role_from_token))) .service(web::resource("/role/{role_id}").route(web::get().to(get_role))) .service(web::resource("/user/invite").route(web::post().to(invite_user))) + .service(web::resource("/user/invite/accept").route(web::post().to(accept_invitation))) .service(web::resource("/update").route(web::post().to(update_user_account_details))) .service( web::resource("/data") diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index d3a2e1af9a71..1c967222dc7f 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -185,7 +185,8 @@ impl From for ApiIdentifier { | Flow::GetRole | Flow::GetRoleFromToken | Flow::UpdateUserRole - | Flow::GetAuthorizationInfo => Self::UserRole, + | Flow::GetAuthorizationInfo + | Flow::AcceptInvitation => Self::UserRole, Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => { Self::ConnectorOnboarding diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs index fe305942d034..73b1ef1b01da 100644 --- a/crates/router/src/routes/user_role.rs +++ b/crates/router/src/routes/user_role.rs @@ -96,3 +96,22 @@ pub async fn update_user_role( )) .await } + +pub async fn accept_invitation( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::AcceptInvitation; + let payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload, + user_role_core::accept_invitation, + &auth::UserWithoutMerchantJWTAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 3370912394e0..eaadc0d5c7be 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -55,6 +55,9 @@ pub enum AuthenticationType { merchant_id: String, user_id: Option, }, + UserJwt { + user_id: String, + }, MerchantId { merchant_id: String, }, @@ -81,11 +84,32 @@ impl AuthenticationType { user_id: _, } | Self::WebhookAuth { merchant_id } => Some(merchant_id.as_ref()), - Self::AdminApiKey | Self::NoAuth => None, + Self::AdminApiKey | Self::UserJwt { .. } | Self::NoAuth => None, } } } +#[derive(Clone, Debug)] +pub struct UserWithoutMerchantFromToken { + pub user_id: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct UserAuthToken { + pub user_id: String, + pub exp: u64, +} + +#[cfg(feature = "olap")] +impl UserAuthToken { + pub async fn new_token(user_id: String, settings: &settings::Settings) -> UserResult { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration)?.as_secs(); + let token_payload = Self { user_id, exp }; + jwt::generate_jwt(&token_payload, settings).await + } +} + #[derive(serde::Serialize, serde::Deserialize)] pub struct AuthToken { pub user_id: String, @@ -276,6 +300,33 @@ pub async fn get_admin_api_key( .await } +#[derive(Debug)] +pub struct UserWithoutMerchantJWTAuth; + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for UserWithoutMerchantJWTAuth +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + Ok(( + UserWithoutMerchantFromToken { + user_id: payload.user_id.clone(), + }, + AuthenticationType::UserJwt { + user_id: payload.user_id, + }, + )) + } +} + #[derive(Debug)] pub struct AdminApiAuth; diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 53c88f8aea12..bbe21f289aa1 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -739,7 +739,7 @@ impl UserFromStorage { } #[cfg(feature = "email")] - pub fn get_verification_days_left(&self, state: AppState) -> UserResult> { + pub fn get_verification_days_left(&self, state: &AppState) -> UserResult> { if self.0.is_verified { return Ok(None); } diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index a115fa2a2d8a..a3f9e7978aa1 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -56,7 +56,7 @@ impl UserFromToken { } pub async fn generate_jwt_auth_token( - state: AppState, + state: &AppState, user: &UserFromStorage, user_role: &UserRole, ) -> UserResult> { @@ -89,17 +89,13 @@ pub async fn generate_jwt_auth_token_with_custom_role_attributes( Ok(Secret::new(token)) } -#[allow(unused_variables)] pub fn get_dashboard_entry_response( - state: AppState, + state: &AppState, user: UserFromStorage, user_role: UserRole, token: Secret, ) -> UserResult { - #[cfg(feature = "email")] - let verification_days_left = user.get_verification_days_left(state)?; - #[cfg(not(feature = "email"))] - let verification_days_left = None; + let verification_days_left = get_verification_days_left(state, &user)?; Ok(user_api::DashboardEntryResponse { merchant_id: user_role.merchant_id, @@ -111,3 +107,14 @@ pub fn get_dashboard_entry_response( user_role: user_role.role_id, }) } + +#[allow(unused_variables)] +pub fn get_verification_days_left( + state: &AppState, + user: &UserFromStorage, +) -> UserResult> { + #[cfg(feature = "email")] + return user.get_verification_days_left(state); + #[cfg(not(feature = "email"))] + return Ok(None); +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 7e3a692517f1..ba323ebc5e3f 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -335,6 +335,8 @@ pub enum Flow { VerifyEmailRequest, /// Update user account details UpdateUserAccountDetails, + /// Accept user invitation + AcceptInvitation, } /// From 4e1e78ecd962f4b34fa04f611f03e8e6f6e1bd7c Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Fri, 19 Jan 2024 19:32:34 +0530 Subject: [PATCH 32/48] docs: add link to api docs (#3405) Co-authored-by: venkatesh.devendran --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dfa77ebe0666..0f5e924589f2 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ The single API to access payment ecosystems across 130+ countries Quick Start Guide • Local Setup Guide • Fast Integration for Stripe Users • + API Docs • Supported Features • - FAQs
What's Included • Join us in building HyperSwitch • Community • Bugs and feature requests • + FAQs • Versioning • Copyright and License

From 3fba38a06daeccbd7023e508028de9f793ba7713 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:21:11 +0000 Subject: [PATCH 33/48] chore(version): 2024.01.22.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d59aac3f7fa1..51d650f3fb81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.22.0 + +### Features + +- **user_roles:** Add accept invitation API and `UserJWTAuth` ([#3365](https://github.com/juspay/hyperswitch/pull/3365)) ([`a47372a`](https://github.com/juspay/hyperswitch/commit/a47372a451b60defda35fa212565b889ed5b2d2b)) + +### Documentation + +- Add link to api docs ([#3405](https://github.com/juspay/hyperswitch/pull/3405)) ([`4e1e78e`](https://github.com/juspay/hyperswitch/commit/4e1e78ecd962f4b34fa04f611f03e8e6f6e1bd7c)) + +**Full Changelog:** [`2024.01.19.1...2024.01.22.0`](https://github.com/juspay/hyperswitch/compare/2024.01.19.1...2024.01.22.0) + +- - - + ## 2024.01.19.1 ### Bug Fixes From 6c46e9c19b304bb11f304e60c46e8abf67accf6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:53:09 +0530 Subject: [PATCH 34/48] chore(deps): bump the cargo group across 1 directories with 3 updates (#3409) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 261 +++++++++++++++++----------- crates/analytics/Cargo.toml | 2 +- crates/common_utils/Cargo.toml | 2 +- crates/drainer/Cargo.toml | 2 +- crates/external_services/Cargo.toml | 2 +- crates/redis_interface/Cargo.toml | 4 +- crates/router/Cargo.toml | 4 +- crates/router_env/Cargo.toml | 4 +- crates/scheduler/Cargo.toml | 2 +- crates/storage_impl/Cargo.toml | 2 +- crates/test_utils/Cargo.toml | 2 +- 11 files changed, 171 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ce0851ba159..2cd3cd65c318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ "futures-sink", "memchr", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", "tracing", ] @@ -67,7 +67,7 @@ dependencies = [ "rand 0.8.5", "sha1", "smallvec 1.11.1", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", "tracing", "zstd", @@ -105,7 +105,7 @@ dependencies = [ "serde_json", "serde_plain", "tempfile", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -142,7 +142,7 @@ checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" dependencies = [ "actix-macros", "futures-core", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -156,9 +156,9 @@ dependencies = [ "actix-utils", "futures-core", "futures-util", - "mio 0.8.8", - "socket2 0.5.4", - "tokio 1.32.0", + "mio 0.8.10", + "socket2 0.5.5", + "tokio 1.35.1", "tracing", ] @@ -188,7 +188,7 @@ dependencies = [ "pin-project-lite", "rustls 0.21.7", "rustls-webpki", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-rustls", "tokio-util", "tracing", @@ -360,7 +360,7 @@ dependencies = [ "strum 0.25.0", "thiserror", "time", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -508,7 +508,7 @@ dependencies = [ "bb8", "diesel", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", "tracing", ] @@ -533,7 +533,7 @@ dependencies = [ "futures-core", "memchr", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -631,7 +631,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -658,7 +658,7 @@ dependencies = [ "hyper", "ring", "time", - "tokio 1.32.0", + "tokio 1.35.1", "tower", "tracing", "zeroize", @@ -673,7 +673,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-types", "fastrand 1.9.0", - "tokio 1.32.0", + "tokio 1.35.1", "tracing", "zeroize", ] @@ -914,7 +914,7 @@ checksum = "13bda3996044c202d75b91afeb11a9afae9db9a721c6a7a427410018e286b880" dependencies = [ "futures-util", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-stream", ] @@ -958,7 +958,7 @@ dependencies = [ "lazy_static", "pin-project-lite", "rustls 0.20.9", - "tokio 1.32.0", + "tokio 1.35.1", "tower", "tracing", ] @@ -992,7 +992,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "pin-utils", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", "tracing", ] @@ -1168,7 +1168,7 @@ dependencies = [ "futures-channel", "futures-util", "parking_lot 0.12.1", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -1531,7 +1531,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1685,7 +1685,7 @@ dependencies = [ "test-case", "thiserror", "time", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -2071,7 +2071,7 @@ dependencies = [ "deadpool-runtime", "num_cpus", "retain_mut", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -2283,7 +2283,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -2324,23 +2324,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -2439,7 +2428,7 @@ dependencies = [ "router_env", "serde", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -2469,7 +2458,7 @@ dependencies = [ "serde", "serde_json", "time", - "tokio 1.32.0", + "tokio 1.35.1", "url", "webdriver", ] @@ -2562,8 +2551,8 @@ dependencies = [ "rand 0.8.5", "redis-protocol", "semver 1.0.19", - "socket2 0.5.4", - "tokio 1.32.0", + "socket2 0.5.5", + "tokio 1.35.1", "tokio-stream", "tokio-util", "tracing", @@ -2789,7 +2778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2877,9 +2866,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes 1.5.0", "fnv", @@ -2887,9 +2876,9 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", "tracing", ] @@ -3073,7 +3062,7 @@ dependencies = [ "itoa", "pin-project-lite", "socket2 0.4.9", - "tokio 1.32.0", + "tokio 1.35.1", "tower-service", "tracing", "want", @@ -3092,7 +3081,7 @@ dependencies = [ "hyper", "hyper-tls", "native-tls", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-native-tls", "tower-service", ] @@ -3108,7 +3097,7 @@ dependencies = [ "log", "rustls 0.20.9", "rustls-native-certs", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-rustls", ] @@ -3120,7 +3109,7 @@ checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-io-timeout", ] @@ -3133,7 +3122,7 @@ dependencies = [ "bytes 1.5.0", "hyper", "native-tls", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-native-tls", ] @@ -3287,7 +3276,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3474,9 +3463,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-channel" @@ -3725,14 +3714,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4040,7 +4029,7 @@ dependencies = [ "opentelemetry-proto", "prost", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", "tonic", ] @@ -4091,7 +4080,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-stream", ] @@ -4194,7 +4183,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec 1.11.1", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -4796,7 +4785,7 @@ dependencies = [ "serde_derive", "serde_json", "slab", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -4836,7 +4825,7 @@ dependencies = [ "router_env", "serde", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-stream", ] @@ -4970,7 +4959,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "system-configuration", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-native-tls", "tokio-util", "tower-service", @@ -5141,7 +5130,7 @@ dependencies = [ "test_utils", "thiserror", "time", - "tokio 1.32.0", + "tokio 1.35.1", "tracing-futures", "unicode-segmentation", "url", @@ -5184,7 +5173,7 @@ dependencies = [ "serde_path_to_error", "strum 0.24.1", "time", - "tokio 1.32.0", + "tokio 1.35.1", "tracing", "tracing-actix-web", "tracing-appender", @@ -5315,15 +5304,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.17" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -5430,7 +5419,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5463,7 +5452,7 @@ dependencies = [ "strum 0.24.1", "thiserror", "time", - "tokio 1.32.0", + "tokio 1.35.1", "uuid", ] @@ -5793,7 +5782,7 @@ dependencies = [ "futures-core", "libc", "signal-hook", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -5880,12 +5869,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5995,7 +5984,7 @@ checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "native-tls", "once_cell", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-native-tls", ] @@ -6030,7 +6019,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -6204,7 +6193,7 @@ dependencies = [ "fastrand 2.0.1", "redox_syscall 0.3.5", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -6280,7 +6269,7 @@ dependencies = [ "serial_test", "thirtyfour", "time", - "tokio 1.32.0", + "tokio 1.35.1", "toml 0.7.4", ] @@ -6305,7 +6294,7 @@ dependencies = [ "stringmatch", "thirtyfour-macros", "thiserror", - "tokio 1.32.0", + "tokio 1.35.1", "url", "urlparse", ] @@ -6441,21 +6430,21 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes 1.5.0", "libc", - "mio 0.8.8", + "mio 0.8.10", "num_cpus", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -6518,14 +6507,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -6539,7 +6528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] @@ -6568,7 +6557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls 0.20.9", - "tokio 1.32.0", + "tokio 1.35.1", "webpki", ] @@ -6580,7 +6569,7 @@ checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", ] @@ -6680,7 +6669,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project-lite", - "tokio 1.32.0", + "tokio 1.35.1", "tracing", ] @@ -6761,7 +6750,7 @@ dependencies = [ "pin-project", "prost", "prost-derive", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-stream", "tokio-util", "tower", @@ -6784,7 +6773,7 @@ dependencies = [ "pin-project-lite", "rand 0.8.5", "slab", - "tokio 1.32.0", + "tokio 1.35.1", "tokio-util", "tower-layer", "tower-service", @@ -7422,7 +7411,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -7431,7 +7420,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -7440,13 +7438,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -7455,42 +7468,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.19" @@ -7507,7 +7562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7529,7 +7584,7 @@ dependencies = [ "regex", "serde", "serde_json", - "tokio 1.32.0", + "tokio 1.35.1", ] [[package]] diff --git a/crates/analytics/Cargo.toml b/crates/analytics/Cargo.toml index 25066970ddcd..8e7e38d19d9e 100644 --- a/crates/analytics/Cargo.toml +++ b/crates/analytics/Cargo.toml @@ -34,4 +34,4 @@ sqlx = { version = "0.6.3", features = ["postgres", "runtime-actix", "runtime-ac strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.43" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 739129d02db2..3e6ee40f3a7b 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -37,7 +37,7 @@ signal-hook = { version = "0.3.15", optional = true } strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"], optional = true } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"], optional = true } # First party crates common_enums = { version = "0.1.0", path = "../common_enums" } diff --git a/crates/drainer/Cargo.toml b/crates/drainer/Cargo.toml index 50e0effd03e0..f26c31f0e72c 100644 --- a/crates/drainer/Cargo.toml +++ b/crates/drainer/Cargo.toml @@ -24,7 +24,7 @@ serde = "1.0.193" serde_json = "1.0.108" serde_path_to_error = "0.1.14" thiserror = "1.0.40" -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } async-trait = "0.1.74" # First Party Crates diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index 4767e4f8d255..90e5df538055 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -24,7 +24,7 @@ error-stack = "0.3.1" once_cell = "1.18.0" serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.40" -tokio = "1.28.2" +tokio = "1.35.1" hyper-proxy = "0.9.1" hyper = "0.14.26" diff --git a/crates/redis_interface/Cargo.toml b/crates/redis_interface/Cargo.toml index 1a6bc96a7fc4..9d2c6042731b 100644 --- a/crates/redis_interface/Cargo.toml +++ b/crates/redis_interface/Cargo.toml @@ -13,7 +13,7 @@ fred = { version = "7.0.0", features = ["metrics", "partial-tracing", "subscribe futures = "0.3" serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.40" -tokio = "1.28.2" +tokio = "1.35.1" tokio-stream = {version = "0.1.14", features = ["sync"]} # First party crates @@ -21,4 +21,4 @@ common_utils = { version = "0.1.0", path = "../common_utils", features = ["async router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } [dev-dependencies] -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 88272033fb04..acc6b70a2edd 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -91,7 +91,7 @@ strum = { version = "0.25", features = ["derive"] } tera = "1.19.1" thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } unicode-segmentation = "1.10.1" url = { version = "2.4.0", features = ["serde"] } utoipa = { version = "3.3.0", features = ["preserve_order", "time"] } @@ -134,7 +134,7 @@ derive_deref = "1.1.1" rand = "0.8.5" serial_test = "2.0.0" time = { version = "0.3.21", features = ["macros"] } -tokio = "1.28.2" +tokio = "1.35.1" wiremock = "0.5.18" # First party dev-dependencies diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index ae82a0c094dd..8dca7942ab0a 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -21,7 +21,7 @@ serde_json = "1.0.108" serde_path_to_error = "0.1.14" strum = { version = "0.24.1", features = ["derive"] } time = { version = "0.3.21", default-features = false, features = ["formatting"] } -tokio = { version = "1.28.2" } +tokio = { version = "1.35.1" } tracing = { version = "=0.1.36" } tracing-actix-web = { version = "0.7.8", features = ["opentelemetry_0_19", "uuid_v7"], optional = true } tracing-appender = { version = "0.2.2" } @@ -31,7 +31,7 @@ tracing-subscriber = { version = "0.3.17", default-features = true, features = [ vergen = { version = "8.2.1", optional = true, features = ["cargo", "git", "git2", "rustc"] } [dev-dependencies] -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } [build-dependencies] cargo_metadata = "0.15.4" diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index 40f7ff7b9474..fe090552edb3 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -20,7 +20,7 @@ serde_json = "1.0.108" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } -tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } uuid = { version = "1.3.3", features = ["serde", "v4"] } # First party crates diff --git a/crates/storage_impl/Cargo.toml b/crates/storage_impl/Cargo.toml index 0155980d9f7d..c39154d97622 100644 --- a/crates/storage_impl/Cargo.toml +++ b/crates/storage_impl/Cargo.toml @@ -44,4 +44,4 @@ ring = "0.16.20" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" thiserror = "1.0.40" -tokio = { version = "1.28.2", features = ["rt-multi-thread"] } +tokio = { version = "1.35.1", features = ["rt-multi-thread"] } diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index a95e2e3921bd..d53cdc4ac074 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml @@ -24,7 +24,7 @@ serde_urlencoded = "0.7.1" serial_test = "2.0.0" thirtyfour = "0.31.0" time = { version = "0.3.21", features = ["macros"] } -tokio = "1.28.2" +tokio = "1.35.1" toml = "0.7.4" # First party crates From 7813ceece2081b73f1374e2ee5a9a673f0b72127 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:45:39 +0530 Subject: [PATCH 35/48] feat(core): send `customer_name` to connectors when creating customer (#3380) --- .../router/src/connector/stax/transformers.rs | 2 +- .../src/connector/stripe/transformers.rs | 2 +- .../src/core/payments/flows/authorize_flow.rs | 2 +- .../router/src/core/payments/transformers.rs | 28 ++++++++++++++++++- crates/router/src/types.rs | 18 +++--------- .../router/src/types/api/verify_connector.rs | 1 + crates/router/tests/connectors/aci.rs | 1 + crates/router/tests/connectors/adyen.rs | 1 + crates/router/tests/connectors/bitpay.rs | 1 + crates/router/tests/connectors/cashtocode.rs | 1 + crates/router/tests/connectors/coinbase.rs | 1 + crates/router/tests/connectors/cryptopay.rs | 1 + crates/router/tests/connectors/opennode.rs | 1 + crates/router/tests/connectors/utils.rs | 1 + crates/router/tests/connectors/worldline.rs | 1 + 15 files changed, 44 insertions(+), 18 deletions(-) diff --git a/crates/router/src/connector/stax/transformers.rs b/crates/router/src/connector/stax/transformers.rs index 01ae751f7487..596ea1145ecc 100644 --- a/crates/router/src/connector/stax/transformers.rs +++ b/crates/router/src/connector/stax/transformers.rs @@ -147,7 +147,7 @@ pub struct StaxCustomerRequest { #[serde(skip_serializing_if = "Option::is_none")] email: Option, #[serde(skip_serializing_if = "Option::is_none")] - firstname: Option, + firstname: Option>, } impl TryFrom<&types::ConnectorCustomerRouterData> for StaxCustomerRequest { diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 89e186924142..1dbb310868a6 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -2018,7 +2018,7 @@ impl TryFrom<&types::ConnectorCustomerRouterData> for CustomerRequest { description: item.request.description.to_owned(), email: item.request.email.to_owned(), phone: item.request.phone.to_owned(), - name: item.request.name.to_owned().map(Secret::new), + name: item.request.name.to_owned(), source: item.request.preprocessing_id.to_owned(), }) } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 15c79f4b9d95..c6de222f7d83 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -376,7 +376,7 @@ impl TryFrom<&types::RouterData( connector_id: &str, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - customer: &Option, + customer: &'a Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, ) -> RouterResult> where @@ -89,6 +89,7 @@ where connector_name: connector_id.to_string(), payment_data: payment_data.clone(), state, + customer_data: customer, }; let customer_id = customer.to_owned().map(|customer| customer.customer_id); @@ -968,6 +969,7 @@ where connector_name: String, payment_data: PaymentData, state: &'a AppState, + customer_data: &'a Option, } impl TryFrom> for types::PaymentsAuthorizeData { type Error = error_stack::Report; @@ -1048,6 +1050,17 @@ impl TryFrom> for types::PaymentsAuthoriz .as_ref() .map(|surcharge_details| surcharge_details.final_amount) .unwrap_or(payment_data.amount.into()); + + let customer_name = additional_data + .customer_data + .as_ref() + .and_then(|customer_data| { + customer_data + .name + .as_ref() + .map(|customer| customer.clone().into_inner()) + }); + Ok(Self { payment_method_data: payment_method_data.get_required_value("payment_method_data")?, setup_future_usage: payment_data.payment_intent.setup_future_usage, @@ -1062,6 +1075,7 @@ impl TryFrom> for types::PaymentsAuthoriz currency: payment_data.currency, browser_info, email: payment_data.email, + customer_name, payment_experience: payment_data.payment_attempt.payment_experience, order_details, order_category, @@ -1354,6 +1368,17 @@ impl TryFrom> for types::SetupMandateRequ .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "browser_info", })?; + + let customer_name = additional_data + .customer_data + .as_ref() + .and_then(|customer_data| { + customer_data + .name + .as_ref() + .map(|customer| customer.clone().into_inner()) + }); + Ok(Self { currency: payment_data.currency, confirm: true, @@ -1368,6 +1393,7 @@ impl TryFrom> for types::SetupMandateRequ setup_mandate_details: payment_data.setup_mandate, router_return_url, email: payment_data.email, + customer_name, return_url: payment_data.payment_intent.return_url, browser_info, payment_method_type: attempt.payment_method_type, diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index e236113e6768..0809ca178203 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -392,6 +392,7 @@ pub struct PaymentsAuthorizeData { /// ``` pub amount: i64, pub email: Option, + pub customer_name: Option>, pub currency: storage_enums::Currency, pub confirm: bool, pub statement_descriptor_suffix: Option, @@ -461,7 +462,7 @@ pub struct ConnectorCustomerData { pub description: Option, pub email: Option, pub phone: Option>, - pub name: Option, + pub name: Option>, pub preprocessing_id: Option, pub payment_method_data: payments::PaymentMethodData, } @@ -586,6 +587,7 @@ pub struct SetupMandateRequestData { pub router_return_url: Option, pub browser_info: Option, pub email: Option, + pub customer_name: Option>, pub return_url: Option, pub payment_method_type: Option, pub request_incremental_authorization: bool, @@ -1342,19 +1344,6 @@ impl From<&&mut PaymentsAuthorizeRouterData> for AuthorizeSessionTokenData { } } -impl From<&&mut PaymentsAuthorizeRouterData> for ConnectorCustomerData { - fn from(data: &&mut PaymentsAuthorizeRouterData) -> Self { - Self { - email: data.request.email.to_owned(), - preprocessing_id: data.preprocessing_id.to_owned(), - payment_method_data: data.request.payment_method_data.to_owned(), - description: None, - phone: None, - name: None, - } - } -} - impl From<&RouterData> for PaymentMethodTokenizationData { @@ -1411,6 +1400,7 @@ impl From<&SetupMandateRouterData> for PaymentsAuthorizeData { setup_mandate_details: data.request.setup_mandate_details.clone(), router_return_url: data.request.router_return_url.clone(), email: data.request.email.clone(), + customer_name: data.request.customer_name.clone(), amount: 0, statement_descriptor: None, capture_method: None, diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index c5fcce8b185e..fbd942305845 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -24,6 +24,7 @@ impl VerifyConnectorData { types::PaymentsAuthorizeData { payment_method_data: api::PaymentMethodData::Card(self.card_details.clone()), email: None, + customer_name: None, amount: 1000, confirm: true, currency: storage_enums::Currency::USD, diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 35c9cbd952d3..c820b7acd6e4 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -59,6 +59,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { order_details: None, order_category: None, email: None, + customer_name: None, session_token: None, enrolled_for_3ds: false, related_transaction_id: None, diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 490750805062..430ae0bac147 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -147,6 +147,7 @@ impl AdyenTest { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type: None, session_token: None, diff --git a/crates/router/tests/connectors/bitpay.rs b/crates/router/tests/connectors/bitpay.rs index 8bac7c13c85f..892d5b1f208f 100644 --- a/crates/router/tests/connectors/bitpay.rs +++ b/crates/router/tests/connectors/bitpay.rs @@ -81,6 +81,7 @@ fn payment_method_details() -> Option { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type: None, session_token: None, diff --git a/crates/router/tests/connectors/cashtocode.rs b/crates/router/tests/connectors/cashtocode.rs index 68c4eb94bf32..9d0824457199 100644 --- a/crates/router/tests/connectors/cashtocode.rs +++ b/crates/router/tests/connectors/cashtocode.rs @@ -57,6 +57,7 @@ impl CashtocodeTest { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type, session_token: None, diff --git a/crates/router/tests/connectors/coinbase.rs b/crates/router/tests/connectors/coinbase.rs index 73ee93178c01..9a476df7fe63 100644 --- a/crates/router/tests/connectors/coinbase.rs +++ b/crates/router/tests/connectors/coinbase.rs @@ -83,6 +83,7 @@ fn payment_method_details() -> Option { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type: None, session_token: None, diff --git a/crates/router/tests/connectors/cryptopay.rs b/crates/router/tests/connectors/cryptopay.rs index 5df8d80461fa..5e1b3f5ab47b 100644 --- a/crates/router/tests/connectors/cryptopay.rs +++ b/crates/router/tests/connectors/cryptopay.rs @@ -81,6 +81,7 @@ fn payment_method_details() -> Option { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type: None, session_token: None, diff --git a/crates/router/tests/connectors/opennode.rs b/crates/router/tests/connectors/opennode.rs index b140a7c05170..69edec2af2cf 100644 --- a/crates/router/tests/connectors/opennode.rs +++ b/crates/router/tests/connectors/opennode.rs @@ -82,6 +82,7 @@ fn payment_method_details() -> Option { order_details: None, order_category: None, email: None, + customer_name: None, payment_experience: None, payment_method_type: None, session_token: None, diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index db82cd7e0324..ed3cdbe31b52 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -900,6 +900,7 @@ impl Default for PaymentAuthorizeType { order_details: None, order_category: None, email: None, + customer_name: None, session_token: None, enrolled_for_3ds: false, related_transaction_id: None, diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index 4f7a94780a59..8b8657890039 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -92,6 +92,7 @@ impl WorldlineTest { order_details: None, order_category: None, email: None, + customer_name: None, session_token: None, enrolled_for_3ds: false, related_transaction_id: None, From 25790a161aebe86d58edb6feafce821a77b69dd4 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:20:30 +0000 Subject: [PATCH 36/48] chore(version): 2024.01.22.1 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d650f3fb81..a964c6b1748b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.22.1 + +### Features + +- **core:** Send `customer_name` to connectors when creating customer ([#3380](https://github.com/juspay/hyperswitch/pull/3380)) ([`7813cee`](https://github.com/juspay/hyperswitch/commit/7813ceece2081b73f1374e2ee5a9a673f0b72127)) + +### Miscellaneous Tasks + +- Chore(deps): bump the cargo group across 1 directories with 3 updates ([#3409](https://github.com/juspay/hyperswitch/pull/3409)) ([`6c46e9c`](https://github.com/juspay/hyperswitch/commit/6c46e9c19b304bb11f304e60c46e8abf67accf6d)) + +**Full Changelog:** [`2024.01.22.0...2024.01.22.1`](https://github.com/juspay/hyperswitch/compare/2024.01.22.0...2024.01.22.1) + +- - - + ## 2024.01.22.0 ### Features From 4a8104e5f8dd2cfd03de4055baf1256cb7533895 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:04:42 +0530 Subject: [PATCH 37/48] feat(compatibility): add multiuse mandates support in stripe compatibility (#3425) --- .../stripe/payment_intents/types.rs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 38007a3110d6..51f938d445c4 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -738,9 +738,25 @@ impl ForeignTryFrom<(Option, Option)> for Option Some(payments::MandateType::MultiUse(None)), + StripeMandateType::MultiUse => Some(payments::MandateType::MultiUse(Some( + payments::MandateAmountData { + amount: mandate.amount.unwrap_or_default(), + currency, + start_date: mandate.start_date, + end_date: mandate.end_date, + metadata: None, + }, + ))), }, - None => Some(api_models::payments::MandateType::MultiUse(None)), + None => Some(api_models::payments::MandateType::MultiUse(Some( + payments::MandateAmountData { + amount: mandate.amount.unwrap_or_default(), + currency, + start_date: mandate.start_date, + end_date: mandate.end_date, + metadata: None, + }, + ))), }, customer_acceptance: Some(payments::CustomerAcceptance { acceptance_type: payments::AcceptanceType::Online, From d2c3a830ad6ef5c317928949f7f1b20c2f4abb87 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:31:23 +0000 Subject: [PATCH 38/48] chore(version): 2024.01.23.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a964c6b1748b..800218b8a437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.23.0 + +### Features + +- **compatibility:** Add multiuse mandates support in stripe compatibility ([#3425](https://github.com/juspay/hyperswitch/pull/3425)) ([`4a8104e`](https://github.com/juspay/hyperswitch/commit/4a8104e5f8dd2cfd03de4055baf1256cb7533895)) + +**Full Changelog:** [`2024.01.22.1...2024.01.23.0`](https://github.com/juspay/hyperswitch/compare/2024.01.22.1...2024.01.23.0) + +- - - + ## 2024.01.22.1 ### Features From 8551c72fd8cc95ea5de08eb5491bfd43a0c142b1 Mon Sep 17 00:00:00 2001 From: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:37:04 +0530 Subject: [PATCH 39/48] ci(postman): Fix session call in Stripe collection (#3430) --- .../Payment Connector - Create/request.json | 15 --------------- .../Payment Connector - Create/request.json | 2 +- .../Payment Connector - Create/request.json | 2 +- .../Payment Connector - Create/request.json | 2 +- .../Payment Connector - Create/request.json | 2 +- .../Payment Connector - Update/request.json | 2 +- .../Payout Connector - Create/request.json | 2 +- 7 files changed, 6 insertions(+), 21 deletions(-) diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json index 375c1f0df52f..b5002aeca8e7 100644 --- a/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -321,21 +321,6 @@ "merchant_info": { "merchant_name": "Narayan Bhat" } - }, - "apple_pay": { - "session_token_data": { - "initiative": "web", - "certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdKakNDQlE2Z0F3SUJBZ0lRRENzRmFrVkNLU01uc2JacTc1YTI0ekFOQmdrcWhraUc5dzBCQVFzRkFEQjEKTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRApaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFTE1Ba0dBMVVFQ3d3Q1J6TXhFekFSQmdOVkJBb01Da0Z3CmNHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeU1USXdPREE1TVRJeE1Wb1hEVEkxTURFd05qQTUKTVRJeE1Gb3dnYWd4SmpBa0Jnb0praWFKay9Jc1pBRUJEQlp0WlhKamFHRnVkQzVqYjIwdVlXUjVaVzR1YzJGdQpNVHN3T1FZRFZRUUREREpCY0hCc1pTQlFZWGtnVFdWeVkyaGhiblFnU1dSbGJuUnBkSGs2YldWeVkyaGhiblF1ClkyOXRMbUZrZVdWdUxuTmhiakVUTUJFR0ExVUVDd3dLV1UwNVZUY3pXakpLVFRFc01Db0dBMVVFQ2d3alNsVlQKVUVGWklGUkZRMGhPVDB4UFIwbEZVeUJRVWtsV1FWUkZJRXhKVFVsVVJVUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQgpBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRDhIUy81ZmJZNVJLaElYU3pySEpoeTVrNmY0YUdMaEltYklLaXFYRUlUCnVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYzV5eGE0cHg5eHlmQlVIejhzeU9pMjdYNVZaVG8KTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVZlhQWHBjdjFqVVRyRCtlc1RJTFZUb1FUTmhDcwplQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWNaWC9vWTB1R040VWd4c0JYWHdZM0dKbTFSQ3B1CjM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSEhNMGpEQ2lncVU1RktwL1pBbHdzYmg1WVZOU00KWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nV1Y0Z0hUNmhBZ01CQUFHamdnSjhNSUlDZURBTQpCZ05WSFJNQkFmOEVBakFBTUI4R0ExVWRJd1FZTUJhQUZBbit3QldRK2E5a0NwSVN1U1lvWXd5WDdLZXlNSEFHCkNDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnYKYlM5M2QyUnlaek11WkdWeU1ERUdDQ3NHQVFVRkJ6QUJoaVZvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdgpiMk56Y0RBekxYZDNaSEpuTXpBNU1JSUJMUVlEVlIwZ0JJSUJKRENDQVNBd2dnRWNCZ2txaGtpRzkyTmtCUUV3CmdnRU5NSUhSQmdnckJnRUZCUWNDQWpDQnhBeUJ3VkpsYkdsaGJtTmxJRzl1SUhSb2FYTWdRMlZ5ZEdsbWFXTmgKZEdVZ1lua2dZVzU1SUhCaGNuUjVJRzkwYUdWeUlIUm9ZVzRnUVhCd2JHVWdhWE1nY0hKdmFHbGlhWFJsWkM0ZwpVbVZtWlhJZ2RHOGdkR2hsSUdGd2NHeHBZMkZpYkdVZ2MzUmhibVJoY21RZ2RHVnliWE1nWVc1a0lHTnZibVJwCmRHbHZibk1nYjJZZ2RYTmxMQ0JqWlhKMGFXWnBZMkYwWlNCd2IyeHBZM2tnWVc1a0lHTmxjblJwWm1sallYUnAKYjI0Z2NISmhZM1JwWTJVZ2MzUmhkR1Z0Wlc1MGN5NHdOd1lJS3dZQkJRVUhBZ0VXSzJoMGRIQnpPaTh2ZDNkMwpMbUZ3Y0d4bExtTnZiUzlqWlhKMGFXWnBZMkYwWldGMWRHaHZjbWwwZVM4d0V3WURWUjBsQkF3d0NnWUlLd1lCCkJRVUhBd0l3SFFZRFZSME9CQllFRk5RSysxcUNHbDRTQ1p6SzFSUmpnb05nM0hmdk1BNEdBMVVkRHdFQi93UUUKQXdJSGdEQlBCZ2txaGtpRzkyTmtCaUFFUWd4QVFVUkNRemxDTmtGRE5USkVRems0TnpCRk5qYzJNVFpFUkRJdwpPVUkwTWtReE1UVXlSVVpFTURVeFFVRXhRekV6T0ROR00wUkROa1V5TkVNelFqRkVSVEFQQmdrcWhraUc5Mk5rCkJpNEVBZ1VBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBSFR6NTU2RUs5VVp6R0RVd2cvcmFibmYrUXFSYkgKcllVS0ZNcWQwUDhFTHZGMmYrTzN0ZXlDWHNBckF4TmVMY2hRSGVTNUFJOHd2azdMQ0xLUmJCdWJQQy9NVmtBKwpCZ3h5STg2ejJOVUNDWml4QVM1d2JFQWJYOStVMFp2RHp5Y01BbUNrdVVHZjNwWXR5TDNDaEplSGRvOEwwdmdvCnJQWElUSzc4ZjQzenNzYjBTNE5xbTE0eS9LNCs1ZkUzcFUxdEJqME5tUmdKUVJLRnB6MENpV2RPd1BRTk5BYUMKYUNNU2NiYXdwUTBjWEhaZDJWVjNtem4xdUlITEVtaU5GTWVxeEprbjZhUXFFQnNndDUzaUFxcmZMNjEzWStScAppd0tENXVmeU0wYzBweTYyZmkvWEwwS2c4ajEwWU1VdWJpd2dHajAzZThQWTB6bWUvcGZCZ3p6VQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", - "display_name": "applepay", - "certificate_keys": "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRDhIUy81ZmJZNVJLaEkKWFN6ckhKaHk1azZmNGFHTGhJbWJJS2lxWEVJVHVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYwo1eXhhNHB4OXh5ZkJVSHo4c3lPaTI3WDVWWlRvTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVCmZYUFhwY3YxalVUckQrZXNUSUxWVG9RVE5oQ3NlQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWMKWlgvb1kwdUdONFVneHNCWFh3WTNHSm0xUkNwdTM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSApITTBqRENpZ3FVNUZLcC9aQWx3c2JoNVlWTlNNWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nCldWNGdIVDZoQWdNQkFBRUNnZ0VBZFNaRzVhTFJxdmpKVFo3bVFYWHZMT3p4dWY5SlpxQTJwUHZmQkJLTXJjZC8KL2RDZXpGSGRhZ1VvWXNUQjRXekluaVVjL2Z3bDJTUzJyREFMZjB2dnRjNTJIYkQ5OHhwMnc3VmVjTGFnMCtuWAo2dUJaSEZCS3FWNU1LZ1l6YUpVMTdqaDM2VEV3dTFnbmdlZnRqVlpBV1NERTFvbDBlSzZ3Mk5kOExjVWdxRkxUCjVHYUlBV01nd0NKL3pzQmwydUV1Y0Q4S21WL1Z2MkVCQVJLWGZtci92UU1NelZrNkhhalprVGZqbWY2cWFVQVMKQWlFblROcHBic2ZrdTk2VGdIa2owWm10VWc0SFkzSU9qWFlpaGJsSjJzQ1JjS3p6cXkxa3B3WlpHcHo1NXEzbgphSXEwenJ3RjlpTUZubEhCa04yK3FjSnhzcDNTalhRdFRLTTY4WHRrVlFLQmdRRC8wemtCVlExR2Q1U0Mzb2czCnM3QWRCZ243dnVMdUZHZFFZY3c0OUppRGw1a1BZRXlKdGQvTVpNOEpFdk1nbVVTeUZmczNZcGtmQ2VGbUp0S3QKMnNNNEdCRWxqTVBQNjI3Q0QrV3c4L3JpWmlOZEg3OUhPRjRneTRGbjBycDNqanlLSWF1OHJISDQwRUUvSkVyOQpxWFQ1SGdWMmJQOGhMcW5sSjFmSDhpY2Zkd0tCZ1FEOFNWQ3ZDV2txQkh2SzE5ZDVTNlArdm5hcXhnTWo0U0srCnJ6L1I1c3pTaW5lS045VEhyeVkxYUZJbVFJZjJYOUdaQXBocUhrckxtQ3BIcURHOWQ3WDVQdUxxQzhmc09kVTYKRzhWaFRXeXdaSVNjdGRSYkk5S2xKUFk2V2ZDQjk0ODNVaDJGbW1xV2JuNWcwZUJxZWZzamVVdEtHekNRaGJDYworR1dBREVRSXB3S0JnUURmaWYvN3pBZm5sVUh1QU9saVV0OEczV29IMGtxVTRydE1IOGpGMCtVWXgzVDFYSjVFCmp1blp2aFN5eHg0dlUvNFU1dVEzQnk3cFVrYmtiZlFWK2x3dlBjaHQyVXlZK0E0MkFKSWlSMjdvT1h1Wk9jNTQKT3liMDNSNWNUR1NuWjJBN0N5VDNubStRak5rV2hXNEpyUE1MWTFJK293dGtRVlF2YW10bnlZNnFEUUtCZ0ZYWgpLT0IzSmxjSzhZa0R5Nm5WeUhkZUhvbGNHaU55YjkxTlN6MUUrWHZIYklnWEdZdmRtUFhoaXRyRGFNQzR1RjBGCjJoRjZQMTlxWnpDOUZqZnY3WGRrSTlrYXF5eENQY0dwUTVBcHhZdDhtUGV1bEJWemFqR1NFMHVsNFVhSWxDNXgKL2VQQnVQVjVvZjJXVFhST0Q5eHhZT0pWd0QvZGprekw1ZFlkMW1UUEFvR0JBTWVwY3diVEphZ3BoZk5zOHY0WAprclNoWXplbVpxY2EwQzRFc2QwNGYwTUxHSlVNS3Zpck4zN0g1OUFjT2IvNWtZcTU5WFRwRmJPWjdmYlpHdDZnCkxnM2hWSHRacElOVGJ5Ni9GOTBUZ09Za3RxUnhNVmc3UFBxbjFqdEFiVU15eVpVZFdHcFNNMmI0bXQ5dGhlUDEKblhMR09NWUtnS2JYbjZXWWN5K2U5eW9ICi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KCg==", - "initiative_context": "hyperswitch-sdk-test.netlify.app", - "merchant_identifier": "merchant.com.adyen.san" - }, - "payment_request_data": { - "label": "applepay pvt.ltd", - "supported_networks": ["visa", "masterCard", "amex", "discover"], - "merchant_capabilities": ["supports3DS"] - } } } } diff --git a/postman/collection-dir/hyperswitch/Hackathon/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/hyperswitch/Hackathon/QuickStart/Payment Connector - Create/request.json index 7eab20001651..7e3f37901512 100644 --- a/postman/collection-dir/hyperswitch/Hackathon/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/hyperswitch/Hackathon/QuickStart/Payment Connector - Create/request.json @@ -286,7 +286,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { diff --git a/postman/collection-dir/hyperswitch/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/hyperswitch/QuickStart/Payment Connector - Create/request.json index 7eab20001651..7e3f37901512 100644 --- a/postman/collection-dir/hyperswitch/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/hyperswitch/QuickStart/Payment Connector - Create/request.json @@ -286,7 +286,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { diff --git a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json index 291a5bf84b51..69e35cff273c 100644 --- a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -308,7 +308,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json index 9d742acdf3c6..3a281d4004a9 100644 --- a/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json @@ -291,7 +291,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json index ef812ef5d172..766fd2508190 100644 --- a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json @@ -288,7 +288,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { diff --git a/postman/collection-dir/wise/Flow Testcases/QuickStart/Payout Connector - Create/request.json b/postman/collection-dir/wise/Flow Testcases/QuickStart/Payout Connector - Create/request.json index 817114b426a7..6b4757c18c39 100644 --- a/postman/collection-dir/wise/Flow Testcases/QuickStart/Payout Connector - Create/request.json +++ b/postman/collection-dir/wise/Flow Testcases/QuickStart/Payout Connector - Create/request.json @@ -305,7 +305,7 @@ "certificate": "{{certificate}}", "display_name": "applepay", "certificate_keys": "{{certificate_keys}}", - "initiative_context": "hyperswitch-sdk-test.netlify.app", + "initiative_context": "sdk-test-app.netlify.app", "merchant_identifier": "merchant.com.stripe.sang" }, "payment_request_data": { From 7885b2a213f474da3e018ddeb56bc6e407c48471 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:18:36 +0000 Subject: [PATCH 40/48] chore(postman): update Postman collection files --- postman/collection-json/adyen_uk.postman_collection.json | 2 +- postman/collection-json/hyperswitch.postman_collection.json | 4 ++-- postman/collection-json/stripe.postman_collection.json | 6 +++--- postman/collection-json/wise.postman_collection.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/postman/collection-json/adyen_uk.postman_collection.json b/postman/collection-json/adyen_uk.postman_collection.json index 26963aa8abbe..91a03afa47c8 100644 --- a/postman/collection-json/adyen_uk.postman_collection.json +++ b/postman/collection-json/adyen_uk.postman_collection.json @@ -472,7 +472,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"adyen\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\",\"api_secret\":\"{{connector_api_secret}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"gift_card\",\"payment_method_types\":[{\"payment_method_type\":\"givex\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdKakNDQlE2Z0F3SUJBZ0lRRENzRmFrVkNLU01uc2JacTc1YTI0ekFOQmdrcWhraUc5dzBCQVFzRkFEQjEKTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRApaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFTE1Ba0dBMVVFQ3d3Q1J6TXhFekFSQmdOVkJBb01Da0Z3CmNHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeU1USXdPREE1TVRJeE1Wb1hEVEkxTURFd05qQTUKTVRJeE1Gb3dnYWd4SmpBa0Jnb0praWFKay9Jc1pBRUJEQlp0WlhKamFHRnVkQzVqYjIwdVlXUjVaVzR1YzJGdQpNVHN3T1FZRFZRUUREREpCY0hCc1pTQlFZWGtnVFdWeVkyaGhiblFnU1dSbGJuUnBkSGs2YldWeVkyaGhiblF1ClkyOXRMbUZrZVdWdUxuTmhiakVUTUJFR0ExVUVDd3dLV1UwNVZUY3pXakpLVFRFc01Db0dBMVVFQ2d3alNsVlQKVUVGWklGUkZRMGhPVDB4UFIwbEZVeUJRVWtsV1FWUkZJRXhKVFVsVVJVUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQgpBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRDhIUy81ZmJZNVJLaElYU3pySEpoeTVrNmY0YUdMaEltYklLaXFYRUlUCnVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYzV5eGE0cHg5eHlmQlVIejhzeU9pMjdYNVZaVG8KTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVZlhQWHBjdjFqVVRyRCtlc1RJTFZUb1FUTmhDcwplQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWNaWC9vWTB1R040VWd4c0JYWHdZM0dKbTFSQ3B1CjM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSEhNMGpEQ2lncVU1RktwL1pBbHdzYmg1WVZOU00KWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nV1Y0Z0hUNmhBZ01CQUFHamdnSjhNSUlDZURBTQpCZ05WSFJNQkFmOEVBakFBTUI4R0ExVWRJd1FZTUJhQUZBbit3QldRK2E5a0NwSVN1U1lvWXd5WDdLZXlNSEFHCkNDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnYKYlM5M2QyUnlaek11WkdWeU1ERUdDQ3NHQVFVRkJ6QUJoaVZvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdgpiMk56Y0RBekxYZDNaSEpuTXpBNU1JSUJMUVlEVlIwZ0JJSUJKRENDQVNBd2dnRWNCZ2txaGtpRzkyTmtCUUV3CmdnRU5NSUhSQmdnckJnRUZCUWNDQWpDQnhBeUJ3VkpsYkdsaGJtTmxJRzl1SUhSb2FYTWdRMlZ5ZEdsbWFXTmgKZEdVZ1lua2dZVzU1SUhCaGNuUjVJRzkwYUdWeUlIUm9ZVzRnUVhCd2JHVWdhWE1nY0hKdmFHbGlhWFJsWkM0ZwpVbVZtWlhJZ2RHOGdkR2hsSUdGd2NHeHBZMkZpYkdVZ2MzUmhibVJoY21RZ2RHVnliWE1nWVc1a0lHTnZibVJwCmRHbHZibk1nYjJZZ2RYTmxMQ0JqWlhKMGFXWnBZMkYwWlNCd2IyeHBZM2tnWVc1a0lHTmxjblJwWm1sallYUnAKYjI0Z2NISmhZM1JwWTJVZ2MzUmhkR1Z0Wlc1MGN5NHdOd1lJS3dZQkJRVUhBZ0VXSzJoMGRIQnpPaTh2ZDNkMwpMbUZ3Y0d4bExtTnZiUzlqWlhKMGFXWnBZMkYwWldGMWRHaHZjbWwwZVM4d0V3WURWUjBsQkF3d0NnWUlLd1lCCkJRVUhBd0l3SFFZRFZSME9CQllFRk5RSysxcUNHbDRTQ1p6SzFSUmpnb05nM0hmdk1BNEdBMVVkRHdFQi93UUUKQXdJSGdEQlBCZ2txaGtpRzkyTmtCaUFFUWd4QVFVUkNRemxDTmtGRE5USkVRems0TnpCRk5qYzJNVFpFUkRJdwpPVUkwTWtReE1UVXlSVVpFTURVeFFVRXhRekV6T0ROR00wUkROa1V5TkVNelFqRkVSVEFQQmdrcWhraUc5Mk5rCkJpNEVBZ1VBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBSFR6NTU2RUs5VVp6R0RVd2cvcmFibmYrUXFSYkgKcllVS0ZNcWQwUDhFTHZGMmYrTzN0ZXlDWHNBckF4TmVMY2hRSGVTNUFJOHd2azdMQ0xLUmJCdWJQQy9NVmtBKwpCZ3h5STg2ejJOVUNDWml4QVM1d2JFQWJYOStVMFp2RHp5Y01BbUNrdVVHZjNwWXR5TDNDaEplSGRvOEwwdmdvCnJQWElUSzc4ZjQzenNzYjBTNE5xbTE0eS9LNCs1ZkUzcFUxdEJqME5tUmdKUVJLRnB6MENpV2RPd1BRTk5BYUMKYUNNU2NiYXdwUTBjWEhaZDJWVjNtem4xdUlITEVtaU5GTWVxeEprbjZhUXFFQnNndDUzaUFxcmZMNjEzWStScAppd0tENXVmeU0wYzBweTYyZmkvWEwwS2c4ajEwWU1VdWJpd2dHajAzZThQWTB6bWUvcGZCZ3p6VQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\",\"display_name\":\"applepay\",\"certificate_keys\":\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRDhIUy81ZmJZNVJLaEkKWFN6ckhKaHk1azZmNGFHTGhJbWJJS2lxWEVJVHVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYwo1eXhhNHB4OXh5ZkJVSHo4c3lPaTI3WDVWWlRvTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVCmZYUFhwY3YxalVUckQrZXNUSUxWVG9RVE5oQ3NlQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWMKWlgvb1kwdUdONFVneHNCWFh3WTNHSm0xUkNwdTM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSApITTBqRENpZ3FVNUZLcC9aQWx3c2JoNVlWTlNNWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nCldWNGdIVDZoQWdNQkFBRUNnZ0VBZFNaRzVhTFJxdmpKVFo3bVFYWHZMT3p4dWY5SlpxQTJwUHZmQkJLTXJjZC8KL2RDZXpGSGRhZ1VvWXNUQjRXekluaVVjL2Z3bDJTUzJyREFMZjB2dnRjNTJIYkQ5OHhwMnc3VmVjTGFnMCtuWAo2dUJaSEZCS3FWNU1LZ1l6YUpVMTdqaDM2VEV3dTFnbmdlZnRqVlpBV1NERTFvbDBlSzZ3Mk5kOExjVWdxRkxUCjVHYUlBV01nd0NKL3pzQmwydUV1Y0Q4S21WL1Z2MkVCQVJLWGZtci92UU1NelZrNkhhalprVGZqbWY2cWFVQVMKQWlFblROcHBic2ZrdTk2VGdIa2owWm10VWc0SFkzSU9qWFlpaGJsSjJzQ1JjS3p6cXkxa3B3WlpHcHo1NXEzbgphSXEwenJ3RjlpTUZubEhCa04yK3FjSnhzcDNTalhRdFRLTTY4WHRrVlFLQmdRRC8wemtCVlExR2Q1U0Mzb2czCnM3QWRCZ243dnVMdUZHZFFZY3c0OUppRGw1a1BZRXlKdGQvTVpNOEpFdk1nbVVTeUZmczNZcGtmQ2VGbUp0S3QKMnNNNEdCRWxqTVBQNjI3Q0QrV3c4L3JpWmlOZEg3OUhPRjRneTRGbjBycDNqanlLSWF1OHJISDQwRUUvSkVyOQpxWFQ1SGdWMmJQOGhMcW5sSjFmSDhpY2Zkd0tCZ1FEOFNWQ3ZDV2txQkh2SzE5ZDVTNlArdm5hcXhnTWo0U0srCnJ6L1I1c3pTaW5lS045VEhyeVkxYUZJbVFJZjJYOUdaQXBocUhrckxtQ3BIcURHOWQ3WDVQdUxxQzhmc09kVTYKRzhWaFRXeXdaSVNjdGRSYkk5S2xKUFk2V2ZDQjk0ODNVaDJGbW1xV2JuNWcwZUJxZWZzamVVdEtHekNRaGJDYworR1dBREVRSXB3S0JnUURmaWYvN3pBZm5sVUh1QU9saVV0OEczV29IMGtxVTRydE1IOGpGMCtVWXgzVDFYSjVFCmp1blp2aFN5eHg0dlUvNFU1dVEzQnk3cFVrYmtiZlFWK2x3dlBjaHQyVXlZK0E0MkFKSWlSMjdvT1h1Wk9jNTQKT3liMDNSNWNUR1NuWjJBN0N5VDNubStRak5rV2hXNEpyUE1MWTFJK293dGtRVlF2YW10bnlZNnFEUUtCZ0ZYWgpLT0IzSmxjSzhZa0R5Nm5WeUhkZUhvbGNHaU55YjkxTlN6MUUrWHZIYklnWEdZdmRtUFhoaXRyRGFNQzR1RjBGCjJoRjZQMTlxWnpDOUZqZnY3WGRrSTlrYXF5eENQY0dwUTVBcHhZdDhtUGV1bEJWemFqR1NFMHVsNFVhSWxDNXgKL2VQQnVQVjVvZjJXVFhST0Q5eHhZT0pWd0QvZGprekw1ZFlkMW1UUEFvR0JBTWVwY3diVEphZ3BoZk5zOHY0WAprclNoWXplbVpxY2EwQzRFc2QwNGYwTUxHSlVNS3Zpck4zN0g1OUFjT2IvNWtZcTU5WFRwRmJPWjdmYlpHdDZnCkxnM2hWSHRacElOVGJ5Ni9GOTBUZ09Za3RxUnhNVmc3UFBxbjFqdEFiVU15eVpVZFdHcFNNMmI0bXQ5dGhlUDEKblhMR09NWUtnS2JYbjZXWWN5K2U5eW9ICi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KCg==\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.adyen.san\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"adyen\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\",\"api_secret\":\"{{connector_api_secret}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"gift_card\",\"payment_method_types\":[{\"payment_method_type\":\"givex\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", diff --git a/postman/collection-json/hyperswitch.postman_collection.json b/postman/collection-json/hyperswitch.postman_collection.json index ab710ca4316a..0acf2ee2b3fc 100644 --- a/postman/collection-json/hyperswitch.postman_collection.json +++ b/postman/collection-json/hyperswitch.postman_collection.json @@ -1915,7 +1915,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -4798,7 +4798,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index e158ccd1a5eb..0638ff734c4a 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -2079,7 +2079,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}_invalid_values\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}_invalid_values\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -2349,7 +2349,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors/:connector_id", @@ -5664,7 +5664,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", diff --git a/postman/collection-json/wise.postman_collection.json b/postman/collection-json/wise.postman_collection.json index dc4d9395d3ac..410f066ff6fb 100644 --- a/postman/collection-json/wise.postman_collection.json +++ b/postman/collection-json/wise.postman_collection.json @@ -424,7 +424,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"payout_processor\",\"connector_name\":\"wise\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"payout_processor\",\"connector_name\":\"wise\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"sdk-test-app.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", From cc7e33a5751d97b44c7aba561c974f529ce8824a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 04:03:19 +0000 Subject: [PATCH 41/48] chore(version): 2024.01.24.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 800218b8a437..af517a6a1153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.24.0 + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`7885b2a`](https://github.com/juspay/hyperswitch/commit/7885b2a213f474da3e018ddeb56bc6e407c48471)) + +**Full Changelog:** [`2024.01.23.0...2024.01.24.0`](https://github.com/juspay/hyperswitch/compare/2024.01.23.0...2024.01.24.0) + +- - - + ## 2024.01.23.0 ### Features From 629d546aa7c774e86d609abec3b3ab5cf0d100a7 Mon Sep 17 00:00:00 2001 From: Nishant Joshi Date: Wed, 24 Jan 2024 14:06:52 +0530 Subject: [PATCH 42/48] feat(hashicorp): implement hashicorp secrets manager solution (#3297) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .typos.toml | 1 + Cargo.lock | 192 ++++++++++++++-- Makefile | 9 +- crates/drainer/Cargo.toml | 1 + crates/drainer/src/connection.rs | 15 +- crates/drainer/src/services.rs | 10 + crates/drainer/src/settings.rs | 4 + crates/external_services/Cargo.toml | 3 + .../external_services/src/hashicorp_vault.rs | 215 ++++++++++++++++++ .../src/hashicorp_vault/decrypt.rs | 50 ++++ crates/external_services/src/kms.rs | 38 ++++ crates/external_services/src/lib.rs | 3 + crates/router/Cargo.toml | 1 + crates/router/src/configs.rs | 2 + crates/router/src/configs/hc_vault.rs | 134 +++++++++++ crates/router/src/configs/settings.rs | 4 + crates/router/src/core/api_keys.rs | 36 ++- crates/router/src/core/currency.rs | 4 + crates/router/src/core/payments.rs | 2 +- .../src/core/payments/flows/session_flow.rs | 103 +++++++-- crates/router/src/core/payments/helpers.rs | 60 ++++- crates/router/src/core/pm_auth.rs | 27 ++- crates/router/src/routes/api_keys.rs | 12 + crates/router/src/routes/app.rs | 48 +++- crates/router/src/services.rs | 76 ++++--- crates/router/src/services/authentication.rs | 27 ++- crates/router/src/utils/currency.rs | 95 +++++++- crates/router_env/Cargo.toml | 6 +- 28 files changed, 1094 insertions(+), 84 deletions(-) create mode 100644 crates/external_services/src/hashicorp_vault.rs create mode 100644 crates/external_services/src/hashicorp_vault/decrypt.rs create mode 100644 crates/router/src/configs/hc_vault.rs diff --git a/.typos.toml b/.typos.toml index 4ce21526604b..40acb1305892 100644 --- a/.typos.toml +++ b/.typos.toml @@ -36,6 +36,7 @@ ba = "ba" # ignore minor commit conversions ede = "ede" # ignore minor commit conversions daa = "daa" # Commit id afe = "afe" # Commit id +Hashi = "Hashi" # HashiCorp [files] extend-exclude = [ diff --git a/Cargo.lock b/Cargo.lock index 2cd3cd65c318..5623fd9f729f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,7 +114,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a0a77f836d869f700e5b47ac7c3c8b9c8bc82e4aec861954c6198abee3ebd4d" dependencies = [ - "darling", + "darling 0.20.3", "parse-size", "proc-macro2", "quote", @@ -189,10 +189,10 @@ dependencies = [ "rustls 0.21.7", "rustls-webpki", "tokio 1.35.1", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-util", "tracing", - "webpki-roots", + "webpki-roots 0.22.6", ] [[package]] @@ -954,7 +954,7 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.2", "lazy_static", "pin-project-lite", "rustls 0.20.9", @@ -1990,14 +1990,38 @@ dependencies = [ "thiserror", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + [[package]] name = "darling" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.3", + "darling_macro 0.20.3", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", ] [[package]] @@ -2014,13 +2038,24 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core", + "darling_core 0.20.3", "quote", "syn 2.0.48", ] @@ -2104,6 +2139,37 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_deref" version = "1.1.1" @@ -2421,6 +2487,7 @@ dependencies = [ "common_utils", "dyn-clone", "error-stack", + "hex", "hyper", "hyper-proxy", "masking", @@ -2429,6 +2496,7 @@ dependencies = [ "serde", "thiserror", "tokio 1.35.1", + "vaultrs", ] [[package]] @@ -2453,7 +2521,7 @@ dependencies = [ "futures-util", "http", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.2", "mime", "serde", "serde_json", @@ -3098,7 +3166,21 @@ dependencies = [ "rustls 0.20.9", "rustls-native-certs", "tokio 1.35.1", - "tokio-rustls", + "tokio-rustls 0.23.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls 0.21.7", + "tokio 1.35.1", + "tokio-rustls 0.24.1", ] [[package]] @@ -4945,6 +5027,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -4955,18 +5038,22 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.7", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio 1.35.1", "tokio-native-tls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.3", "winreg", ] @@ -5302,6 +5389,40 @@ dependencies = [ "nom", ] +[[package]] +name = "rustify" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706" +dependencies = [ + "anyhow", + "async-trait", + "bytes 1.5.0", + "http", + "reqwest", + "rustify_derive", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "rustify_derive" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "serde_urlencoded", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "rustix" version = "0.38.28" @@ -5670,7 +5791,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ - "darling", + "darling 0.20.3", "proc-macro2", "quote", "syn 2.0.48", @@ -6561,6 +6682,16 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio 1.35.1", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -6794,11 +6925,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -6833,20 +6963,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -7155,6 +7285,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vaultrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28084ac780b443e7f3514df984a2933bd3ab39e71914d951cdf8e4d298a7c9bc" +dependencies = [ + "async-trait", + "bytes 1.5.0", + "derive_builder", + "http", + "reqwest", + "rustify", + "rustify_derive", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -7346,6 +7496,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "weezl" version = "0.1.7" diff --git a/Makefile b/Makefile index 780d5a993c92..a39fc4c22673 100644 --- a/Makefile +++ b/Makefile @@ -34,11 +34,18 @@ ROOT_DIR := $(realpath $(ROOT_DIR_WITH_SLASH)) release +# Check a local package and all of its dependencies for errors +# +# Usage : +# make check +check: + cargo check + + # Compile application for running on local machine # # Usage : # make build - build : cargo build diff --git a/crates/drainer/Cargo.toml b/crates/drainer/Cargo.toml index f26c31f0e72c..67169a151044 100644 --- a/crates/drainer/Cargo.toml +++ b/crates/drainer/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [features] release = ["kms", "vergen"] kms = ["external_services/kms"] +hashicorp-vault = ["external_services/hashicorp-vault"] vergen = ["router_env/vergen"] [dependencies] diff --git a/crates/drainer/src/connection.rs b/crates/drainer/src/connection.rs index 7b273244cbce..6af0a9782232 100644 --- a/crates/drainer/src/connection.rs +++ b/crates/drainer/src/connection.rs @@ -1,5 +1,7 @@ use bb8::PooledConnection; use diesel::PgConnection; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::{self, decrypt::VaultFetch, Kv2}; #[cfg(feature = "kms")] use external_services::kms::{self, decrypt::KmsDecrypt}; #[cfg(not(feature = "kms"))] @@ -27,16 +29,23 @@ pub async fn diesel_make_pg_pool( database: &Database, _test_transaction: bool, #[cfg(feature = "kms")] kms_client: &'static kms::KmsClient, + #[cfg(feature = "hashicorp-vault")] hashicorp_client: &'static hashicorp_vault::HashiCorpVault, ) -> PgPool { + let password = database.password.clone(); + #[cfg(feature = "hashicorp-vault")] + let password = password + .fetch_inner::(hashicorp_client) + .await + .expect("Failed while fetching db password"); + #[cfg(feature = "kms")] - let password = database - .password + let password = password .decrypt_inner(kms_client) .await .expect("Failed to decrypt password"); #[cfg(not(feature = "kms"))] - let password = &database.password.peek(); + let password = &password.peek(); let database_url = format!( "postgres://{}:{}@{}:{}/{}", diff --git a/crates/drainer/src/services.rs b/crates/drainer/src/services.rs index 481fcc07221c..4393ebb9dc97 100644 --- a/crates/drainer/src/services.rs +++ b/crates/drainer/src/services.rs @@ -17,6 +17,11 @@ pub struct StoreConfig { } impl Store { + /// # Panics + /// + /// Panics if there is a failure while obtaining the HashiCorp client using the provided configuration. + /// This panic indicates a critical failure in setting up external services, and the application cannot proceed without a valid HashiCorp client. + /// pub async fn new(config: &crate::settings::Settings, test_transaction: bool) -> Self { Self { master_pool: diesel_make_pg_pool( @@ -24,6 +29,11 @@ impl Store { test_transaction, #[cfg(feature = "kms")] external_services::kms::get_kms_client(&config.kms).await, + #[cfg(feature = "hashicorp-vault")] + #[allow(clippy::expect_used)] + external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault) + .await + .expect("Failed while getting hashicorp client"), ) .await, redis_conn: Arc::new(crate::connection::redis_connection(config).await), diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index 8101abf5028e..5b80ee375f54 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; use common_utils::ext_traits::ConfigExt; use config::{Environment, File}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault; #[cfg(feature = "kms")] use external_services::kms; use redis_interface as redis; @@ -34,6 +36,8 @@ pub struct Settings { pub drainer: DrainerSettings, #[cfg(feature = "kms")] pub kms: kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + pub hc_vault: hashicorp_vault::HashiCorpVaultConfig, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index 90e5df538055..6552b57b0e54 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [features] kms = ["dep:aws-config", "dep:aws-sdk-kms"] email = ["dep:aws-config"] +hashicorp-vault = [ "dep:vaultrs" ] [dependencies] async-trait = "0.1.68" @@ -27,6 +28,8 @@ thiserror = "1.0.40" tokio = "1.35.1" hyper-proxy = "0.9.1" hyper = "0.14.26" +vaultrs = { version = "0.7.0", optional = true } +hex = "0.4.3" # First party crates common_utils = { version = "0.1.0", path = "../common_utils" } diff --git a/crates/external_services/src/hashicorp_vault.rs b/crates/external_services/src/hashicorp_vault.rs new file mode 100644 index 000000000000..e31c8f01392e --- /dev/null +++ b/crates/external_services/src/hashicorp_vault.rs @@ -0,0 +1,215 @@ +//! Interactions with the HashiCorp Vault + +use std::{collections::HashMap, future::Future, pin::Pin}; + +use error_stack::{Report, ResultExt}; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; + +/// Utilities for supporting decryption of data +pub mod decrypt; + +static HC_CLIENT: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); + +#[allow(missing_debug_implementations)] +/// A struct representing a connection to HashiCorp Vault. +pub struct HashiCorpVault { + /// The underlying client used for interacting with HashiCorp Vault. + client: VaultClient, +} + +/// Configuration for connecting to HashiCorp Vault. +#[derive(Clone, Debug, Default, serde::Deserialize)] +#[serde(default)] +pub struct HashiCorpVaultConfig { + /// The URL of the HashiCorp Vault server. + pub url: String, + /// The authentication token used to access HashiCorp Vault. + pub token: String, +} + +/// Asynchronously retrieves a HashiCorp Vault client based on the provided configuration. +/// +/// # Parameters +/// +/// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details. +pub async fn get_hashicorp_client( + config: &HashiCorpVaultConfig, +) -> error_stack::Result<&'static HashiCorpVault, HashiCorpError> { + HC_CLIENT + .get_or_try_init(|| async { HashiCorpVault::new(config) }) + .await +} + +/// A trait defining an engine for interacting with HashiCorp Vault. +pub trait Engine: Sized { + /// The associated type representing the return type of the engine's operations. + type ReturnType<'b, T> + where + T: 'b, + Self: 'b; + /// Reads data from HashiCorp Vault at the specified location. + /// + /// # Parameters + /// + /// - `client`: A reference to the HashiCorpVault client. + /// - `location`: The location in HashiCorp Vault to read data from. + /// + /// # Returns + /// + /// A future representing the result of the read operation. + fn read(client: &HashiCorpVault, location: String) -> Self::ReturnType<'_, String>; +} + +/// An implementation of the `Engine` trait for the Key-Value version 2 (Kv2) engine. +#[derive(Debug)] +pub enum Kv2 {} + +impl Engine for Kv2 { + type ReturnType<'b, T: 'b> = + Pin> + Send + 'b>>; + fn read(client: &HashiCorpVault, location: String) -> Self::ReturnType<'_, String> { + Box::pin(async move { + let mut split = location.split(':'); + let mount = split.next().ok_or(HashiCorpError::IncompleteData)?; + let path = split.next().ok_or(HashiCorpError::IncompleteData)?; + let key = split.next().unwrap_or("value"); + + let mut output = + vaultrs::kv2::read::>(&client.client, mount, path) + .await + .map_err(Into::>::into) + .change_context(HashiCorpError::FetchFailed)?; + + Ok(output.remove(key).ok_or(HashiCorpError::ParseError)?) + }) + } +} + +impl HashiCorpVault { + /// Creates a new instance of HashiCorpVault based on the provided configuration. + /// + /// # Parameters + /// + /// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details. + /// + pub fn new(config: &HashiCorpVaultConfig) -> error_stack::Result { + VaultClient::new( + VaultClientSettingsBuilder::default() + .address(&config.url) + .token(&config.token) + .build() + .map_err(Into::>::into) + .change_context(HashiCorpError::ClientCreationFailed) + .attach_printable("Failed while building vault settings")?, + ) + .map_err(Into::>::into) + .change_context(HashiCorpError::ClientCreationFailed) + .map(|client| Self { client }) + } + + /// Asynchronously fetches data from HashiCorp Vault using the specified engine. + /// + /// # Parameters + /// + /// - `data`: A String representing the location or identifier of the data in HashiCorp Vault. + /// + /// # Type Parameters + /// + /// - `En`: The engine type that implements the `Engine` trait. + /// - `I`: The type that can be constructed from the retrieved encoded data. + /// + pub async fn fetch(&self, data: String) -> error_stack::Result + where + for<'a> En: Engine< + ReturnType<'a, String> = Pin< + Box< + dyn Future> + + Send + + 'a, + >, + >, + > + 'a, + I: FromEncoded, + { + let output = En::read(self, data).await?; + I::from_encoded(output).ok_or(error_stack::report!(HashiCorpError::HexDecodingFailed)) + } +} + +/// A trait for types that can be constructed from encoded data in the form of a String. +pub trait FromEncoded: Sized { + /// Constructs an instance of the type from the provided encoded input. + /// + /// # Parameters + /// + /// - `input`: A String containing the encoded data. + /// + /// # Returns + /// + /// An `Option` representing the constructed instance if successful, or `None` otherwise. + /// + /// # Example + /// + /// ```rust + /// # use your_module::{FromEncoded, masking::Secret, Vec}; + /// let secret_instance = Secret::::from_encoded("encoded_secret_string".to_string()); + /// let vec_instance = Vec::::from_encoded("68656c6c6f".to_string()); + /// ``` + fn from_encoded(input: String) -> Option; +} + +impl FromEncoded for masking::Secret { + fn from_encoded(input: String) -> Option { + Some(input.into()) + } +} + +impl FromEncoded for Vec { + fn from_encoded(input: String) -> Option { + hex::decode(input).ok() + } +} + +/// An enumeration representing various errors that can occur in interactions with HashiCorp Vault. +#[derive(Debug, thiserror::Error)] +pub enum HashiCorpError { + /// Failed while creating hashicorp client + #[error("Failed while creating a new client")] + ClientCreationFailed, + + /// Failed while building configurations for hashicorp client + #[error("Failed while building configuration")] + ConfigurationBuildFailed, + + /// Failed while decoding data to hex format + #[error("Failed while decoding hex data")] + HexDecodingFailed, + + /// An error occurred when base64 decoding input data. + #[error("Failed to base64 decode input data")] + Base64DecodingFailed, + + /// An error occurred when KMS decrypting input data. + #[error("Failed to KMS decrypt input data")] + DecryptionFailed, + + /// The KMS decrypted output does not include a plaintext output. + #[error("Missing plaintext KMS decryption output")] + MissingPlaintextDecryptionOutput, + + /// An error occurred UTF-8 decoding KMS decrypted output. + #[error("Failed to UTF-8 decode decryption output")] + Utf8DecodingFailed, + + /// Incomplete data provided to fetch data from hasicorp + #[error("Provided information about the value is incomplete")] + IncompleteData, + + /// Failed while fetching data from vault + #[error("Failed while fetching data from the server")] + FetchFailed, + + /// Failed while parsing received data + #[error("Failed while parsing the response")] + ParseError, +} diff --git a/crates/external_services/src/hashicorp_vault/decrypt.rs b/crates/external_services/src/hashicorp_vault/decrypt.rs new file mode 100644 index 000000000000..1bc1b6ffa16e --- /dev/null +++ b/crates/external_services/src/hashicorp_vault/decrypt.rs @@ -0,0 +1,50 @@ +use std::{future::Future, pin::Pin}; + +use masking::ExposeInterface; + +/// A trait for types that can be asynchronously fetched and decrypted from HashiCorp Vault. +#[async_trait::async_trait] +pub trait VaultFetch: Sized { + /// Asynchronously decrypts the inner content of the type. + /// + /// # Returns + /// + /// An `Result` representing the decrypted instance if successful, + /// or an `super::HashiCorpError` with details about the encountered error. + /// + async fn fetch_inner( + self, + client: &super::HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: super::Engine< + ReturnType<'a, String> = Pin< + Box< + dyn Future> + + Send + + 'a, + >, + >, + > + 'a; +} + +#[async_trait::async_trait] +impl VaultFetch for masking::Secret { + async fn fetch_inner( + self, + client: &super::HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: super::Engine< + ReturnType<'a, String> = Pin< + Box< + dyn Future> + + Send + + 'a, + >, + >, + > + 'a, + { + client.fetch::(self.expose()).await + } +} diff --git a/crates/external_services/src/kms.rs b/crates/external_services/src/kms.rs index 04a58e4b23f4..740bca4d821b 100644 --- a/crates/external_services/src/kms.rs +++ b/crates/external_services/src/kms.rs @@ -190,6 +190,44 @@ impl KmsConfig { #[serde(transparent)] pub struct KmsValue(Secret); +impl From for KmsValue { + fn from(value: String) -> Self { + Self(Secret::new(value)) + } +} + +impl From> for KmsValue { + fn from(value: Secret) -> Self { + Self(value) + } +} + +#[cfg(feature = "hashicorp-vault")] +#[async_trait::async_trait] +impl super::hashicorp_vault::decrypt::VaultFetch for KmsValue { + async fn fetch_inner( + self, + client: &super::hashicorp_vault::HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: super::hashicorp_vault::Engine< + ReturnType<'a, String> = std::pin::Pin< + Box< + dyn std::future::Future< + Output = error_stack::Result< + String, + super::hashicorp_vault::HashiCorpError, + >, + > + Send + + 'a, + >, + >, + > + 'a, + { + self.0.fetch_inner::(client).await.map(KmsValue) + } +} + impl common_utils::ext_traits::ConfigExt for KmsValue { fn is_empty_after_trim(&self) -> bool { self.0.peek().is_empty_after_trim() diff --git a/crates/external_services/src/lib.rs b/crates/external_services/src/lib.rs index ccf1db47a3ae..9bf4916eec33 100644 --- a/crates/external_services/src/lib.rs +++ b/crates/external_services/src/lib.rs @@ -9,6 +9,9 @@ pub mod email; #[cfg(feature = "kms")] pub mod kms; +#[cfg(feature = "hashicorp-vault")] +pub mod hashicorp_vault; + /// Crate specific constants #[cfg(feature = "kms")] pub mod consts { diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index acc6b70a2edd..ef6ea41d524a 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"] aws_s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] +hashicorp-vault = ["external_services/hashicorp-vault"] email = ["external_services/email", "dep:aws-config", "olap"] frm = [] stripe = ["dep:serde_qs"] diff --git a/crates/router/src/configs.rs b/crates/router/src/configs.rs index bb8f61646f26..5cb1df0644ee 100644 --- a/crates/router/src/configs.rs +++ b/crates/router/src/configs.rs @@ -1,4 +1,6 @@ mod defaults; +#[cfg(feature = "hashicorp-vault")] +pub mod hc_vault; #[cfg(feature = "kms")] pub mod kms; pub mod settings; diff --git a/crates/router/src/configs/hc_vault.rs b/crates/router/src/configs/hc_vault.rs new file mode 100644 index 000000000000..f20d8e79ed89 --- /dev/null +++ b/crates/router/src/configs/hc_vault.rs @@ -0,0 +1,134 @@ +use external_services::hashicorp_vault::{ + decrypt::VaultFetch, Engine, HashiCorpError, HashiCorpVault, +}; +use masking::ExposeInterface; + +use crate::configs::settings; + +#[async_trait::async_trait] +impl VaultFetch for settings::Jwekey { + async fn fetch_inner( + mut self, + client: &HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: Engine< + ReturnType<'a, String> = std::pin::Pin< + Box< + dyn std::future::Future< + Output = error_stack::Result, + > + Send + + 'a, + >, + >, + > + 'a, + { + ( + self.vault_encryption_key, + self.rust_locker_encryption_key, + self.vault_private_key, + self.tunnel_private_key, + ) = ( + masking::Secret::new(self.vault_encryption_key) + .fetch_inner::(client) + .await? + .expose(), + masking::Secret::new(self.rust_locker_encryption_key) + .fetch_inner::(client) + .await? + .expose(), + masking::Secret::new(self.vault_private_key) + .fetch_inner::(client) + .await? + .expose(), + masking::Secret::new(self.tunnel_private_key) + .fetch_inner::(client) + .await? + .expose(), + ); + Ok(self) + } +} + +#[async_trait::async_trait] +impl VaultFetch for settings::Database { + async fn fetch_inner( + mut self, + client: &HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: Engine< + ReturnType<'a, String> = std::pin::Pin< + Box< + dyn std::future::Future< + Output = error_stack::Result, + > + Send + + 'a, + >, + >, + > + 'a, + { + Ok(Self { + host: self.host, + port: self.port, + dbname: self.dbname, + username: self.username, + password: self.password.fetch_inner::(client).await?, + pool_size: self.pool_size, + connection_timeout: self.connection_timeout, + queue_strategy: self.queue_strategy, + min_idle: self.min_idle, + max_lifetime: self.max_lifetime, + }) + } +} + +#[cfg(feature = "olap")] +#[async_trait::async_trait] +impl VaultFetch for settings::PayPalOnboarding { + async fn fetch_inner( + mut self, + client: &HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: Engine< + ReturnType<'a, String> = std::pin::Pin< + Box< + dyn std::future::Future< + Output = error_stack::Result, + > + Send + + 'a, + >, + >, + > + 'a, + { + self.client_id = self.client_id.fetch_inner::(client).await?; + self.client_secret = self.client_secret.fetch_inner::(client).await?; + self.partner_id = self.partner_id.fetch_inner::(client).await?; + Ok(self) + } +} + +#[cfg(feature = "olap")] +#[async_trait::async_trait] +impl VaultFetch for settings::ConnectorOnboarding { + async fn fetch_inner( + mut self, + client: &HashiCorpVault, + ) -> error_stack::Result + where + for<'a> En: Engine< + ReturnType<'a, String> = std::pin::Pin< + Box< + dyn std::future::Future< + Output = error_stack::Result, + > + Send + + 'a, + >, + >, + > + 'a, + { + self.paypal = self.paypal.fetch_inner::(client).await?; + Ok(self) + } +} diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index cb4fdd70eb64..3c1d9f7d397e 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -11,6 +11,8 @@ use common_utils::ext_traits::ConfigExt; use config::{Environment, File}; #[cfg(feature = "email")] use external_services::email::EmailSettings; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault; #[cfg(feature = "kms")] use external_services::kms; use redis_interface::RedisSettings; @@ -88,6 +90,8 @@ pub struct Settings { pub api_keys: ApiKeys, #[cfg(feature = "kms")] pub kms: kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + pub hc_vault: hashicorp_vault::HashiCorpVaultConfig, #[cfg(feature = "aws_s3")] pub file_upload_config: FileUploadConfig, pub tokenization: TokenizationConfig, diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs index 78d4e801e8f2..f28d845609a1 100644 --- a/crates/router/src/core/api_keys.rs +++ b/crates/router/src/core/api_keys.rs @@ -2,8 +2,12 @@ use common_utils::date_time; #[cfg(feature = "email")] use diesel_models::{api_keys::ApiKey, enums as storage_enums}; use error_stack::{report, IntoReport, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms; +#[cfg(not(feature = "kms"))] +use masking::ExposeInterface; use masking::{PeekInterface, StrongSecret}; use router_env::{instrument, tracing}; @@ -35,19 +39,37 @@ static HASH_KEY: tokio::sync::OnceCell errors::RouterResult<&'static StrongSecret<[u8; PlaintextApiKey::HASH_KEY_LEN]>> { HASH_KEY .get_or_try_init(|| async { + let hash_key = { + #[cfg(feature = "kms")] + { + api_key_config.kms_encrypted_hash_key.clone() + } + #[cfg(not(feature = "kms"))] + { + masking::Secret::<_, masking::WithType>::new(api_key_config.hash_key.clone()) + } + }; + + #[cfg(feature = "hashicorp-vault")] + let hash_key = hash_key + .fetch_inner::(hc_client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + #[cfg(feature = "kms")] - let hash_key = api_key_config - .kms_encrypted_hash_key + let hash_key = hash_key .decrypt_inner(kms_client) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to KMS decrypt API key hashing key")?; #[cfg(not(feature = "kms"))] - let hash_key = &api_key_config.hash_key; + let hash_key = hash_key.expose(); <[u8; PlaintextApiKey::HASH_KEY_LEN]>::try_from( hex::decode(hash_key) @@ -132,6 +154,8 @@ impl PlaintextApiKey { pub async fn create_api_key( state: AppState, #[cfg(feature = "kms")] kms_client: &kms::KmsClient, + #[cfg(feature = "hashicorp-vault")] + hc_client: &external_services::hashicorp_vault::HashiCorpVault, api_key: api::CreateApiKeyRequest, merchant_id: String, ) -> RouterResponse { @@ -153,6 +177,8 @@ pub async fn create_api_key( api_key_config, #[cfg(feature = "kms")] kms_client, + #[cfg(feature = "hashicorp-vault")] + hc_client, ) .await?; let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH); @@ -565,6 +591,10 @@ mod tests { &settings.api_keys, #[cfg(feature = "kms")] external_services::kms::get_kms_client(&settings.kms).await, + #[cfg(feature = "hashicorp-vault")] + external_services::hashicorp_vault::get_hashicorp_client(&settings.hc_vault) + .await + .unwrap(), ) .await .unwrap(); diff --git a/crates/router/src/core/currency.rs b/crates/router/src/core/currency.rs index 1ea9454f00a0..41699df47a76 100644 --- a/crates/router/src/core/currency.rs +++ b/crates/router/src/core/currency.rs @@ -19,6 +19,8 @@ pub async fn retrieve_forex( state.conf.forex_api.local_fetch_retry_count, #[cfg(feature = "kms")] &state.conf.kms, + #[cfg(feature = "hashicorp-vault")] + &state.conf.hc_vault, ) .await .change_context(ApiErrorResponse::GenericNotFoundError { @@ -44,6 +46,8 @@ pub async fn convert_forex( from_currency, #[cfg(feature = "kms")] &state.conf.kms, + #[cfg(feature = "hashicorp-vault")] + &state.conf.hc_vault, )) .await .change_context(ApiErrorResponse::InternalServerError)?, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 10aa00f5963c..043863a98fa3 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -68,7 +68,7 @@ use crate::{ workflows::payment_sync, }; -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::type_complexity)] #[instrument(skip_all, fields(payment_id, merchant_id))] pub async fn payments_operation_core( state: &AppState, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index de697e02f780..099c266e04f2 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -2,8 +2,14 @@ use api_models::payments as payment_types; use async_trait::async_trait; use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, Report, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms; +#[cfg(feature = "hashicorp-vault")] +use masking::ExposeInterface; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -177,10 +183,85 @@ async fn create_applepay_session_token( payment_request_data, session_token_data, } => { + let ( + apple_pay_merchant_cert, + apple_pay_merchant_cert_key, + common_merchant_identifier, + ) = async { + #[cfg(feature = "hashicorp-vault")] + let client = external_services::hashicorp_vault::get_hashicorp_client( + &state.conf.hc_vault, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while building hashicorp client")?; + + #[cfg(feature = "hashicorp-vault")] + { + Ok::<_, Report>(( + masking::Secret::new( + state + .conf + .applepay_decrypt_keys + .apple_pay_merchant_cert + .clone(), + ) + .fetch_inner::(client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)? + .expose(), + masking::Secret::new( + state + .conf + .applepay_decrypt_keys + .apple_pay_merchant_cert_key + .clone(), + ) + .fetch_inner::(client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)? + .expose(), + masking::Secret::new( + state + .conf + .applepay_merchant_configs + .common_merchant_identifier + .clone(), + ) + .fetch_inner::(client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)? + .expose(), + )) + } + + #[cfg(not(feature = "hashicorp-vault"))] + { + Ok::<_, Report>(( + state + .conf + .applepay_decrypt_keys + .apple_pay_merchant_cert + .clone(), + state + .conf + .applepay_decrypt_keys + .apple_pay_merchant_cert_key + .clone(), + state + .conf + .applepay_merchant_configs + .common_merchant_identifier + .clone(), + )) + } + } + .await?; + #[cfg(feature = "kms")] let decrypted_apple_pay_merchant_cert = kms::get_kms_client(&state.conf.kms) .await - .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert) + .decrypt(apple_pay_merchant_cert) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Apple pay merchant certificate decryption failed")?; @@ -189,7 +270,7 @@ async fn create_applepay_session_token( let decrypted_apple_pay_merchant_cert_key = kms::get_kms_client(&state.conf.kms) .await - .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_merchant_cert_key) + .decrypt(apple_pay_merchant_cert_key) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( @@ -199,21 +280,13 @@ async fn create_applepay_session_token( #[cfg(feature = "kms")] let decrypted_merchant_identifier = kms::get_kms_client(&state.conf.kms) .await - .decrypt( - &state - .conf - .applepay_merchant_configs - .common_merchant_identifier, - ) + .decrypt(common_merchant_identifier) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Apple pay merchant identifier decryption failed")?; #[cfg(not(feature = "kms"))] - let decrypted_merchant_identifier = &state - .conf - .applepay_merchant_configs - .common_merchant_identifier; + let decrypted_merchant_identifier = common_merchant_identifier; let apple_pay_session_request = get_session_request_for_simplified_apple_pay( decrypted_merchant_identifier.to_string(), @@ -221,12 +294,10 @@ async fn create_applepay_session_token( ); #[cfg(not(feature = "kms"))] - let decrypted_apple_pay_merchant_cert = - &state.conf.applepay_decrypt_keys.apple_pay_merchant_cert; + let decrypted_apple_pay_merchant_cert = apple_pay_merchant_cert; #[cfg(not(feature = "kms"))] - let decrypted_apple_pay_merchant_cert_key = - &state.conf.applepay_decrypt_keys.apple_pay_merchant_cert_key; + let decrypted_apple_pay_merchant_cert_key = apple_pay_merchant_cert_key; ( payment_request_data, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 9d3da6c78e4a..213adc79fb01 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -13,6 +13,10 @@ use data_models::{ use diesel_models::enums; // TODO : Evaluate all the helper functions () use error_stack::{report, IntoReport, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms; use josekit::jwe; @@ -1250,6 +1254,7 @@ pub async fn get_connector_default( } #[instrument(skip_all)] +#[allow(clippy::type_complexity)] pub async fn create_customer_if_not_exist<'a, F: Clone, R, Ctx>( operation: BoxedOperation<'a, F, R, Ctx>, db: &dyn StorageInterface, @@ -3501,15 +3506,38 @@ impl ApplePayData { &self, state: &AppState, ) -> CustomResult { + let apple_pay_ppc = async { + #[cfg(feature = "hashicorp-vault")] + let client = + external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault) + .await + .change_context(errors::ApplePayDecryptionError::DecryptionFailed) + .attach_printable("Failed while creating client")?; + + #[cfg(feature = "hashicorp-vault")] + let output = + masking::Secret::new(state.conf.applepay_decrypt_keys.apple_pay_ppc.clone()) + .fetch_inner::(client) + .await + .change_context(errors::ApplePayDecryptionError::DecryptionFailed)? + .expose(); + + #[cfg(not(feature = "hashicorp-vault"))] + let output = state.conf.applepay_decrypt_keys.apple_pay_ppc.clone(); + + Ok::<_, error_stack::Report>(output) + } + .await?; + #[cfg(feature = "kms")] let cert_data = kms::get_kms_client(&state.conf.kms) .await - .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc) + .decrypt(&apple_pay_ppc) .await .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; #[cfg(not(feature = "kms"))] - let cert_data = &state.conf.applepay_decrypt_keys.apple_pay_ppc; + let cert_data = &apple_pay_ppc; let base64_decode_cert_data = BASE64_ENGINE .decode(cert_data) @@ -3561,15 +3589,39 @@ impl ApplePayData { .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed) .attach_printable("Failed to deserialize the public key")?; + let apple_pay_ppc_key = async { + #[cfg(feature = "hashicorp-vault")] + let client = + external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault) + .await + .change_context(errors::ApplePayDecryptionError::DecryptionFailed) + .attach_printable("Failed while creating client")?; + + #[cfg(feature = "hashicorp-vault")] + let output = + masking::Secret::new(state.conf.applepay_decrypt_keys.apple_pay_ppc_key.clone()) + .fetch_inner::(client) + .await + .change_context(errors::ApplePayDecryptionError::DecryptionFailed) + .attach_printable("Failed while creating client")? + .expose(); + + #[cfg(not(feature = "hashicorp-vault"))] + let output = state.conf.applepay_decrypt_keys.apple_pay_ppc_key.clone(); + + Ok::<_, error_stack::Report>(output) + } + .await?; + #[cfg(feature = "kms")] let decrypted_apple_pay_ppc_key = kms::get_kms_client(&state.conf.kms) .await - .decrypt(&state.conf.applepay_decrypt_keys.apple_pay_ppc_key) + .decrypt(&apple_pay_ppc_key) .await .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?; #[cfg(not(feature = "kms"))] - let decrypted_apple_pay_ppc_key = &state.conf.applepay_decrypt_keys.apple_pay_ppc_key; + let decrypted_apple_pay_ppc_key = &apple_pay_ppc_key; // Create PKey objects from EcKey let private_key = PKey::private_key_from_pem(decrypted_apple_pay_ppc_key.as_bytes()) .into_report() diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 0750ff82bf5e..d805925f3183 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -5,6 +5,8 @@ use api_models::{ payment_methods::{self, BankAccountAccessCreds}, payments::{AddressDetails, BankDebitBilling, BankDebitData, PaymentMethodData}, }; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::{self, decrypt::VaultFetch}; use hex; pub mod helpers; pub mod transformers; @@ -345,15 +347,36 @@ async fn store_bank_details_in_payment_methods( } } + let pm_auth_key = async { + #[cfg(feature = "hashicorp-vault")] + let client = external_services::hashicorp_vault::get_hashicorp_client(&state.conf.hc_vault) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed while creating client")?; + + #[cfg(feature = "hashicorp-vault")] + let output = masking::Secret::new(state.conf.payment_method_auth.pm_auth_key.clone()) + .fetch_inner::(client) + .await + .change_context(ApiErrorResponse::InternalServerError)? + .expose(); + + #[cfg(not(feature = "hashicorp-vault"))] + let output = state.conf.payment_method_auth.pm_auth_key.clone(); + + Ok::<_, error_stack::Report>(output) + } + .await?; + #[cfg(feature = "kms")] let pm_auth_key = kms::get_kms_client(&state.conf.kms) .await - .decrypt(state.conf.payment_method_auth.pm_auth_key.clone()) + .decrypt(pm_auth_key) .await .change_context(ApiErrorResponse::InternalServerError)?; #[cfg(not(feature = "kms"))] - let pm_auth_key = state.conf.payment_method_auth.pm_auth_key.clone(); + let pm_auth_key = pm_auth_key; let mut update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)> = Vec::new(); diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs index 9293d6e11431..fb1851af00d9 100644 --- a/crates/router/src/routes/api_keys.rs +++ b/crates/router/src/routes/api_keys.rs @@ -1,4 +1,6 @@ use actix_web::{web, HttpRequest, Responder}; +#[cfg(feature = "hashicorp-vault")] +use error_stack::ResultExt; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -44,10 +46,20 @@ pub async fn api_key_create( |state, _, payload| async { #[cfg(feature = "kms")] let kms_client = external_services::kms::get_kms_client(&state.clone().conf.kms).await; + + #[cfg(feature = "hashicorp-vault")] + let hc_client = external_services::hashicorp_vault::get_hashicorp_client( + &state.clone().conf.hc_vault, + ) + .await + .change_context(crate::core::errors::ApiErrorResponse::InternalServerError)?; + api_keys::create_api_key( state, #[cfg(feature = "kms")] kms_client, + #[cfg(feature = "hashicorp-vault")] + hc_client, payload, merchant_id.clone(), ) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 4345109a6724..d3a43f0f490d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1,10 +1,12 @@ use std::sync::Arc; use actix_web::{web, Scope}; -#[cfg(all(feature = "kms", feature = "olap"))] +#[cfg(all(feature = "olap", any(feature = "hashicorp-vault", feature = "kms")))] use analytics::AnalyticsConfig; #[cfg(feature = "email")] use external_services::email::{ses::AwsSes, EmailService}; +#[cfg(all(feature = "olap", feature = "hashicorp-vault"))] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms::{self, decrypt::KmsDecrypt}; #[cfg(all(feature = "olap", feature = "kms"))] @@ -146,6 +148,12 @@ impl AppState { Box::pin(async move { #[cfg(feature = "kms")] let kms_client = kms::get_kms_client(&conf.kms).await; + #[cfg(all(feature = "hashicorp-vault", feature = "olap"))] + #[allow(clippy::expect_used)] + let hc_client = + external_services::hashicorp_vault::get_hashicorp_client(&conf.hc_vault) + .await + .expect("Failed while creating hashicorp_client"); let testable = storage_impl == StorageImpl::PostgresqlTest; #[allow(clippy::expect_used)] let event_handler = conf @@ -153,6 +161,7 @@ impl AppState { .get_event_handler() .await .expect("Failed to create event handler"); + let store: Box = match storage_impl { StorageImpl::Postgresql | StorageImpl::PostgresqlTest => match &event_handler { EventsHandler::Kafka(kafka_client) => Box::new( @@ -180,6 +189,22 @@ impl AppState { ), }; + #[cfg(all(feature = "hashicorp-vault", feature = "olap"))] + #[allow(clippy::expect_used)] + match conf.analytics { + AnalyticsConfig::Clickhouse { .. } => {} + AnalyticsConfig::Sqlx { ref mut sqlx } + | AnalyticsConfig::CombinedCkh { ref mut sqlx, .. } + | AnalyticsConfig::CombinedSqlx { ref mut sqlx, .. } => { + sqlx.password = sqlx + .password + .clone() + .fetch_inner::(hc_client) + .await + .expect("Failed while fetching from hashicorp vault"); + } + }; + #[cfg(all(feature = "kms", feature = "olap"))] #[allow(clippy::expect_used)] match conf.analytics { @@ -195,6 +220,16 @@ impl AppState { } }; + #[cfg(all(feature = "hashicorp-vault", feature = "olap"))] + #[allow(clippy::expect_used)] + { + conf.connector_onboarding = conf + .connector_onboarding + .fetch_inner::(hc_client) + .await + .expect("Failed to decrypt connector onboarding credentials"); + } + #[cfg(all(feature = "kms", feature = "olap"))] #[allow(clippy::expect_used)] { @@ -208,6 +243,17 @@ impl AppState { #[cfg(feature = "olap")] let pool = crate::analytics::AnalyticsProvider::from_conf(&conf.analytics).await; + #[cfg(all(feature = "hashicorp-vault", feature = "olap"))] + #[allow(clippy::expect_used)] + { + conf.jwekey = conf + .jwekey + .clone() + .fetch_inner::(hc_client) + .await + .expect("Failed to decrypt connector onboarding credentials"); + } + #[cfg(feature = "kms")] #[allow(clippy::expect_used)] let kms_secrets = settings::ActiveKmsSecrets { diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 8c973105d53b..c0ed2b442d0e 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -13,15 +13,15 @@ pub mod recon; #[cfg(feature = "email")] pub mod email; -#[cfg(feature = "kms")] +#[cfg(any(feature = "kms", feature = "hashicorp-vault"))] use data_models::errors::StorageError; use data_models::errors::StorageResult; use error_stack::{IntoReport, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms::{self, decrypt::KmsDecrypt}; -#[cfg(not(feature = "kms"))] -use masking::PeekInterface; -use masking::StrongSecret; +use masking::{PeekInterface, StrongSecret}; #[cfg(feature = "kv_store")] use storage_impl::KVRouterStore; use storage_impl::RouterStore; @@ -48,39 +48,58 @@ pub async fn get_store( #[cfg(feature = "kms")] let kms_client = kms::get_kms_client(&config.kms).await; + #[cfg(feature = "hashicorp-vault")] + let hc_client = external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault) + .await + .change_context(StorageError::InitializationError)?; + + let master_config = config.master_database.clone(); + + #[cfg(feature = "hashicorp-vault")] + let master_config = master_config + .fetch_inner::(hc_client) + .await + .change_context(StorageError::InitializationError) + .attach_printable("Failed to fetch data from hashicorp vault")?; + #[cfg(feature = "kms")] - let master_config = config - .master_database - .clone() + let master_config = master_config .decrypt_inner(kms_client) .await .change_context(StorageError::InitializationError) .attach_printable("Failed to decrypt master database config")?; - #[cfg(not(feature = "kms"))] - let master_config = config.master_database.clone().into(); + + #[cfg(feature = "olap")] + let replica_config = config.replica_database.clone(); + + #[cfg(all(feature = "olap", feature = "hashicorp-vault"))] + let replica_config = replica_config + .fetch_inner::(hc_client) + .await + .change_context(StorageError::InitializationError) + .attach_printable("Failed to fetch data from hashicorp vault")?; #[cfg(all(feature = "olap", feature = "kms"))] - let replica_config = config - .replica_database - .clone() + let replica_config = replica_config .decrypt_inner(kms_client) .await .change_context(StorageError::InitializationError) .attach_printable("Failed to decrypt replica database config")?; - #[cfg(all(feature = "olap", not(feature = "kms")))] - let replica_config = config.replica_database.clone().into(); - let master_enc_key = get_master_enc_key( config, #[cfg(feature = "kms")] kms_client, + #[cfg(feature = "hashicorp-vault")] + hc_client, ) .await; #[cfg(not(feature = "olap"))] - let conf = master_config; + let conf = master_config.into(); #[cfg(feature = "olap")] - let conf = (master_config, replica_config); + // this would get abstracted, for all cases + #[allow(clippy::useless_conversion)] + let conf = (master_config.into(), replica_config.into()); let store: RouterStore = if test_transaction { RouterStore::test_store(conf, &config.redis, master_enc_key).await? @@ -110,21 +129,26 @@ pub async fn get_store( async fn get_master_enc_key( conf: &crate::configs::settings::Settings, #[cfg(feature = "kms")] kms_client: &kms::KmsClient, + #[cfg(feature = "hashicorp-vault")] + hc_client: &external_services::hashicorp_vault::HashiCorpVault, ) -> StrongSecret> { + let master_enc_key = conf.secrets.master_enc_key.clone(); + + #[cfg(feature = "hashicorp-vault")] + let master_enc_key = master_enc_key + .fetch_inner::(hc_client) + .await + .expect("Failed to fetch master enc key"); + #[cfg(feature = "kms")] - let master_enc_key = hex::decode( - conf.secrets - .master_enc_key - .clone() + let master_enc_key = masking::Secret::<_, masking::WithType>::new( + master_enc_key .decrypt_inner(kms_client) .await .expect("Failed to decrypt master enc key"), - ) - .expect("Failed to decode from hex"); + ); - #[cfg(not(feature = "kms"))] - let master_enc_key = - hex::decode(conf.secrets.master_enc_key.peek()).expect("Failed to decode from hex"); + let master_enc_key = hex::decode(master_enc_key.peek()).expect("Failed to decode from hex"); StrongSecret::new(master_enc_key) } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index eaadc0d5c7be..7f1e078ad53d 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -3,9 +3,13 @@ use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; use common_utils::date_time; use error_stack::{report, IntoReport, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::decrypt::VaultFetch; #[cfg(feature = "kms")] use external_services::kms::{self, decrypt::KmsDecrypt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +#[cfg(feature = "hashicorp-vault")] +use masking::ExposeInterface; use masking::{PeekInterface, StrongSecret}; use serde::Serialize; @@ -222,6 +226,10 @@ where &config.api_keys, #[cfg(feature = "kms")] kms::get_kms_client(&config.kms).await, + #[cfg(feature = "hashicorp-vault")] + external_services::hashicorp_vault::get_hashicorp_client(&config.hc_vault) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?, ) .await? }; @@ -281,9 +289,14 @@ static ADMIN_API_KEY: tokio::sync::OnceCell> = pub async fn get_admin_api_key( secrets: &settings::Secrets, #[cfg(feature = "kms")] kms_client: &kms::KmsClient, + #[cfg(feature = "hashicorp-vault")] + hc_client: &external_services::hashicorp_vault::HashiCorpVault, ) -> RouterResult<&'static StrongSecret> { ADMIN_API_KEY .get_or_try_init(|| async { + #[cfg(not(feature = "kms"))] + let admin_api_key = secrets.admin_api_key.clone(); + #[cfg(feature = "kms")] let admin_api_key = secrets .kms_encrypted_admin_api_key @@ -292,8 +305,13 @@ pub async fn get_admin_api_key( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to KMS decrypt admin API key")?; - #[cfg(not(feature = "kms"))] - let admin_api_key = secrets.admin_api_key.clone(); + #[cfg(feature = "hashicorp-vault")] + let admin_api_key = masking::Secret::new(admin_api_key) + .fetch_inner::(hc_client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to KMS decrypt admin API key")? + .expose(); Ok(StrongSecret::new(admin_api_key)) }) @@ -348,6 +366,11 @@ where &conf.secrets, #[cfg(feature = "kms")] kms::get_kms_client(&conf.kms).await, + #[cfg(feature = "hashicorp-vault")] + external_services::hashicorp_vault::get_hashicorp_client(&conf.hc_vault) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting admin api key")?, ) .await?; diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs index 118d9df28e22..a01f2520b6ac 100644 --- a/crates/router/src/utils/currency.rs +++ b/crates/router/src/utils/currency.rs @@ -4,6 +4,8 @@ use api_models::enums; use common_utils::{date_time, errors::CustomResult, events::ApiEventMetric, ext_traits::AsyncExt}; use currency_conversion::types::{CurrencyFactors, ExchangeRates}; use error_stack::{IntoReport, ResultExt}; +#[cfg(feature = "hashicorp-vault")] +use external_services::hashicorp_vault::{self, decrypt::VaultFetch}; #[cfg(feature = "kms")] use external_services::kms; use masking::PeekInterface; @@ -127,6 +129,8 @@ async fn waited_fetch_and_update_caches( local_fetch_retry_delay: u64, local_fetch_retry_count: u64, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { for _n in 1..local_fetch_retry_count { sleep(Duration::from_millis(local_fetch_retry_delay)).await; @@ -149,6 +153,8 @@ async fn waited_fetch_and_update_caches( None, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -187,6 +193,8 @@ pub async fn get_forex_rates( local_fetch_retry_delay: u64, local_fetch_retry_count: u64, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { if let Some(local_rates) = retrieve_forex_from_local().await { if local_rates.is_expired(call_delay) { @@ -197,6 +205,8 @@ pub async fn get_forex_rates( local_rates, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } else { @@ -212,6 +222,8 @@ pub async fn get_forex_rates( local_fetch_retry_count, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -223,6 +235,8 @@ async fn handler_local_no_data( _local_fetch_retry_delay: u64, _local_fetch_retry_count: u64, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { match retrieve_forex_from_redis(state).await { Ok(Some(data)) => { @@ -232,6 +246,8 @@ async fn handler_local_no_data( call_delay, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -242,6 +258,8 @@ async fn handler_local_no_data( None, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await?) } @@ -252,6 +270,8 @@ async fn handler_local_no_data( None, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await?) } @@ -262,6 +282,8 @@ async fn successive_fetch_and_save_forex( state: &AppState, stale_redis_data: Option, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { match acquire_redis_lock(state).await { Ok(lock_acquired) => { @@ -272,6 +294,8 @@ async fn successive_fetch_and_save_forex( state, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await; match api_rates { @@ -283,6 +307,8 @@ async fn successive_fetch_and_save_forex( state, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await; match secondary_api_rates { @@ -326,6 +352,8 @@ async fn fallback_forex_redis_check( redis_data: FxExchangeRatesCacheEntry, call_delay: i64, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { match is_redis_expired(Some(redis_data.clone()).as_ref(), call_delay).await { Some(redis_forex) => { @@ -341,6 +369,8 @@ async fn fallback_forex_redis_check( Some(redis_data), #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -352,6 +382,8 @@ async fn handler_local_expired( call_delay: i64, local_rates: FxExchangeRatesCacheEntry, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { match retrieve_forex_from_redis(state).await { Ok(redis_data) => { @@ -370,6 +402,8 @@ async fn handler_local_expired( Some(local_rates), #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -383,6 +417,8 @@ async fn handler_local_expired( Some(local_rates), #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await } @@ -392,16 +428,40 @@ async fn handler_local_expired( async fn fetch_forex_rates( state: &AppState, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> Result> { + let forex_api_key = async { + #[cfg(feature = "hashicorp-vault")] + let client = hashicorp_vault::get_hashicorp_client(hc_config) + .await + .change_context(ForexCacheError::KmsDecryptionFailed)?; + + #[cfg(not(feature = "hashicorp-vault"))] + let output = state.conf.forex_api.api_key.clone(); + #[cfg(feature = "hashicorp-vault")] + let output = state + .conf + .forex_api + .api_key + .clone() + .fetch_inner::(client) + .await + .change_context(ForexCacheError::KmsDecryptionFailed)?; + + Ok::<_, error_stack::Report>(output) + } + .await?; #[cfg(feature = "kms")] let forex_api_key = kms::get_kms_client(kms_config) .await - .decrypt(state.conf.forex_api.api_key.peek()) + .decrypt(forex_api_key.peek()) .await .change_context(ForexCacheError::KmsDecryptionFailed)?; #[cfg(not(feature = "kms"))] - let forex_api_key = state.conf.forex_api.api_key.peek(); + let forex_api_key = forex_api_key.peek(); let forex_url: String = format!("{}{}{}", FOREX_BASE_URL, forex_api_key, FOREX_BASE_CURRENCY); let forex_request = services::RequestBuilder::new() @@ -457,16 +517,39 @@ async fn fetch_forex_rates( pub async fn fallback_fetch_forex_rates( state: &AppState, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { + let fallback_api_key = async { + #[cfg(feature = "hashicorp-vault")] + let client = hashicorp_vault::get_hashicorp_client(hc_config) + .await + .change_context(ForexCacheError::KmsDecryptionFailed)?; + + #[cfg(not(feature = "hashicorp-vault"))] + let output = state.conf.forex_api.fallback_api_key.clone(); + #[cfg(feature = "hashicorp-vault")] + let output = state + .conf + .forex_api + .fallback_api_key + .clone() + .fetch_inner::(client) + .await + .change_context(ForexCacheError::KmsDecryptionFailed)?; + + Ok::<_, error_stack::Report>(output) + } + .await?; #[cfg(feature = "kms")] let fallback_forex_api_key = kms::get_kms_client(kms_config) .await - .decrypt(state.conf.forex_api.fallback_api_key.peek()) + .decrypt(fallback_api_key.peek()) .await .change_context(ForexCacheError::KmsDecryptionFailed)?; #[cfg(not(feature = "kms"))] - let fallback_forex_api_key = state.conf.forex_api.fallback_api_key.peek(); + let fallback_forex_api_key = fallback_api_key.peek(); let fallback_forex_url: String = format!("{}{}", FALLBACK_FOREX_BASE_URL, fallback_forex_api_key,); @@ -609,6 +692,8 @@ pub async fn convert_currency( to_currency: String, from_currency: String, #[cfg(feature = "kms")] kms_config: &kms::KmsConfig, + #[cfg(feature = "hashicorp-vault")] + hc_config: &external_services::hashicorp_vault::HashiCorpVaultConfig, ) -> CustomResult { let rates = get_forex_rates( &state, @@ -617,6 +702,8 @@ pub async fn convert_currency( state.conf.forex_api.local_fetch_retry_count, #[cfg(feature = "kms")] kms_config, + #[cfg(feature = "hashicorp-vault")] + hc_config, ) .await .change_context(ForexCacheError::ApiError)?; diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index 8dca7942ab0a..579f4ef10e7c 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -22,10 +22,10 @@ serde_path_to_error = "0.1.14" strum = { version = "0.24.1", features = ["derive"] } time = { version = "0.3.21", default-features = false, features = ["formatting"] } tokio = { version = "1.35.1" } -tracing = { version = "=0.1.36" } +tracing = { version = "0.1.37" } tracing-actix-web = { version = "0.7.8", features = ["opentelemetry_0_19", "uuid_v7"], optional = true } tracing-appender = { version = "0.2.2" } -tracing-attributes = "=0.1.22" +tracing-attributes = "0.1.27" tracing-opentelemetry = { version = "0.19.0" } tracing-subscriber = { version = "0.3.17", default-features = true, features = ["env-filter", "json", "registry"] } vergen = { version = "8.2.1", optional = true, features = ["cargo", "git", "git2", "rustc"] } @@ -43,4 +43,4 @@ actix_web = ["tracing-actix-web"] log_custom_entries_to_extra = [] log_extra_implicit_fields = [] log_active_span_json = [] -payouts = [] \ No newline at end of file +payouts = [] From 3f343d36bff7ce8f73602a2391d205367d5581c7 Mon Sep 17 00:00:00 2001 From: ivor-juspay <138492857+ivor-juspay@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:48:59 +0530 Subject: [PATCH 43/48] chore(ckh-source): updated ckh analytics source tables (#3397) Co-authored-by: Sampras lopes --- crates/analytics/src/clickhouse.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index f81c29c801c0..00ae3b6e3103 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -354,11 +354,11 @@ impl ToSql for PrimitiveDateTime { impl ToSql for AnalyticsCollection { fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { match self { - Self::Payment => Ok("payment_attempt_dist".to_string()), - Self::Refund => Ok("refund_dist".to_string()), - Self::SdkEvents => Ok("sdk_events_dist".to_string()), - Self::ApiEvents => Ok("api_audit_log".to_string()), - Self::PaymentIntent => Ok("payment_intents_dist".to_string()), + Self::Payment => Ok("payment_attempts".to_string()), + Self::Refund => Ok("refunds".to_string()), + Self::SdkEvents => Ok("sdk_events_audit".to_string()), + Self::ApiEvents => Ok("api_events_audit".to_string()), + Self::PaymentIntent => Ok("payment_intents".to_string()), Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), } From 8a019f08acf74e04c3ae9c8790dd481301bdcfee Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:46:29 +0530 Subject: [PATCH 44/48] Refactor(compatibility): revert add multiuse mandates support in stripe compatibility (#3436) --- .../stripe/payment_intents/types.rs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 51f938d445c4..38007a3110d6 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -738,25 +738,9 @@ impl ForeignTryFrom<(Option, Option)> for Option Some(payments::MandateType::MultiUse(Some( - payments::MandateAmountData { - amount: mandate.amount.unwrap_or_default(), - currency, - start_date: mandate.start_date, - end_date: mandate.end_date, - metadata: None, - }, - ))), + StripeMandateType::MultiUse => Some(payments::MandateType::MultiUse(None)), }, - None => Some(api_models::payments::MandateType::MultiUse(Some( - payments::MandateAmountData { - amount: mandate.amount.unwrap_or_default(), - currency, - start_date: mandate.start_date, - end_date: mandate.end_date, - metadata: None, - }, - ))), + None => Some(api_models::payments::MandateType::MultiUse(None)), }, customer_acceptance: Some(payments::CustomerAcceptance { acceptance_type: payments::AcceptanceType::Online, From 4cd65a24f70fdef160eb2d87654f1e30538c3339 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:09:53 +0530 Subject: [PATCH 45/48] Refactor(Router): [Noon] revert adding new field max_amount to mandate request (#3435) --- .../router/src/connector/noon/transformers.rs | 59 +++++++------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 81f3ab33e2f3..bbf284848b59 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -1,5 +1,5 @@ use common_utils::pii; -use error_stack::{IntoReport, ResultExt}; +use error_stack::ResultExt; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -7,7 +7,7 @@ use crate::{ connector::utils::{ self as conn_utils, CardData, PaymentsAuthorizeRequestData, RouterData, WalletData, }, - core::{errors, mandate::MandateBehaviour}, + core::errors, services, types::{self, api, storage::enums, transformers::ForeignFrom, ErrorResponse}, utils, @@ -30,13 +30,11 @@ pub enum NoonSubscriptionType { } #[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] pub struct NoonSubscriptionData { #[serde(rename = "type")] subscription_type: NoonSubscriptionType, //Short description about the subscription. name: String, - max_amount: String, } #[derive(Debug, Serialize)] @@ -93,7 +91,7 @@ pub struct NoonSubscription { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct NoonCard { - name_on_card: Option>, + name_on_card: Secret, number_plain: cards::CardNumber, expiry_month: Secret, expiry_year: Secret, @@ -160,7 +158,7 @@ pub struct NoonPayPal { } #[derive(Debug, Serialize)] -#[serde(tag = "type", content = "data", rename_all = "UPPERCASE")] +#[serde(tag = "type", content = "data")] pub enum NoonPaymentData { Card(NoonCard), Subscription(NoonSubscription), @@ -202,7 +200,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { _ => ( match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(req_card) => Ok(NoonPaymentData::Card(NoonCard { - name_on_card: req_card.card_holder_name.clone(), + name_on_card: req_card + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), number_plain: req_card.card_number.clone(), expiry_month: req_card.card_exp_month.clone(), expiry_year: req_card.get_expiry_year_4_digit(), @@ -295,11 +296,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { } }?, Some(item.request.currency), - Some(item.request.order_category.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "order_category", - }, - )?), + item.request.order_category.clone(), ), }; @@ -333,33 +330,17 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { }, }); - let subscription = item - .request - .get_setup_mandate_details() - .map(|mandate_data| { - let max_amount = match &mandate_data.mandate_type { - Some(data_models::mandates::MandateDataType::SingleUse(mandate)) - | Some(data_models::mandates::MandateDataType::MultiUse(Some(mandate))) => { - conn_utils::to_currency_base_unit(mandate.amount, mandate.currency) - } - _ => Err(errors::ConnectorError::MissingRequiredField { - field_name: "setup_future_usage.mandate_data.mandate_type", - }) - .into_report(), - }?; - - Ok::>( - NoonSubscriptionData { - subscription_type: NoonSubscriptionType::Unscheduled, - name: name.clone(), - max_amount, - }, - ) - }) - .transpose()?; - - let tokenize_c_c = subscription.is_some().then_some(true); - + let (subscription, tokenize_c_c) = + match item.request.setup_future_usage.is_some().then_some(( + NoonSubscriptionData { + subscription_type: NoonSubscriptionType::Unscheduled, + name: name.clone(), + }, + true, + )) { + Some((a, b)) => (Some(a), Some(b)), + None => (None, None), + }; let order = NoonOrder { amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?, currency, From 61439533594f93287f15aaffb5d65ed12e77e7ec Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:54:55 +0000 Subject: [PATCH 46/48] chore(version): 2024.01.24.1 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af517a6a1153..3e300ccbb119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.24.1 + +### Features + +- **hashicorp:** Implement hashicorp secrets manager solution ([#3297](https://github.com/juspay/hyperswitch/pull/3297)) ([`629d546`](https://github.com/juspay/hyperswitch/commit/629d546aa7c774e86d609abec3b3ab5cf0d100a7)) + +### Refactors + +- **Router:** [Noon] revert adding new field max_amount to mandate request ([#3435](https://github.com/juspay/hyperswitch/pull/3435)) ([`4cd65a2`](https://github.com/juspay/hyperswitch/commit/4cd65a24f70fdef160eb2d87654f1e30538c3339)) +- **compatibility:** Revert add multiuse mandates support in stripe compatibility ([#3436](https://github.com/juspay/hyperswitch/pull/3436)) ([`8a019f0`](https://github.com/juspay/hyperswitch/commit/8a019f08acf74e04c3ae9c8790dd481301bdcfee)) + +### Miscellaneous Tasks + +- **ckh-source:** Updated ckh analytics source tables ([#3397](https://github.com/juspay/hyperswitch/pull/3397)) ([`3f343d3`](https://github.com/juspay/hyperswitch/commit/3f343d36bff7ce8f73602a2391d205367d5581c7)) + +**Full Changelog:** [`2024.01.24.0...2024.01.24.1`](https://github.com/juspay/hyperswitch/compare/2024.01.24.0...2024.01.24.1) + +- - - + ## 2024.01.24.0 ### Miscellaneous Tasks From 77c1bbb5a3fe3244cd988ac1260a4a31ae7fcd20 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Wed, 24 Jan 2024 21:45:25 +0530 Subject: [PATCH 47/48] refactor(configs): add configs for deployments to environments (#3265) Co-authored-by: Sk Sakil Mostak --- config/config.example.toml | 113 ++++---- config/deployments/README.md | 158 +++++++++++ config/deployments/drainer.toml | 39 +++ config/deployments/env_specific.toml | 211 +++++++++++++++ config/deployments/integration_test.toml | 279 +++++++++++++++++++ config/deployments/production.toml | 295 ++++++++++++++++++++ config/deployments/sandbox.toml | 296 +++++++++++++++++++++ config/deployments/scheduler/consumer.toml | 11 + config/deployments/scheduler/producer.toml | 14 + 9 files changed, 1362 insertions(+), 54 deletions(-) create mode 100644 config/deployments/README.md create mode 100644 config/deployments/drainer.toml create mode 100644 config/deployments/env_specific.toml create mode 100644 config/deployments/integration_test.toml create mode 100644 config/deployments/production.toml create mode 100644 config/deployments/sandbox.toml create mode 100644 config/deployments/scheduler/consumer.toml create mode 100644 config/deployments/scheduler/producer.toml diff --git a/config/config.example.toml b/config/config.example.toml index cf25ef195a24..0ad50736e9ed 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -45,25 +45,25 @@ queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 clie [redis] host = "127.0.0.1" port = 6379 -pool_size = 5 # Number of connections to keep open -reconnect_max_attempts = 5 # Maximum number of reconnection attempts to make before failing. Set to 0 to retry forever. -reconnect_delay = 5 # Delay between reconnection attempts, in milliseconds -default_ttl = 300 # Default TTL for entries, in seconds -default_hash_ttl = 900 # Default TTL for hashes entries, in seconds -use_legacy_version = false # Resp protocol for fred crate (set this to true if using RESPv2 or redis version < 6) -stream_read_count = 1 # Default number of entries to read from stream if not provided in stream read options -auto_pipeline = true # Whether or not the client should automatically pipeline commands across tasks when possible. +pool_size = 5 # Number of connections to keep open +reconnect_max_attempts = 5 # Maximum number of reconnection attempts to make before failing. Set to 0 to retry forever. +reconnect_delay = 5 # Delay between reconnection attempts, in milliseconds +default_ttl = 300 # Default TTL for entries, in seconds +default_hash_ttl = 900 # Default TTL for hashes entries, in seconds +use_legacy_version = false # Resp protocol for fred crate (set this to true if using RESPv2 or redis version < 6) +stream_read_count = 1 # Default number of entries to read from stream if not provided in stream read options +auto_pipeline = true # Whether or not the client should automatically pipeline commands across tasks when possible. disable_auto_backpressure = false # Whether or not to disable the automatic backpressure features when pipelining is enabled. -max_in_flight_commands = 5000 # The maximum number of in-flight commands (per connection) before backpressure will be applied. -default_command_timeout = 0 # An optional timeout to apply to all commands. -max_feed_count = 200 # The maximum number of frames that will be fed to a socket before flushing. +max_in_flight_commands = 5000 # The maximum number of in-flight commands (per connection) before backpressure will be applied. +default_command_timeout = 0 # An optional timeout to apply to all commands. +max_feed_count = 200 # The maximum number of frames that will be fed to a socket before flushing. # This section provides configs for currency conversion api [forex_api] call_delay = 21600 # Api calls are made after every 6 hrs local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 local_fetch_retry_delay = 1000 # Retry delay for checking write condition -api_timeout = 20000 # Api timeouts once it crosses 2000 ms +api_timeout = 20000 # Api timeouts once it crosses 20000 ms api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api fallback_api_key = "YOUR API KEY" # Api key for the fallback service redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called @@ -126,12 +126,11 @@ kms_encrypted_recon_admin_api_key = "" # Base64-encoded (KMS encrypted) cipher # PCI Compliant storage entity which stores payment method information # like card details [locker] -host = "" # Locker host -host_rs = "" # Rust Locker host -mock_locker = true # Emulate a locker locally using Postgres -basilisk_host = "" # Basilisk host -locker_signing_key_id = "1" # Key_id to sign basilisk hs locker -locker_enabled = true # Boolean to enable or disable saving cards in locker +host = "" # Locker host +host_rs = "" # Rust Locker host +mock_locker = true # Emulate a locker locally using Postgres +locker_signing_key_id = "1" # Key_id to sign basilisk hs locker +locker_enabled = true # Boolean to enable or disable saving cards in locker [delayed_session_response] connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which has delayed session response @@ -144,7 +143,6 @@ vault_encryption_key = "" # public key in pem format, corresponding privat rust_locker_encryption_key = "" # public key in pem format, corresponding private key in rust locker vault_private_key = "" # private key in pem format, corresponding public key in basilisk-hs - # Refund configuration [refund] max_attempts = 10 # Number of refund attempts allowed @@ -336,7 +334,6 @@ active_email_client = "SES" # The currently active email client email_role_arn = "" # The amazon resource name ( arn ) of the role which has permission to send emails sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session. - #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } @@ -352,9 +349,9 @@ stripe = { payment_method = "bank_transfer" } nuvei = { payment_method = "card" } shift4 = { payment_method = "card" } bluesnap = { payment_method = "card" } -bankofamerica = {payment_method = "card"} -cybersource = {payment_method = "card"} -nmi = {payment_method = "card"} +bankofamerica = { payment_method = "card" } +cybersource = { payment_method = "card" } +nmi = { payment_method = "card" } [dummy_connector] enabled = true # Whether dummy connector is enabled or not @@ -377,13 +374,13 @@ slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswit discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch [mandates.supported_payment_methods] -card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card -wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets -pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later -bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_redirect.ideal = {connector_list = "stripe,adyen"} # Mandate supported payment method type and connector for bank_redirect +card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card +wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets +pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later +bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_redirect.ideal = { connector_list = "stripe,adyen" } # Mandate supported payment method type and connector for bank_redirect # Required fields info used while listing the payment_method_data @@ -464,10 +461,27 @@ adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_ba supported_connectors = "braintree" [applepay_decrypt_keys] -apple_pay_ppc = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE" #Payment Processing Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Payment Processing Certificate -apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" #Private key generate by Elliptic-curve prime256v1 curve -apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" #Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate -apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key generate by RSA:2048 algorithm +apple_pay_ppc = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE" # Payment Processing Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Payment Processing Certificate +apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" # Private key generated by Elliptic-curve prime256v1 curve. You can use `openssl ecparam -out private.key -name prime256v1 -genkey` to generate the private key +apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate +apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key generated by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key + +[applepay_merchant_configs] +# Run below command to get common merchant identifier for applepay in shell +# +# CERT_PATH="path/to/certificate.pem" +# MERCHANT_ID=$(openssl x509 -in "$CERT_PATH" -noout -text | +# awk -v oid="1.2.840.113635.100.6.32" ' +# BEGIN { RS = "\n\n" } +# /X509v3 extensions/ { in_extension=1 } +# in_extension && /'"$oid"'/ { print $0; exit }' | +# grep -oE '\.@[A-F0-9]+' | sed 's/^\.@//' +# ) +# echo "Merchant ID: $MERCHANT_ID" +common_merchant_identifier = "APPLE_PAY_COMMON_MERCHANT_IDENTIFIER" # This can be obtained by decrypting the apple_pay_ppc_key as shown above in comments +merchant_cert = "APPLE_PAY_MERCHANT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate +merchant_cert_key = "APPLE_PAY_MERCHANT_CERTIFICATE_KEY" # Private key generate by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key +applepay_endpoint = "https://apple-pay-gateway.apple.com/paymentservices/registerMerchant" # Apple pay gateway merchant endpoint [payment_link] sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js" @@ -505,28 +519,19 @@ ttl = 900 enabled = true [paypal_onboarding] -client_id = "paypal_client_id" # Client ID for PayPal onboarding -client_secret = "paypal_secret_key" # Secret key for PayPal onboarding -partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding -enabled = true # Switch to enable or disable PayPal onboarding - -[frm] -enabled = true - -[paypal_onboarding] -client_id = "paypal_client_id" # Client ID for PayPal onboarding +client_id = "paypal_client_id" # Client ID for PayPal onboarding client_secret = "paypal_secret_key" # Secret key for PayPal onboarding -partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding -enabled = true # Switch to enable or disable PayPal onboarding +partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding +enabled = true # Switch to enable or disable PayPal onboarding [events] -source = "logs" # The event sink to push events supports kafka or logs (stdout) +source = "logs" # The event sink to push events supports kafka or logs (stdout) [events.kafka] -brokers = [] # Kafka broker urls for bootstrapping the client -intent_analytics_topic = "topic" # Kafka topic to be used for PaymentIntent events -attempt_analytics_topic = "topic" # Kafka topic to be used for PaymentAttempt events -refund_analytics_topic = "topic" # Kafka topic to be used for Refund events -api_logs_topic = "topic" # Kafka topic to be used for incoming api events -connector_logs_topic = "topic" # Kafka topic to be used for connector api events -outgoing_webhook_logs_topic = "topic" # Kafka topic to be used for outgoing webhook events +brokers = [] # Kafka broker urls for bootstrapping the client +intent_analytics_topic = "topic" # Kafka topic to be used for PaymentIntent events +attempt_analytics_topic = "topic" # Kafka topic to be used for PaymentAttempt events +refund_analytics_topic = "topic" # Kafka topic to be used for Refund events +api_logs_topic = "topic" # Kafka topic to be used for incoming api events +connector_logs_topic = "topic" # Kafka topic to be used for connector api events +outgoing_webhook_logs_topic = "topic" # Kafka topic to be used for outgoing webhook events diff --git a/config/deployments/README.md b/config/deployments/README.md new file mode 100644 index 000000000000..c807892f1e06 --- /dev/null +++ b/config/deployments/README.md @@ -0,0 +1,158 @@ +# Configs for deployments + +## Introduction + +This directory contains the configs for deployments of Hyperswitch in different hosted environments. + +Hyperswitch has **3** components namely, + +- router +- drainer +- scheduler + - consumer + - producer + +We maintain configs for the `router` component for 3 different environments, namely, + +- Integration Test +- Sandbox +- Production + +To learn about what "router", "drainer" and "scheduler" is, please refer to the [Hyperswitch architecture][architecture] documentation. + +### Tree structure + +```text +config/deployments # Root directory for the deployment configs +├── README.md # This file +├── drainer.toml # Config specific to drainer +├── env_specific.toml # Config for environment specific values which are meant to be sensitive (to be set by the user) +├── integration_test.toml # Config specific to integration_test environment +├── production.toml # Config specific to production environment +├── sandbox.toml # Config specific to sandbox environment +└── scheduler # Directory for scheduler configs + ├── consumer.toml # Config specific to consumer + └── producer.toml # Config specific to producer +``` + +## Router + +The `integration_test.toml`, `sandbox.toml`, and `production.toml` files are configuration files for the environments `integration_test`, `sandbox`, and `production`, respectively. These files maintain a 1:1 mapping with the environment names, and it is recommended to use the same name for the environment throughout this document. + +### Generating a Config File for the Router + +The `env_specific.toml` file contains values that are specific to the environment. This file is kept separate because the values in it are sensitive and are meant to be set by the user. The `env_specific.toml` file is merged with the `integration_test.toml`, `sandbox.toml`, or `production.toml` file to create the final configuration file for the router. + +For example, to build and deploy Hyperswitch in the **sandbox environment**, you can duplicate the `env_specific.toml` file and rename it as `sandbox_config.toml`. Then, update the values in the file with the proper values for the sandbox environment. + +The environment-specific `sandbox.toml` file, which contains the Hyperswitch recommended defaults, is merged with the `sandbox_config.toml` file to create the final configuration file called `sandbox_release.toml`. This file is marked as ready for deploying on the sandbox environment. + +1. Duplicate the `env_specific.toml` file and rename it as `sandbox_config.toml`: + + ```shell + cp config/deployments/env_specific.toml config/deployments/sandbox_config.toml + ``` + +2. Update the values in the `sandbox_config.toml` file with the proper values for the sandbox environment: + + ```shell + vi config/deployments/sandbox_config.toml + ``` + +3. To merge the files you can use `cat`: + + ```shell + cat config/deployments/sandbox.toml config/deployments/sandbox_config.toml > config/deployments/sandbox_release.toml + ``` + +> [!NOTE] +> You can refer to the [`config.example.toml`][config_example] file to understand the variables that used are in the `env_specific.toml` file. + +## Scheduler + +The scheduler has two components, namely `consumer` and `producer`. + +The `consumer.toml` and `producer.toml` files are the configuration files for the `consumer` and `producer`, respectively. These files contain the default values recommended by Hyperswitch. + +### Generating a Config File for the Scheduler + +Scheduler configuration files are built on top of the router configuration files. So, the `sandbox_release.toml` file is merged with the `consumer.toml` or `producer.toml` file to create the final configuration file for the scheduler. + +You can use `cat` to merge the files in the terminal. + +- Below is an example for consumer in sandbox environment: + + ```shell + cat config/deployments/scheduler/consumer.toml config/deployments/sandbox_release.toml > config/deployments/consumer_sandbox_release.toml + ``` + +- Below is an example for producer in sandbox environment: + + ```shell + cat config/deployments/scheduler/producer.toml config/deployments/sandbox_release.toml > config/deployments/producer_sandbox_release.toml + ``` + +## Drainer + +Drainer is an independent component, and hence, the drainer configs can be used directly provided that the user updates the `drainer.toml` file with proper values before using. + +## Running Hyperswitch through Docker Compose + +To run the router, you can use the following snippet in the `docker-compose.yml` file: + +```yaml +### Application services +hyperswitch-server: + image: juspaydotin/hyperswitch-router:latest # This pulls the latest image from Docker Hub. If you wish to use a version without added features (like KMS), you can replace `latest` with `standalone`. However, please note that the standalone version is not recommended for production use. + command: /local/bin/router --config-path /local/config/deployments/sandbox_release.toml # <--- Change this to the config file that is generated for the environment. + ports: + - "8080:8080" + volumes: + - ./config:/local/config +``` + +To run the producer, you can use the following snippet in the `docker-compose.yml` file: + +```yaml +hyperswitch-producer: + image: juspaydotin/hyperswitch-producer:latest + command: /local/bin/scheduler --config-path /local/config/deployments/producer_sandbox_release.toml # <--- Change this to the config file that is generated for the environment. + volumes: + - ./config:/local/config + environment: + - SCHEDULER_FLOW=producer +``` + +To run the consumer, you can use the following snippet in the `docker-compose.yml` file: + +```yaml +hyperswitch-consumer: + image: juspaydotin/hyperswitch-consumer:latest + command: /local/bin/scheduler --config-path /local/config/deployments/consumer_sandbox_release.toml # <--- Change this to the config file that is generated for the environment + volumes: + - ./config:/local/config + environment: + - SCHEDULER_FLOW=consumer +``` + +To run the drainer, you can use the following snippet in the `docker-compose.yml` file: + +```yaml +hyperswitch-drainer: + image: juspaydotin/hyperswitch-drainer:latest + command: /local/bin/drainer --config-path /local/config/deployments/drainer.toml + volumes: + - ./config:/local/config +``` + +> [!NOTE] +> You can replace the term `sandbox` with the environment name that you are deploying to (e.g., `production`, `integration_test`, etc.) with respective changes (optional) and use the same steps to generate the final configuration file for the environment. + +You can verify that the server is up and running by hitting the health check endpoint: + +```shell +curl --head --request GET 'http://localhost:8080/health' +``` + +[architecture]: /docs/architecture.md +[config_example]: /config/config.example.toml diff --git a/config/deployments/drainer.toml b/config/deployments/drainer.toml new file mode 100644 index 000000000000..42c89cbfd584 --- /dev/null +++ b/config/deployments/drainer.toml @@ -0,0 +1,39 @@ +[drainer] +loop_interval = 500 +max_read_count = 100 +num_partitions = 64 +shutdown_interval = 1000 +stream_name = "drainer_stream" + +[kms] +key_id = "kms_key_id" +region = "kms_region" + +[log.console] +enabled = true +level = "DEBUG" +log_format = "json" + +[log.telemetry] +metrics_enabled = true +otel_exporter_otlp_endpoint = "http://localhost:4317" + +[master_database] +dbname = "master_database_name" +host = "localhost" +password = "master_database_password" +pool_size = 3 +port = 5432 +username = "username" + +[redis] +cluster_enabled = false +cluster_urls = ["redis.cluster.uri-1:8080", "redis.cluster.uri-2:4115"] # List of redis cluster urls +default_ttl = 300 +host = "localhost" +pool_size = 5 +port = 6379 +reconnect_delay = 5 +reconnect_max_attempts = 5 +stream_read_count = 1 +use_legacy_version = false diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml new file mode 100644 index 000000000000..354c320e8f58 --- /dev/null +++ b/config/deployments/env_specific.toml @@ -0,0 +1,211 @@ +# For explanantion of each config, please refer to the `config/config.example.toml` file + +[analytics.clickhouse] +username = "clickhouse_username" # Clickhouse username +password = "clickhouse_password" # Clickhouse password (optional) +host = "http://localhost:8123" # Clickhouse host in http(s)://: format +database_name = "clickhouse_db_name" # Clickhouse database name + +# Analytics configuration. +[analytics] +source = "sqlx" # The Analytics source/strategy to be used + +[analytics.sqlx] +username = "db_user" # Analytics DB Username +password = "db_pass" # Analytics DB Password +host = "localhost" # Analytics DB Host +port = 5432 # Analytics DB Port +dbname = "hyperswitch_db" # Name of Database +pool_size = 5 # Number of connections to keep open +connection_timeout = 10 # Timeout for database connection in seconds +queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 client + +[api_keys] +kms_encrypted_hash_key = "base64_encoded_ciphertext" # Base64-encoded (KMS encrypted) ciphertext of the API key hashing key +hash_key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # API key hashing key. Only applicable when KMS is disabled. + +[applepay_decrypt_keys] +apple_pay_ppc = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE" # Payment Processing Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Payment Processing Certificate +apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" # Private key generated by Elliptic-curve prime256v1 curve. You can use `openssl ecparam -out private.key -name prime256v1 -genkey` to generate the private key +apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate +apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key generated by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key + +[applepay_merchant_configs] +common_merchant_identifier = "APPLE_PAY_COMMON_MERCHANT_IDENTIFIER" # Refer to config.example.toml to learn how you can generate this value +merchant_cert = "APPLE_PAY_MERCHANT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate +merchant_cert_key = "APPLE_PAY_MERCHANT_CERTIFICATE_KEY" # Private key generate by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key +applepay_endpoint = "https://apple-pay-gateway.apple.com/paymentservices/registerMerchant" # Apple pay gateway merchant endpoint + +[connector_onboarding.paypal] +enabled = true # boolean +client_id = "paypal_client_id" +client_secret = "paypal_client_secret" +partner_id = "paypal_partner_id" + +[connector_request_reference_id_config] +merchant_ids_send_payment_id_as_connector_request_id = ["merchant_id_1", "merchant_id_2", "etc.,"] + +# EmailClient configuration. Only applicable when the `email` feature flag is enabled. +[email] +sender_email = "example@example.com" # Sender email +aws_region = "" # AWS region used by AWS SES +base_url = "" # Dashboard base url used when adding links that should redirect to self, say https://app.hyperswitch.io for example +allowed_unverified_days = 1 # Number of days the api calls ( with jwt token ) can be made without verifying the email +active_email_client = "SES" # The currently active email client + +# Configuration for aws ses, applicable when the active email client is SES +[email.aws_ses] +email_role_arn = "" # The amazon resource name ( arn ) of the role which has permission to send emails +sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session. + +[events] +source = "logs" # The event sink to push events supports kafka or logs (stdout) + +[events.kafka] +brokers = [] # Kafka broker urls for bootstrapping the client +intent_analytics_topic = "topic" # Kafka topic to be used for PaymentIntent events +attempt_analytics_topic = "topic" # Kafka topic to be used for PaymentAttempt events +refund_analytics_topic = "topic" # Kafka topic to be used for Refund events +api_logs_topic = "topic" # Kafka topic to be used for incoming api events +connector_logs_topic = "topic" # Kafka topic to be used for connector api events +outgoing_webhook_logs_topic = "topic" # Kafka topic to be used for outgoing webhook events + +[file_upload_config] +bucket_name = "bucket" +region = "bucket_region" + +# This section provides configs for currency conversion api +[forex_api] +call_delay = 21600 # Api calls are made after every 6 hrs +local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 +local_fetch_retry_delay = 1000 # Retry delay for checking write condition +api_timeout = 20000 # Api timeouts once it crosses 20000 ms +api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api +fallback_api_key = "YOUR API KEY" # Api key for the fallback service +redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called + +[jwekey] # 3 priv/pub key pair +vault_encryption_key = "" # public key in pem format, corresponding private key in rust locker +rust_locker_encryption_key = "" # public key in pem format, corresponding private key in rust locker +vault_private_key = "" # private key in pem format, corresponding public key in rust locker + +# KMS configuration. Only applicable when the `kms` feature flag is enabled. +[kms] +key_id = "" # The AWS key ID used by the KMS SDK for decrypting data. +region = "" # The AWS region used by the KMS SDK for decrypting data. + +# Locker settings contain details for accessing a card locker, a +# PCI Compliant storage entity which stores payment method information +# like card details +[locker] +host = "" # Locker host +host_rs = "" # Rust Locker host +mock_locker = true # Emulate a locker locally using Postgres +locker_signing_key_id = "1" # Key_id to sign basilisk hs locker +locker_enabled = true # Boolean to enable or disable saving cards in locker +redis_temp_locker_encryption_key = "redis_temp_locker_encryption_key" # Encryption key for redis temp locker + +[log.console] +enabled = true +level = "DEBUG" +log_format = "json" + +[log.file] +enabled = false +level = "DEBUG" +log_format = "json" + +# Telemetry configuration for metrics and traces +[log.telemetry] +traces_enabled = false # boolean [true or false], whether traces are enabled +metrics_enabled = false # boolean [true or false], whether metrics are enabled +ignore_errors = false # boolean [true or false], whether to ignore errors during traces or metrics pipeline setup +sampling_rate = 0.1 # decimal rate between 0.0 - 1.0 +otel_exporter_otlp_endpoint = "http://localhost:4317" # endpoint to send metrics and traces to, can include port number +otel_exporter_otlp_timeout = 5000 # timeout (in milliseconds) for sending metrics and traces +use_xray_generator = false # Set this to true for AWS X-ray compatible traces +route_to_trace = ["*/confirm"] + +[lock_settings] +delay_between_retries_in_milliseconds = 500 # Delay between retries in milliseconds +redis_lock_expiry_seconds = 180 # Seconds before the redis lock expires + +# Main SQL data store credentials +[master_database] +username = "db_user" # DB Username +password = "db_pass" # DB Password. Use base-64 encoded kms encrypted value here when kms is enabled +host = "localhost" # DB Host +port = 5432 # DB Port +dbname = "hyperswitch_db" # Name of Database +pool_size = 5 # Number of connections to keep open +connection_timeout = 10 # Timeout for database connection in seconds +queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 client + +[payment_link] +sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js" + +[payment_method_auth] +pm_auth_key = "pm_auth_key" # Payment method auth key used for authorization +redis_expiry = 900 # Redis expiry time in milliseconds + +[proxy] +http_url = "http://proxy_http_url" # Outgoing proxy http URL to proxy the HTTP traffic +https_url = "https://proxy_https_url" # Outgoing proxy https URL to proxy the HTTPS traffic + +# Redis credentials +[redis] +host = "127.0.0.1" +port = 6379 +pool_size = 5 # Number of connections to keep open +reconnect_max_attempts = 5 # Maximum number of reconnection attempts to make before failing. Set to 0 to retry forever. +reconnect_delay = 5 # Delay between reconnection attempts, in milliseconds +default_ttl = 300 # Default TTL for entries, in seconds +default_hash_ttl = 900 # Default TTL for hashes entries, in seconds +use_legacy_version = false # RESP protocol for fred crate (set this to true if using RESPv2 or redis version < 6) +stream_read_count = 1 # Default number of entries to read from stream if not provided in stream read options +auto_pipeline = true # Whether or not the client should automatically pipeline commands across tasks when possible. +disable_auto_backpressure = false # Whether or not to disable the automatic backpressure features when pipelining is enabled. +max_in_flight_commands = 5000 # The maximum number of in-flight commands (per connection) before backpressure will be applied. +default_command_timeout = 0 # An optional timeout to apply to all commands. +max_feed_count = 200 # The maximum number of frames that will be fed to a socket before flushing. +cluster_enabled = true # boolean +cluster_urls = ["redis.cluster.uri-1:8080", "redis.cluster.uri-2:4115"] # List of redis cluster urls + +# Replica SQL data store credentials +[replica_database] +username = "replica_user" # DB Username +password = "db_pass" # DB Password. Use base-64 encoded kms encrypted value here when kms is enabled +host = "localhost" # DB Host +port = 5432 # DB Port +dbname = "hyperswitch_db" # Name of Database +pool_size = 5 # Number of connections to keep open +connection_timeout = 10 # Timeout for database connection in seconds +queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 client + +[report_download_config] +dispute_function = "report_download_config_dispute_function" # Config to download dispute report +payment_function = "report_download_config_payment_function" # Config to download payment report +refund_function = "report_download_config_refund_function" # Config to download refund report +region = "report_download_config_region" # Region of the bucket + +# This section provides some secret values. +[secrets] +master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long. +admin_api_key = "test_admin" # admin API key for admin authentication. Only applicable when KMS is disabled. +kms_encrypted_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the admin_api_key. Only applicable when KMS is enabled. +jwt_secret = "secret" # JWT secret used for user authentication. Only applicable when KMS is disabled. +kms_encrypted_jwt_secret = "" # Base64-encoded (KMS encrypted) ciphertext of the jwt_secret. Only applicable when KMS is enabled. +recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication. Only applicable when KMS is disabled. +kms_encrypted_recon_admin_api_key = "" # Base64-encoded (KMS encrypted) ciphertext of the recon_admin_api_key. Only applicable when KMS is enabled + +# Server configuration +[server] +base_url = "https://server_base_url" +workers = 8 +port = 8080 +host = "127.0.0.1" +# This is the grace time (in seconds) given to the actix-server to stop the execution +# For more details: https://actix.rs/docs/server/#graceful-shutdown +shutdown_timeout = 30 +# HTTP Request body limit. Defaults to 32kB +request_body_limit = 32_768 diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml new file mode 100644 index 000000000000..4a858588b504 --- /dev/null +++ b/config/deployments/integration_test.toml @@ -0,0 +1,279 @@ +[bank_config] +eps.adyen.banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_tirol_bank_ag,posojilnica_bank_e_gen,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag" +eps.stripe.banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" +ideal.adyen.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" +online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,may_bank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" +online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" +online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" +open_banking_uk.adyen.banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,halifax,lloyds,monzo,nat_west,nationwide_bank,royal_bank_of_scotland,starling,tsb_bank,tesco_bank,ulster_bank,barclays,hsbc_bank,revolut,santander_przelew24,open_bank_success,open_bank_failure,open_bank_cancelled" +przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,banki_spbdzielcze,blik,bnp_paribas,boz,citi,credit_agricole,e_transfer_pocztowy24,getin_bank,idea_bank,inteligo,mbank_mtransfer,nest_przelew,noble_pay,pbac_z_ipko,plus_bank,santander_przelew24,toyota_bank,volkswagen_bank" + +[connectors] +aci.base_url = "https://eu-test.oppwa.com/" +adyen.base_url = "https://checkout-test.adyen.com/" +adyen.secondary_base_url = "https://pal-test.adyen.com/" +airwallex.base_url = "https://api-demo.airwallex.com/" +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/" +bitpay.base_url = "https://test.bitpay.com" +bluesnap.base_url = "https://sandbox.bluesnap.com/" +bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" +boku.base_url = "https://$-api4-stage.boku.com" +braintree.base_url = "https://api.sandbox.braintreegateway.com/" +braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql" +cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +checkout.base_url = "https://api.sandbox.checkout.com/" +coinbase.base_url = "https://api.commerce.coinbase.com" +cryptopay.base_url = "https://business-sandbox.cryptopay.me" +cybersource.base_url = "https://apitest.cybersource.com/" +dlocal.base_url = "https://sandbox.dlocal.com/" +dummyconnector.base_url = "http://localhost:8080/dummy-connector" +fiserv.base_url = "https://cert.api.fiservapps.com/" +forte.base_url = "https://sandbox.forte.net/api/v3" +globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" +globepay.base_url = "https://pay.globepay.co/" +gocardless.base_url = "https://api-sandbox.gocardless.com" +helcim.base_url = "https://api.helcim.com/" +iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +klarna.base_url = "https://api-na.playground.klarna.com/" +mollie.base_url = "https://api.mollie.com/v2/" +mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +multisafepay.base_url = "https://testapi.multisafepay.com/" +nexinets.base_url = "https://apitest.payengine.de/v1" +nmi.base_url = "https://secure.nmi.com/" +noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" +nuvei.base_url = "https://ppp-test.nuvei.com/" +opayo.base_url = "https://pi-test.sagepay.com/" +opennode.base_url = "https://dev-api.opennode.com" +payeezy.base_url = "https://api-cert.payeezy.com/" +payme.base_url = "https://sandbox.payme.io/" +paypal.base_url = "https://api-m.sandbox.paypal.com/" +payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" +powertranz.base_url = "https://staging.ptranz.com/api/" +prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" +rapyd.base_url = "https://sandboxapi.rapyd.net" +shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" +riskified.base_url = "https://sandbox.riskified.com/api" +square.base_url = "https://connect.squareupsandbox.com/" +square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" +stax.base_url = "https://apiprod.fattlabs.com/" +stripe.base_url = "https://api.stripe.com/" +stripe.base_url_file_upload = "https://files.stripe.com/" +trustpay.base_url = "https://test-tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" +tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" +wise.base_url = "https://api.sandbox.transferwise.tech/" +worldline.base_url = "https://eu.sandbox.api-ingenico.com/" +worldpay.base_url = "https://try.access.worldpay.com/" +zen.base_url = "https://api.zen-test.com/" +zen.secondary_base_url = "https://secure.zen-test.com/" + +[dummy_connector] +enabled = true +assets_base_url = "https://app.hyperswitch.io/assets/TestProcessor/" +authorize_ttl = 36000 +default_return_url = "https://app.hyperswitch.io/" +discord_invite_url = "https://discord.gg/wJZ7DVW8mm" +payment_complete_duration = 500 +payment_complete_tolerance = 100 +payment_duration = 1000 +payment_retrieve_duration = 500 +payment_retrieve_tolerance = 100 +payment_tolerance = 100 +payment_ttl = 172800 +refund_duration = 1000 +refund_retrieve_duration = 500 +refund_retrieve_tolerance = 100 +refund_tolerance = 100 +refund_ttl = 172800 +slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-1k6cz4lee-SAJzhz6bjmpp4jZCDOtOIg" + +[frm] +enabled = true + +[connector_customer] +connector_list = "gocardless,stax,stripe" +payout_connector_list = "wise" + +[delayed_session_response] +connectors_with_delayed_session_response = "trustpay,payme" + +[mandates.supported_payment_methods] +bank_debit.ach.connector_list = "gocardless" +bank_debit.becs.connector_list = "gocardless" +bank_debit.sepa.connector_list = "gocardless" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource" +wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.paypal.connector_list = "adyen" + +[multiple_api_version_supported_connectors] +supported_connectors = "braintree" + +[payouts] +payout_eligibility = true + +[pm_filters.default] +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "GBP" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AT,BE,DK,FI,FR,DE,IE,IT,NL,NO,ES,SE,GB,US,CA", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } +paypal.country = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" +sofort = { country = "ES,GB,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } + +[pm_filters.adyen] +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "AU,NZ,ES,UK,FR,IT,CA,US", currency = "GBP" } +alfamart = { country = "ID", currency = "IDR" } +ali_pay = { country = "AU,N,JP,HK,SG,MY,TH,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } +ali_pay_hk = { country = "HK", currency = "HKD" } +alma = { country = "FR", currency = "EUR" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,UK,SE,NO,AK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +atome = { country = "MY,SG", currency = "MYR,SGD" } +bacs = { country = "UK", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } +bca_bank_transfer = { country = "ID", currency = "IDR" } +bizum = { country = "ES", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } +bni_va = { country = "ID", currency = "IDR" } +boleto = { country = "BR", currency = "BRL" } +bri_va = { country = "ID", currency = "IDR" } +cimb_va = { country = "ID", currency = "IDR" } +dana = { country = "ID", currency = "IDR" } +danamon_va = { country = "ID", currency = "IDR" } +eps = { country = "AT", currency = "EUR" } +family_mart = { country = "JP", currency = "JPY" } +gcash = { country = "PH", currency = "PHP" } +giropay = { country = "DE", currency = "EUR" } +go_pay = { country = "ID", currency = "IDR" } +google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } +indomaret = { country = "ID", currency = "IDR" } +kakao_pay = { country = "KR", currency = "KRW" } +klarna = { country = "AT,ES,UK,SE,NO,AT,NL,DE,CH,BE,FR,DK,FI,PT,IE,IT,PL,CA,US", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } +lawson = { country = "JP", currency = "JPY" } +mandiri_va = { country = "ID", currency = "IDR" } +mb_way = { country = "PT", currency = "EUR" } +mini_stop = { country = "JP", currency = "JPY" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +momo = { country = "VN", currency = "VND" } +momo_atm = { country = "VN", currency = "VND" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_fpx = { country = "MY", currency = "MYR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +online_banking_thailand = { country = "TH", currency = "THB" } +open_banking_uk = { country = "GB", currency = "GBP" } +oxxo = { country = "MX", currency = "MXN" } +pay_bright = { country = "CA", currency = "CAD" } +pay_easy = { country = "JP", currency = "JPY" } +pay_safe_card = { country = "AT,AU,BE,BR,BE,CA,HR,CY,CZ,DK,FI,FR,GE,DE,GI,HU,IS,IE,KW,LV,IE,LI,LT,LU,MT,MX,MD,ME,NL,NZ,NO,PY,PE,PL,PT,RO,SA,RS,SK,SI,ES,SE,CH,TR,UAE,UK,US,UY", currency = "EUR,AUD,BRL,CAD,CZK,DKK,GEL,GIP,HUF,ISK,KWD,CHF,MXN,MDL,NZD,NOK,PYG,PEN,PLN,RON,SAR,RSD,SEK,TRY,AED,GBP,USD,UYU" } +paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } +permata_bank_transfer = { country = "ID", currency = "IDR" } +seicomart = { country = "JP", currency = "JPY" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +seven_eleven = { country = "JP", currency = "JPY" } +sofort = { country = "ES,UK,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } +swish = { country = "SE", currency = "SEK" } +touch_n_go = { country = "MY", currency = "MYR" } +trustly = { country = "ES,UK,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +twint = { country = "CH", currency = "CHF" } +vipps = { country = "NO", currency = "NOK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD,CNY" } + +[pm_filters.authorizedotnet] +google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" + +[pm_filters.braintree] +paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" + +[pm_filters.forte] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.helcim] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.globepay] +ali_pay.currency = "GBP,CNY" +we_chat_pay.currency = "GBP,CNY" + +[pm_filters.klarna] +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.prophetpay] +card_redirect.currency = "USD" + +[pm_filters.stax] +ach = { country = "US", currency = "USD" } + +[pm_filters.stripe] +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "USD,CAD,GBP,AUD,NZD" } +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA" +cashapp = { country = "US", currency = "USD" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,CAD,CHF,CZK,DKK,EUR,GBP,NOK,NZD,PLN,SEK,USD" } +sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } + +[pm_filters.worldpay] +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" + +[pm_filters.zen] +boleto = { country = "BR", currency = "BRL" } +efecty = { country = "CO", currency = "COP" } +multibanco = { country = "PT", currency = "EUR" } +pago_efectivo = { country = "PE", currency = "PEN" } +pix = { country = "BR", currency = "BRL" } +pse = { country = "CO", currency = "COP" } +red_compra = { country = "CL", currency = "CLP" } +red_pagos = { country = "UY", currency = "UYU" } + +[temp_locker_enable_config] +bluesnap.payment_method = "card" +nuvei.payment_method = "card" +shift4.payment_method = "card" +stripe.payment_method = "bank_transfer" +bankofamerica = { payment_method = "card" } +cybersource = { payment_method = "card" } +nmi.payment_method = "card" + +[tokenization] +braintree = { long_lived_token = false, payment_method = "card" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } +gocardless = { long_lived_token = true, payment_method = "bank_debit" } +mollie = { long_lived_token = false, payment_method = "card" } +payme = { long_lived_token = false, payment_method = "card" } +square = { long_lived_token = false, payment_method = "card" } +stax = { long_lived_token = true, payment_method = "card,bank_debit" } +stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { list = "google_pay", type = "disable_only" } } + +[webhooks] +outgoing_enabled = true + +[webhook_source_verification_call] +connectors_with_webhook_source_verification_call = "paypal" diff --git a/config/deployments/production.toml b/config/deployments/production.toml new file mode 100644 index 000000000000..376ae579a507 --- /dev/null +++ b/config/deployments/production.toml @@ -0,0 +1,295 @@ +[bank_config] +eps.adyen.banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_tirol_bank_ag,posojilnica_bank_e_gen,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag" +eps.stripe.banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" +ideal.adyen.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" +online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,may_bank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" +online_banking_slovakia.adyen.banks = "e_platby_v_u_b,e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo,volksbank_gruppe,volkskredit_bank_ag,vr_bank_braunau" +online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" +open_banking_uk.adyen.banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,halifax,lloyds,monzo,nat_west,nationwide_bank,royal_bank_of_scotland,starling,tsb_bank,tesco_bank,ulster_bank,barclays,hsbc_bank,revolut,santander_przelew24,open_bank_success,open_bank_failure,open_bank_cancelled" +przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,banki_spbdzielcze,blik,bnp_paribas,boz,citi,credit_agricole,e_transfer_pocztowy24,getin_bank,idea_bank,inteligo,mbank_mtransfer,nest_przelew,noble_pay,pbac_z_ipko,plus_bank,santander_przelew24,toyota_bank,volkswagen_bank" + +[connector_customer] +connector_list = "stax,stripe,gocardless" +payout_connector_list = "wise" + +[connectors] +aci.base_url = "https://eu-test.oppwa.com/" +adyen.base_url = "https://checkout-test.adyen.com/" +adyen.secondary_base_url = "https://pal-test.adyen.com/" +airwallex.base_url = "https://api-demo.airwallex.com/" +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://api.merchant-services.bankofamerica.com/" +bitpay.base_url = "https://bitpay.com" +bluesnap.base_url = "https://ws.bluesnap.com/" +bluesnap.secondary_base_url = "https://pay.bluesnap.com/" +boku.base_url = "https://country-api4-stage.boku.com" +braintree.base_url = "https://api.sandbox.braintreegateway.com/" +braintree.secondary_base_url = "https://payments.braintree-api.com/graphql" +cashtocode.base_url = "https://cluster14.api.cashtocode.com" +checkout.base_url = "https://api.checkout.com/" +coinbase.base_url = "https://api.commerce.coinbase.com" +cryptopay.base_url = "https://business.cryptopay.me/" +cybersource.base_url = "https://api.cybersource.com/" +dlocal.base_url = "https://sandbox.dlocal.com/" +dummyconnector.base_url = "http://localhost:8080/dummy-connector" +fiserv.base_url = "https://cert.api.fiservapps.com/" +forte.base_url = "https://sandbox.forte.net/api/v3" +globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" +globepay.base_url = "https://pay.globepay.co/" +gocardless.base_url = "https://api.gocardless.com" +helcim.base_url = "https://api.helcim.com/" +iatapay.base_url = "https://iata-pay.iata.org/api/v1" +klarna.base_url = "https://api-na.playground.klarna.com/" +mollie.base_url = "https://api.mollie.com/v2/" +mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +multisafepay.base_url = "https://testapi.multisafepay.com/" +nexinets.base_url = "https://api.payengine.de/v1" +nmi.base_url = "https://secure.nmi.com/" +noon.base_url = "https://api.noonpayments.com/" +noon.key_mode = "Live" +nuvei.base_url = "https://ppp-test.nuvei.com/" +opayo.base_url = "https://pi-live.sagepay.com/" +opennode.base_url = "https://api.opennode.com" +payeezy.base_url = "https://api.payeezy.com/" +payme.base_url = "https://live.payme.io/" +paypal.base_url = "https://api-m.paypal.com/" +payu.base_url = "https://secure.payu.com/api/" +placetopay.base_url = "https://checkout.placetopay.com/rest/gateway" +powertranz.base_url = "https://staging.ptranz.com/api/" +prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" +rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://wh.riskified.com/api/" +shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" +square.base_url = "https://connect.squareupsandbox.com/" +square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" +stax.base_url = "https://apiprod.fattlabs.com/" +stripe.base_url = "https://api.stripe.com/" +stripe.base_url_file_upload = "https://files.stripe.com/" +trustpay.base_url = "https://tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" +tsys.base_url = "https://gateway.transit-pass.com/" +volt.base_url = "https://api.volt.io/" +wise.base_url = "https://api.sandbox.transferwise.tech/" +worldline.base_url = "https://eu.sandbox.api-ingenico.com/" +worldpay.base_url = "https://try.access.worldpay.com/" +zen.base_url = "https://api.zen.com/" +zen.secondary_base_url = "https://secure.zen.com/" + +[delayed_session_response] +connectors_with_delayed_session_response = "trustpay,payme" + +[dummy_connector] +assets_base_url = "https://app.hyperswitch.io/assets/TestProcessor/" +authorize_ttl = 36000 +default_return_url = "https://app.hyperswitch.io/" +discord_invite_url = "https://discord.gg/wJZ7DVW8mm" +enabled = false +payment_complete_duration = 500 +payment_complete_tolerance = 100 +payment_duration = 1000 +payment_retrieve_duration = 500 +payment_retrieve_tolerance = 100 +payment_tolerance = 100 +payment_ttl = 172800 +refund_duration = 1000 +refund_retrieve_duration = 500 +refund_retrieve_tolerance = 100 +refund_tolerance = 100 +refund_ttl = 172800 +slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-1k6cz4lee-SAJzhz6bjmpp4jZCDOtOIg" + +[frm] +enabled = false + +[mandates.supported_payment_methods] +bank_debit.ach.connector_list = "gocardless" +bank_debit.becs.connector_list = "gocardless" +bank_debit.sepa.connector_list = "gocardless" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource" +wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.paypal.connector_list = "adyen" + +[multiple_api_version_supported_connectors] +supported_connectors = "braintree" + +[payouts] +payout_eligibility = true + +[pm_filters.default] +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "AU,NZ,ES,UK,FR,IT,CA,US", currency = "GBP" } +ali_pay = { country = "AU,N,JP,HK,SG,MY,TH,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD,CNY" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +bacs = { country = "UK", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AT,ES,UK,SE,NO,AT,NL,DE,CH,BE,FR,DK,FI,PT,IE,IT,PL,CA,US", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } +mb_way = { country = "PT", currency = "EUR" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +pay_bright = { country = "CA", currency = "CAD" } +paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +sofort = { country = "ES,UK,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } +trustly = { country = "ES,UK,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD" } + +[pm_filters.adyen] +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "AU,CA,ES,FR,IT,NZ,UK,US", currency = "USD,AUD,CAD,NZD,GBP" } +alfamart = { country = "ID", currency = "IDR" } +ali_pay = { country = "AU,N,JP,HK,SG,MY,TH,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } +ali_pay_hk = { country = "HK", currency = "HKD" } +alma = { country = "FR", currency = "EUR" } +apple_pay = { country = "AE,AK,AM,AR,AT,AU,AZ,BE,BG,BH,BR,BY,CA,CH,CN,CO,CR,CY,CZ,DE,DK,EE,ES,FI,FO,FR,GB,GE,GG,GL,GR,HK,HR,HU,IE,IL,IM,IS,IT,JE,JO,JP,KW,KZ,LI,LT,LU,LV,MC,MD,ME,MO,MT,MX,MY,NL,NO,NZ,PE,PL,PS,PT,QA,RO,RS,SA,SE,SG,SI,SK,SM,TW,UA,UK,UM,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +atome = { country = "MY,SG", currency = "MYR,SGD" } +bacs = { country = "UK", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } +bca_bank_transfer = { country = "ID", currency = "IDR" } +bizum = { country = "ES", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } +bni_va = { country = "ID", currency = "IDR" } +boleto = { country = "BR", currency = "BRL" } +bri_va = { country = "ID", currency = "IDR" } +cimb_va = { country = "ID", currency = "IDR" } +dana = { country = "ID", currency = "IDR" } +danamon_va = { country = "ID", currency = "IDR" } +eps = { country = "AT", currency = "EUR" } +family_mart = { country = "JP", currency = "JPY" } +gcash = { country = "PH", currency = "PHP" } +giropay = { country = "DE", currency = "EUR" } +go_pay = { country = "ID", currency = "IDR" } +google_pay = { country = "AE,AG,AL,AO,AR,AS,AT,AU,AZ,BE,BG,BH,BR,BY,CA,CH,CL,CO,CY,CZ,DE,DK,DO,DZ,EE,EG,ES,FI,FR,GB,GR,HK,HR,HU,ID,IE,IL,IN,IS,IT,JO,JP,KE,KW,KZ,LB,LI,LK,LT,LU,LV,MT,MX,MY,NL,NO,NZ,OM,PA,PE,PH,PK,PL,PT,QA,RO,RU,SA,SE,SG,SI,SK,TH,TR,TW,UA,UK,US,UY,VN,ZA", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } +indomaret = { country = "ID", currency = "IDR" } +kakao_pay = { country = "KR", currency = "KRW" } +klarna = { country = "AT,BE,CA,CH,DE,DK,ES,FI,FR,GB,IE,IT,NL,NO,PL,PT,SE,UK,US", currency = "AUD,CAD,CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD" } +lawson = { country = "JP", currency = "JPY" } +mandiri_va = { country = "ID", currency = "IDR" } +mb_way = { country = "PT", currency = "EUR" } +mini_stop = { country = "JP", currency = "JPY" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +momo = { country = "VN", currency = "VND" } +momo_atm = { country = "VN", currency = "VND" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_fpx = { country = "MY", currency = "MYR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +online_banking_thailand = { country = "TH", currency = "THB" } +open_banking_uk = { country = "GB", currency = "GBP" } +oxxo = { country = "MX", currency = "MXN" } +pay_bright = { country = "CA", currency = "CAD" } +pay_easy = { country = "JP", currency = "JPY" } +pay_safe_card = { country = "AT,AU,BE,BR,BE,CA,HR,CY,CZ,DK,FI,FR,GE,DE,GI,HU,IS,IE,KW,LV,IE,LI,LT,LU,MT,MX,MD,ME,NL,NZ,NO,PY,PE,PL,PT,RO,SA,RS,SK,SI,ES,SE,CH,TR,UAE,UK,US,UY", currency = "EUR,AUD,BRL,CAD,CZK,DKK,GEL,GIP,HUF,ISK,KWD,CHF,MXN,MDL,NZD,NOK,PYG,PEN,PLN,RON,SAR,RSD,SEK,TRY,AED,GBP,USD,UYU" } +paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } +permata_bank_transfer = { country = "ID", currency = "IDR" } +seicomart = { country = "JP", currency = "JPY" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +seven_eleven = { country = "JP", currency = "JPY" } +sofort = { country = "AT,BE,CH,DE,ES,FI,FR,GB,IT,NL,PL,SE,UK", currency = "EUR" } +swish = { country = "SE", currency = "SEK" } +touch_n_go = { country = "MY", currency = "MYR" } +trustly = { country = "ES,UK,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +twint = { country = "CH", currency = "CHF" } +vipps = { country = "NO", currency = "NOK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD" } + +[pm_filters.authorizedotnet] +google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" + +[pm_filters.braintree] +paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" + +[pm_filters.forte] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.helcim] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.globepay] +ali_pay.currency = "GBP,CNY" +we_chat_pay.currency = "GBP,CNY" + +[pm_filters.klarna] +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.prophetpay] +card_redirect.currency = "USD" + +[pm_filters.stax] +ach = { country = "US", currency = "USD" } + +[pm_filters.stripe] +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "USD,CAD,GBP,AUD,NZD" } +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA" +cashapp = { country = "US", currency = "USD" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,CAD,CHF,CZK,DKK,EUR,GBP,NOK,NZD,PLN,SEK,USD" } +sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } + +[pm_filters.worldpay] +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" + +[pm_filters.zen] +boleto = { country = "BR", currency = "BRL" } +efecty = { country = "CO", currency = "COP" } +multibanco = { country = "PT", currency = "EUR" } +pago_efectivo = { country = "PE", currency = "PEN" } +pix = { country = "BR", currency = "BRL" } +pse = { country = "CO", currency = "COP" } +red_compra = { country = "CL", currency = "CLP" } +red_pagos = { country = "UY", currency = "UYU" } + +[temp_locker_enable_config] +bluesnap.payment_method = "card" +nuvei.payment_method = "card" +shift4.payment_method = "card" +stripe.payment_method = "bank_transfer" +bankofamerica = { payment_method = "card" } +cybersource = { payment_method = "card" } +nmi.payment_method = "card" + +[tokenization] +braintree = { long_lived_token = false, payment_method = "card" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } +gocardless = { long_lived_token = true, payment_method = "bank_debit" } +mollie = { long_lived_token = false, payment_method = "card" } +payme = { long_lived_token = false, payment_method = "card" } +square = { long_lived_token = false, payment_method = "card" } +stax = { long_lived_token = true, payment_method = "card,bank_debit" } +stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { list = "google_pay", type = "disable_only" } } + +[webhooks] +outgoing_enabled = true + +[webhook_source_verification_call] +connectors_with_webhook_source_verification_call = "paypal" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml new file mode 100644 index 000000000000..01616f3ecd08 --- /dev/null +++ b/config/deployments/sandbox.toml @@ -0,0 +1,296 @@ +[bank_config] +eps.adyen.banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_tirol_bank_ag,posojilnica_bank_e_gen,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag" +eps.stripe.banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" +ideal.adyen.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" +online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" +online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,may_bank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" +online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" +online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" +open_banking_uk.adyen.banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,halifax,lloyds,monzo,nat_west,nationwide_bank,royal_bank_of_scotland,starling,tsb_bank,tesco_bank,ulster_bank,barclays,hsbc_bank,revolut,santander_przelew24,open_bank_success,open_bank_failure,open_bank_cancelled" +przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,banki_spbdzielcze,blik,bnp_paribas,boz,citi,credit_agricole,e_transfer_pocztowy24,getin_bank,idea_bank,inteligo,mbank_mtransfer,nest_przelew,noble_pay,pbac_z_ipko,plus_bank,santander_przelew24,toyota_bank,volkswagen_bank" + +[connector_customer] +connector_list = "stax,stripe,gocardless" +payout_connector_list = "wise" + +[connectors] +aci.base_url = "https://eu-test.oppwa.com/" +adyen.base_url = "https://checkout-test.adyen.com/" +adyen.secondary_base_url = "https://pal-test.adyen.com/" +airwallex.base_url = "https://api-demo.airwallex.com/" +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/" +bitpay.base_url = "https://test.bitpay.com" +bluesnap.base_url = "https://sandbox.bluesnap.com/" +bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" +boku.base_url = "https://$-api4-stage.boku.com" +braintree.base_url = "https://api.sandbox.braintreegateway.com/" +braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql" +cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +checkout.base_url = "https://api.sandbox.checkout.com/" +coinbase.base_url = "https://api.commerce.coinbase.com" +cryptopay.base_url = "https://business-sandbox.cryptopay.me" +cybersource.base_url = "https://apitest.cybersource.com/" +dlocal.base_url = "https://sandbox.dlocal.com/" +dummyconnector.base_url = "http://localhost:8080/dummy-connector" +fiserv.base_url = "https://cert.api.fiservapps.com/" +forte.base_url = "https://sandbox.forte.net/api/v3" +globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" +globepay.base_url = "https://pay.globepay.co/" +gocardless.base_url = "https://api-sandbox.gocardless.com" +helcim.base_url = "https://api.helcim.com/" +iatapay.base_url = "https://iata-pay.iata.org/api/v1" +klarna.base_url = "https://api-na.playground.klarna.com/" +mollie.base_url = "https://api.mollie.com/v2/" +mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +multisafepay.base_url = "https://testapi.multisafepay.com/" +nexinets.base_url = "https://apitest.payengine.de/v1" +nmi.base_url = "https://secure.nmi.com/" +noon.base_url = "https://api-test.noonpayments.com/" +noon.key_mode = "Test" +nuvei.base_url = "https://ppp-test.nuvei.com/" +opayo.base_url = "https://pi-test.sagepay.com/" +opennode.base_url = "https://dev-api.opennode.com" +payeezy.base_url = "https://api-cert.payeezy.com/" +payme.base_url = "https://sandbox.payme.io/" +paypal.base_url = "https://api-m.sandbox.paypal.com/" +payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" +powertranz.base_url = "https://staging.ptranz.com/api/" +prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" +rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" +shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" +square.base_url = "https://connect.squareupsandbox.com/" +square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" +stax.base_url = "https://apiprod.fattlabs.com/" +stripe.base_url = "https://api.stripe.com/" +stripe.base_url_file_upload = "https://files.stripe.com/" +trustpay.base_url = "https://test-tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" +tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" +wise.base_url = "https://api.sandbox.transferwise.tech/" +worldline.base_url = "https://eu.sandbox.api-ingenico.com/" +worldpay.base_url = "https://try.access.worldpay.com/" +zen.base_url = "https://api.zen-test.com/" +zen.secondary_base_url = "https://secure.zen-test.com/" + +[delayed_session_response] +connectors_with_delayed_session_response = "trustpay,payme" + +[dummy_connector] +enabled = true +assets_base_url = "https://app.hyperswitch.io/assets/TestProcessor/" +authorize_ttl = 36000 +default_return_url = "https://app.hyperswitch.io/" +discord_invite_url = "https://discord.gg/wJZ7DVW8mm" +payment_complete_duration = 500 +payment_complete_tolerance = 100 +payment_duration = 1000 +payment_retrieve_duration = 500 +payment_retrieve_tolerance = 100 +payment_tolerance = 100 +payment_ttl = 172800 +refund_duration = 1000 +refund_retrieve_duration = 500 +refund_retrieve_tolerance = 100 +refund_tolerance = 100 +refund_ttl = 172800 +slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-1k6cz4lee-SAJzhz6bjmpp4jZCDOtOIg" + +[frm] +enabled = true + +[mandates.supported_payment_methods] +bank_debit.ach.connector_list = "gocardless" +bank_debit.becs.connector_list = "gocardless" +bank_debit.sepa.connector_list = "gocardless" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource" +wallet.google_pay.connector_list = "stripe,adyen,cybersource" +wallet.paypal.connector_list = "adyen" + +[multiple_api_version_supported_connectors] +supported_connectors = "braintree" + +[payouts] +payout_eligibility = true + +[pm_filters.default] +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "AU,NZ,ES,UK,FR,IT,CA,US", currency = "GBP" } +ali_pay = { country = "AU,N,JP,HK,SG,MY,TH,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +bacs = { country = "UK", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AT,ES,UK,SE,NO,AT,NL,DE,CH,BE,FR,DK,FI,PT,IE,IT,PL,CA,US", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } +mb_way = { country = "PT", currency = "EUR" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +pay_bright = { country = "CA", currency = "CAD" } +paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +sofort = { country = "ES,UK,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } +trustly = { country = "ES,UK,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD" } + +[pm_filters.adyen] +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "AU,NZ,ES,UK,FR,IT,CA,US", currency = "GBP" } +alfamart = { country = "ID", currency = "IDR" } +ali_pay = { country = "AU,N,JP,HK,SG,MY,TH,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } +ali_pay_hk = { country = "HK", currency = "HKD" } +alma = { country = "FR", currency = "EUR" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,UK,SE,NO,AK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +atome = { country = "MY,SG", currency = "MYR,SGD" } +bacs = { country = "UK", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } +bca_bank_transfer = { country = "ID", currency = "IDR" } +bizum = { country = "ES", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } +bni_va = { country = "ID", currency = "IDR" } +boleto = { country = "BR", currency = "BRL" } +bri_va = { country = "ID", currency = "IDR" } +cimb_va = { country = "ID", currency = "IDR" } +dana = { country = "ID", currency = "IDR" } +danamon_va = { country = "ID", currency = "IDR" } +eps = { country = "AT", currency = "EUR" } +family_mart = { country = "JP", currency = "JPY" } +gcash = { country = "PH", currency = "PHP" } +giropay = { country = "DE", currency = "EUR" } +go_pay = { country = "ID", currency = "IDR" } +google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } +indomaret = { country = "ID", currency = "IDR" } +kakao_pay = { country = "KR", currency = "KRW" } +klarna = { country = "AT,ES,UK,SE,NO,AT,NL,DE,CH,BE,FR,DK,FI,PT,IE,IT,PL,CA,US", currency = "USD,GBP,EUR,CHF,DKK,SEK,NOK,AUD,PLN,CAD" } +lawson = { country = "JP", currency = "JPY" } +mandiri_va = { country = "ID", currency = "IDR" } +mb_way = { country = "PT", currency = "EUR" } +mini_stop = { country = "JP", currency = "JPY" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +momo = { country = "VN", currency = "VND" } +momo_atm = { country = "VN", currency = "VND" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_fpx = { country = "MY", currency = "MYR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +online_banking_thailand = { country = "TH", currency = "THB" } +open_banking_uk = { country = "GB", currency = "GBP" } +oxxo = { country = "MX", currency = "MXN" } +pay_bright = { country = "CA", currency = "CAD" } +pay_easy = { country = "JP", currency = "JPY" } +pay_safe_card = { country = "AT,AU,BE,BR,BE,CA,HR,CY,CZ,DK,FI,FR,GE,DE,GI,HU,IS,IE,KW,LV,IE,LI,LT,LU,MT,MX,MD,ME,NL,NZ,NO,PY,PE,PL,PT,RO,SA,RS,SK,SI,ES,SE,CH,TR,UAE,UK,US,UY", currency = "EUR,AUD,BRL,CAD,CZK,DKK,GEL,GIP,HUF,ISK,KWD,CHF,MXN,MDL,NZD,NOK,PYG,PEN,PLN,RON,SAR,RSD,SEK,TRY,AED,GBP,USD,UYU" } +paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,UK,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } +permata_bank_transfer = { country = "ID", currency = "IDR" } +seicomart = { country = "JP", currency = "JPY" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +seven_eleven = { country = "JP", currency = "JPY" } +sofort = { country = "ES,UK,SE,AT,NL,DE,CH,BE,FR,FI,IT,PL", currency = "EUR" } +swish = { country = "SE", currency = "SEK" } +touch_n_go = { country = "MY", currency = "MYR" } +trustly = { country = "ES,UK,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +twint = { country = "CH", currency = "CHF" } +vipps = { country = "NO", currency = "NOK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,UK,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD" } + +[pm_filters.authorizedotnet] +google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" + +[pm_filters.braintree] +paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" + +[pm_filters.forte] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.helcim] +credit.currency = "USD" +debit.currency = "USD" + +[pm_filters.globepay] +ali_pay.currency = "GBP,CNY" +we_chat_pay.currency = "GBP,CNY" + +[pm_filters.klarna] +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.prophetpay] +card_redirect.currency = "USD" + +[pm_filters.stax] +ach = { country = "US", currency = "USD" } + +[pm_filters.stripe] +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "US,CA,GB,AU,NZ,FR,ES", currency = "USD,CAD,GBP,AUD,NZD" } +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA" +cashapp = { country = "US", currency = "USD" } +eps = { country = "AT", currency = "EUR" } +giropay = { country = "DE", currency = "EUR" } +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +ideal = { country = "NL", currency = "EUR" } +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,CAD,CHF,CZK,DKK,EUR,GBP,NOK,NZD,PLN,SEK,USD" } +sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } + +[pm_filters.worldpay] +apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" +google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" + +[pm_filters.zen] +boleto = { country = "BR", currency = "BRL" } +efecty = { country = "CO", currency = "COP" } +multibanco = { country = "PT", currency = "EUR" } +pago_efectivo = { country = "PE", currency = "PEN" } +pix = { country = "BR", currency = "BRL" } +pse = { country = "CO", currency = "COP" } +red_compra = { country = "CL", currency = "CLP" } +red_pagos = { country = "UY", currency = "UYU" } + + +[temp_locker_enable_config] +bluesnap.payment_method = "card" +nuvei.payment_method = "card" +shift4.payment_method = "card" +stripe.payment_method = "bank_transfer" +bankofamerica = { payment_method = "card" } +cybersource = { payment_method = "card" } +nmi.payment_method = "card" + +[tokenization] +braintree = { long_lived_token = false, payment_method = "card" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } +gocardless = { long_lived_token = true, payment_method = "bank_debit" } +mollie = { long_lived_token = false, payment_method = "card" } +payme = { long_lived_token = false, payment_method = "card" } +square = { long_lived_token = false, payment_method = "card" } +stax = { long_lived_token = true, payment_method = "card,bank_debit" } +stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { list = "google_pay", type = "disable_only" } } + +[webhooks] +outgoing_enabled = true + +[webhook_source_verification_call] +connectors_with_webhook_source_verification_call = "paypal" diff --git a/config/deployments/scheduler/consumer.toml b/config/deployments/scheduler/consumer.toml new file mode 100644 index 000000000000..907e3b8297e3 --- /dev/null +++ b/config/deployments/scheduler/consumer.toml @@ -0,0 +1,11 @@ +# Scheduler settings provides a point to modify the behaviour of scheduler flow. +# It defines the the streams/queues name and configuration as well as event selection variables +[scheduler] +consumer_group = "scheduler_group" +graceful_shutdown_interval = 60000 # Specifies how much time to wait while re-attempting shutdown for a service (in milliseconds) +loop_interval = 3000 # Specifies how much time to wait before starting the defined behaviour of producer or consumer (in milliseconds)0 +stream = "scheduler_stream" + +[scheduler.consumer] +consumer_group = "scheduler_group" +disabled = false # This flag decides if the consumer should actively consume task diff --git a/config/deployments/scheduler/producer.toml b/config/deployments/scheduler/producer.toml new file mode 100644 index 000000000000..579466a23cc8 --- /dev/null +++ b/config/deployments/scheduler/producer.toml @@ -0,0 +1,14 @@ +# Scheduler settings provides a point to modify the behaviour of scheduler flow. +# It defines the the streams/queues name and configuration as well as event selection variables +[scheduler] +consumer_group = "scheduler_group" +graceful_shutdown_interval = 60000 # Specifies how much time to wait while re-attempting shutdown for a service (in milliseconds) +loop_interval = 30000 # Specifies how much time to wait before starting the defined behaviour of producer or consumer (in milliseconds) +stream = "scheduler_stream" + +[scheduler.producer] +batch_size = 50 # Specifies the batch size the producer will push under a single entry in the redis queue +lock_key = "producer_locking_key" # The following keys defines the producer lock that is created in redis with +lock_ttl = 160 # the ttl being the expiry (in seconds) +lower_fetch_limit = 900 # Lower limit for fetching entries from redis queue (in seconds) +upper_fetch_limit = 0 # Upper limit for fetching entries from the redis queue (in seconds)0 From f1fd0b101791f20980e21e8fd8bf10fac3179209 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 00:20:31 +0000 Subject: [PATCH 48/48] chore(version): 2024.01.25.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e300ccbb119..071f60d224f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.25.0 + +### Refactors + +- **configs:** Add configs for deployments to environments ([#3265](https://github.com/juspay/hyperswitch/pull/3265)) ([`77c1bbb`](https://github.com/juspay/hyperswitch/commit/77c1bbb5a3fe3244cd988ac1260a4a31ae7fcd20)) + +**Full Changelog:** [`2024.01.24.1...2024.01.25.0`](https://github.com/juspay/hyperswitch/compare/2024.01.24.1...2024.01.25.0) + +- - - + ## 2024.01.24.1 ### Features