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 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 10/10] 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