diff --git a/CHANGELOG.md b/CHANGELOG.md index 32bff5be6353..2fff3b4b230d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,94 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.06.20.0 + +### Features + +- **core:** Add logger for sessions call failure ([#5036](https://github.com/juspay/hyperswitch/pull/5036)) ([`397d221`](https://github.com/juspay/hyperswitch/commit/397d2212405d63dab17272f8c26e4b3a5fc47aa8)) +- **router:** Add payment method type duplication check for `google_pay` ([#5023](https://github.com/juspay/hyperswitch/pull/5023)) ([`7d46afd`](https://github.com/juspay/hyperswitch/commit/7d46afd9852a793befa5321835ae9413fafb3fd9)) + +### Bug Fixes + +- **connector:** Add local bank redirect type in compatibility layer, default the country to AT for Local Bank Redirect and add creds_identifier in access token ([#5038](https://github.com/juspay/hyperswitch/pull/5038)) ([`655b81d`](https://github.com/juspay/hyperswitch/commit/655b81d69743e64fb982a1e222d7a102a4b3459f)) +- **events:** Correct parsing of API events with user event_type for Clickhouse ([#5022](https://github.com/juspay/hyperswitch/pull/5022)) ([`2106a27`](https://github.com/juspay/hyperswitch/commit/2106a27f4059f7d562ae4c6d0d69cc3acbb43eca)) +- **logging:** Fix stack overflow on recording restricted keys ([#4423](https://github.com/juspay/hyperswitch/pull/4423)) ([`7208ca4`](https://github.com/juspay/hyperswitch/commit/7208ca4db4876015c5beadeacdcbaeeaba3b56e9)) + +### Refactors + +- **connector:** Add amount conversion framework for noon ([#4843](https://github.com/juspay/hyperswitch/pull/4843)) ([`8c7e1a3`](https://github.com/juspay/hyperswitch/commit/8c7e1a3b8bbb7ad7fef460659e9daaa13d71521b)) +- **core:** Reverts the payment method list filtering using constraint graph ([#5044](https://github.com/juspay/hyperswitch/pull/5044)) ([`e486641`](https://github.com/juspay/hyperswitch/commit/e486641c97e0a00835ed49257c2665293591901c)) +- **storage:** Remove `id` from payment intent, attempt and remove datamodel ext from payment intent ([#4923](https://github.com/juspay/hyperswitch/pull/4923)) ([`bec51a3`](https://github.com/juspay/hyperswitch/commit/bec51a35574fcee2473465dc30c8f0f85de18145)) + +**Full Changelog:** [`2024.06.19.0...2024.06.20.0`](https://github.com/juspay/hyperswitch/compare/2024.06.19.0...2024.06.20.0) + +- - - + +## 2024.06.19.0 + +### Features + +- **multitenancy:** Add tenant_id as a field for data pipeline and support individual database for clickhouse ([#4867](https://github.com/juspay/hyperswitch/pull/4867)) ([`776ddb8`](https://github.com/juspay/hyperswitch/commit/776ddb8c1afdd2f9f14980629a0a1d251df41fbc)) +- **router:** Override the `setup_future_usage` to `on_session` based on the merchant config ([#5016](https://github.com/juspay/hyperswitch/pull/5016)) ([`a7ad790`](https://github.com/juspay/hyperswitch/commit/a7ad7906d7e84fa59df3cfffd16dea8db300e675)) + +### Bug Fixes + +- **opensearch:** Handle index not present errors in search api ([#4965](https://github.com/juspay/hyperswitch/pull/4965)) ([`ae1edb0`](https://github.com/juspay/hyperswitch/commit/ae1edb061d38effeb12fd122b94e45fb768dd508)) +- **payment_methods:** Populate card fields while saving card again during metadata change condition ([#5019](https://github.com/juspay/hyperswitch/pull/5019)) ([`010e6fe`](https://github.com/juspay/hyperswitch/commit/010e6fe3eef9398224b6e96b2469ac350d5e13c0)) + +### Refactors + +- Move trait ConnectorIntegration to crate hyperswitch_interfaces ([#4946](https://github.com/juspay/hyperswitch/pull/4946)) ([`cbe3a6d`](https://github.com/juspay/hyperswitch/commit/cbe3a6d43e50ed5f13ecc5738c6a9ab2827b0a3c)) +- Add basic counter metrics for IMC ([#5006](https://github.com/juspay/hyperswitch/pull/5006)) ([`d2092dc`](https://github.com/juspay/hyperswitch/commit/d2092dcb0a5ccddf8248ffb33fae91280268610e)) + +### Miscellaneous Tasks + +- **docker-compose:** Pass correct configuration values for running SDK demo app ([#5012](https://github.com/juspay/hyperswitch/pull/5012)) ([`91c8af6`](https://github.com/juspay/hyperswitch/commit/91c8af6ef6d74cc3e0cb55c5f26ca1eae6907709)) + +**Full Changelog:** [`2024.06.17.0...2024.06.19.0`](https://github.com/juspay/hyperswitch/compare/2024.06.17.0...2024.06.19.0) + +- - - + +## 2024.06.17.0 + +### Features + +- **connector:** Added template code for datatrans ([#4890](https://github.com/juspay/hyperswitch/pull/4890)) ([`6582729`](https://github.com/juspay/hyperswitch/commit/658272904897f7cbc4d9a349278712f35a8d3e96)) + +### Bug Fixes + +- **users:** Magic link is not expiring after one usage ([#4971](https://github.com/juspay/hyperswitch/pull/4971)) ([`2852a3b`](https://github.com/juspay/hyperswitch/commit/2852a3ba156e3e2bd89d0a116990134268e7bee8)) + +### Miscellaneous Tasks + +- **process_tracker:** Use `const` instead of `String` for `business_status` ([#4849](https://github.com/juspay/hyperswitch/pull/4849)) ([`40dfad8`](https://github.com/juspay/hyperswitch/commit/40dfad89ac6e70a15321b3711ee4c05c3c2ff201)) +- Introduce RouterDataNew and FlowSpecificData models ([#4961](https://github.com/juspay/hyperswitch/pull/4961)) ([`656fb3e`](https://github.com/juspay/hyperswitch/commit/656fb3e09a159d1cf67b2f494673bc8ded5a7ae8)) +- Address Rust 1.79 clippy lints ([#5003](https://github.com/juspay/hyperswitch/pull/5003)) ([`edf919e`](https://github.com/juspay/hyperswitch/commit/edf919e142736a28588b0f7e40ce724ad0065777)) + +**Full Changelog:** [`2024.06.14.0...2024.06.17.0`](https://github.com/juspay/hyperswitch/compare/2024.06.14.0...2024.06.17.0) + +- - - + +## 2024.06.14.0 + +### Features + +- **connectors:** [Iatapay] add payment methods ([#4968](https://github.com/juspay/hyperswitch/pull/4968)) ([`0e059e7`](https://github.com/juspay/hyperswitch/commit/0e059e7d847b0c15ed120c72bb4902ac60e6f955)) +- **payment_methods:** Use Ephemeral auth for pm list and pm delete ([#4996](https://github.com/juspay/hyperswitch/pull/4996)) ([`ad7886a`](https://github.com/juspay/hyperswitch/commit/ad7886a6ff636f99e62601483c907f5c90954eb4)) +- **router:** Include the pre-routing connectors in Apple Pay retries ([#4952](https://github.com/juspay/hyperswitch/pull/4952)) ([`fb83661`](https://github.com/juspay/hyperswitch/commit/fb836618a66f57fca5c78aa1c2a255792ab1dfb4)) + +### Bug Fixes + +- **cypress:** Add `jwt_token` and `cookie` for routing test ([#4953](https://github.com/juspay/hyperswitch/pull/4953)) ([`0a86cdb`](https://github.com/juspay/hyperswitch/commit/0a86cdb5060679e9609c2db6d2f82b8edda1b374)) + +### Refactors + +- **core:** Make country aggregator nodes weak for list payment methods ([#4994](https://github.com/juspay/hyperswitch/pull/4994)) ([`18493bd`](https://github.com/juspay/hyperswitch/commit/18493bd8f03b933b15bc3c40b3501222587fc59f)) + +**Full Changelog:** [`2024.06.13.0...2024.06.14.0`](https://github.com/juspay/hyperswitch/compare/2024.06.13.0...2024.06.14.0) + +- - - + ## 2024.06.13.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index 77f9b33ddc4f..abb65b07a3f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3677,10 +3677,12 @@ dependencies = [ "common_utils", "diesel_models", "error-stack", + "futures 0.3.30", "http 0.2.12", "masking", "mime", "router_derive", + "router_env", "serde", "serde_json", "serde_with", @@ -3695,11 +3697,22 @@ name = "hyperswitch_interfaces" version = "0.1.0" dependencies = [ "async-trait", + "bytes 1.6.0", "common_utils", "dyn-clone", + "http 0.2.12", + "hyperswitch_domain_models", "masking", + "mime", + "once_cell", + "reqwest", + "router_derive", + "router_env", "serde", + "serde_json", + "storage_impl", "thiserror", + "time", ] [[package]] diff --git a/config/config.example.toml b/config/config.example.toml index 81516ee8b6bf..332ab4bc00be 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -186,6 +186,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -276,6 +277,7 @@ cards = [ "braintree", "checkout", "cybersource", + "datatrans", "globalpay", "globepay", "gocardless", @@ -639,6 +641,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant \ No newline at end of file diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 9ab790b8ee8e..162444c90988 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -256,6 +256,7 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data. [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 5abbc870fd01..f5f69568da69 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -40,6 +40,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index b4e02172dfcc..bbaf22067b50 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -44,6 +44,7 @@ checkout.base_url = "https://api.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" +datatrans.base_url = "https://api.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index a8ddbc55f5fb..de274ad94114 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -44,6 +44,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" diff --git a/config/development.toml b/config/development.toml index 9a40a99cfce6..c2f877206781 100644 --- a/config/development.toml +++ b/config/development.toml @@ -109,6 +109,7 @@ cards = [ "coinbase", "cryptopay", "cybersource", + "datatrans", "dlocal", "dummyconnector", "ebanx", @@ -188,6 +189,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -648,6 +650,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 2cd93eade017..328dd30520b2 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -125,6 +125,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -202,6 +203,7 @@ cards = [ "coinbase", "cryptopay", "cybersource", + "datatrans", "dlocal", "dummyconnector", "ebanx", @@ -502,6 +504,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file diff --git a/crates/analytics/docs/README.md b/crates/analytics/docs/README.md index b822edd810ec..da9a3c79f1f6 100644 --- a/crates/analytics/docs/README.md +++ b/crates/analytics/docs/README.md @@ -101,4 +101,18 @@ Here's an example of how to do this: [default.features] audit_trail=true system_metrics=true -``` \ No newline at end of file +global_search=true +``` + +## Viewing the data on OpenSearch Dashboard + +To view the data on the OpenSearch dashboard perform the following steps: + +- Go to the OpenSearch Dashboard home and click on `Stack Management` under the Management tab +- Select `Index Patterns` +- Click on `Create index pattern` +- Define an index pattern with the same name that matches your indices and click on `Next Step` +- Select a time field that will be used for time-based queries +- Save the index pattern + +Now, head on to the `Discover` tab, to select the newly created index pattern and query the data \ No newline at end of file diff --git a/crates/analytics/src/api_event/core.rs b/crates/analytics/src/api_event/core.rs index 7225a6322d47..c7d6d9ac3394 100644 --- a/crates/analytics/src/api_event/core.rs +++ b/crates/analytics/src/api_event/core.rs @@ -12,6 +12,7 @@ use common_utils::errors::ReportSwitchExt; use error_stack::ResultExt; use router_env::{ instrument, logger, + metrics::add_attributes, tracing::{self, Instrument}, }; @@ -135,10 +136,10 @@ pub async fn get_api_event_metrics( .change_context(AnalyticsError::UnknownError)? { let data = data?; - let attributes = &[ - metrics::request::add_attributes("metric_type", metric.to_string()), - metrics::request::add_attributes("source", pool.to_string()), - ]; + let attributes = &add_attributes([ + ("metric_type", metric.to_string()), + ("source", pool.to_string()), + ]); let value = u64::try_from(data.len()); if let Ok(val) = value { diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 64064c09c880..fd1746c4b9ee 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -35,6 +35,7 @@ pub type ClickhouseResult = error_stack::Result; #[derive(Clone, Debug)] pub struct ClickhouseClient { pub config: Arc, + pub database: String, } #[derive(Clone, Debug, serde::Deserialize)] @@ -42,7 +43,6 @@ pub struct ClickhouseConfig { username: String, password: Option, host: String, - database_name: String, } impl Default for ClickhouseConfig { @@ -51,7 +51,6 @@ impl Default for ClickhouseConfig { username: "default".to_string(), password: None, host: "http://localhost:8123".to_string(), - database_name: "default".to_string(), } } } @@ -63,7 +62,7 @@ impl ClickhouseClient { let params = CkhQuery { date_time_output_format: String::from("iso"), output_format_json_quote_64bit_integers: 0, - database: self.config.database_name.clone(), + database: self.database.clone(), }; let response = client .post(&self.config.host) diff --git a/crates/analytics/src/disputes/core.rs b/crates/analytics/src/disputes/core.rs index dfba5fea1126..d2279180255d 100644 --- a/crates/analytics/src/disputes/core.rs +++ b/crates/analytics/src/disputes/core.rs @@ -11,6 +11,7 @@ use api_models::analytics::{ use error_stack::ResultExt; use router_env::{ logger, + metrics::add_attributes, tracing::{self, Instrument}, }; @@ -70,10 +71,10 @@ pub async fn get_metrics( .change_context(AnalyticsError::UnknownError)? { let data = data?; - let attributes = &[ - metrics::request::add_attributes("metric_type", metric.to_string()), - metrics::request::add_attributes("source", pool.to_string()), - ]; + let attributes = &add_attributes([ + ("metric_type", metric.to_string()), + ("source", pool.to_string()), + ]); let value = u64::try_from(data.len()); if let Ok(val) = value { diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index f658f6790972..5c269a4bb186 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -601,22 +601,30 @@ impl AnalyticsProvider { } } - pub async fn from_conf(config: &AnalyticsConfig, tenant: &str) -> Self { + pub async fn from_conf( + config: &AnalyticsConfig, + tenant: &dyn storage_impl::config::ClickHouseConfig, + ) -> Self { match config { - AnalyticsConfig::Sqlx { sqlx } => Self::Sqlx(SqlxClient::from_conf(sqlx, tenant).await), + AnalyticsConfig::Sqlx { sqlx } => { + Self::Sqlx(SqlxClient::from_conf(sqlx, tenant.get_schema()).await) + } AnalyticsConfig::Clickhouse { clickhouse } => Self::Clickhouse(ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }), AnalyticsConfig::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh( - SqlxClient::from_conf(sqlx, tenant).await, + SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }, ), AnalyticsConfig::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx( - SqlxClient::from_conf(sqlx, tenant).await, + SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), + database: tenant.get_clickhouse_database().to_string(), }, ), } diff --git a/crates/analytics/src/metrics/request.rs b/crates/analytics/src/metrics/request.rs index 3d1a78808f34..39375d391a3e 100644 --- a/crates/analytics/src/metrics/request.rs +++ b/crates/analytics/src/metrics/request.rs @@ -1,9 +1,6 @@ -pub fn add_attributes>( - key: &'static str, - value: T, -) -> router_env::opentelemetry::KeyValue { - router_env::opentelemetry::KeyValue::new(key, value) -} +use std::time; + +use router_env::metrics::add_attributes; #[inline] pub async fn record_operation_time( @@ -17,10 +14,10 @@ where T: ToString, { let (result, time) = time_future(future).await; - let attributes = &[ - add_attributes("metric_name", metric_name.to_string()), - add_attributes("source", source.to_string()), - ]; + let attributes = &add_attributes([ + ("metric_name", metric_name.to_string()), + ("source", source.to_string()), + ]); let value = time.as_secs_f64(); metric.record(&super::CONTEXT, value, attributes); @@ -28,8 +25,6 @@ where result } -use std::time; - #[inline] pub async fn time_future(future: F) -> (R, time::Duration) where diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index 7b19ba0ed06d..e8f87aaef2e0 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -76,6 +76,8 @@ pub enum OpenSearchError { ResponseError, #[error("Opensearch query building error")] QueryBuildingError, + #[error("Opensearch deserialisation error")] + DeserialisationError, } impl ErrorSwitch for QueryBuildingError { @@ -111,6 +113,12 @@ impl ErrorSwitch for OpenSearchError { "Query building error", None, )), + Self::DeserialisationError => ApiErrorResponse::InternalServerError(ApiError::new( + "IR", + 0, + "Deserialisation error", + None, + )), } } } diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs index a3f24b65a17e..2508866626a3 100644 --- a/crates/analytics/src/payments/core.rs +++ b/crates/analytics/src/payments/core.rs @@ -13,6 +13,7 @@ use common_utils::errors::CustomResult; use error_stack::ResultExt; use router_env::{ instrument, logger, + metrics::add_attributes, tracing::{self, Instrument}, }; @@ -120,10 +121,10 @@ pub async fn get_metrics( match task_type { TaskType::MetricTask(metric, data) => { let data = data?; - let attributes = &[ - metrics::request::add_attributes("metric_type", metric.to_string()), - metrics::request::add_attributes("source", pool.to_string()), - ]; + let attributes = &add_attributes([ + ("metric_type", metric.to_string()), + ("source", pool.to_string()), + ]); let value = u64::try_from(data.len()); if let Ok(val) = value { @@ -172,10 +173,10 @@ pub async fn get_metrics( } TaskType::DistributionTask(distribution, data) => { let data = data?; - let attributes = &[ - metrics::request::add_attributes("distribution_type", distribution.to_string()), - metrics::request::add_attributes("source", pool.to_string()), - ]; + let attributes = &add_attributes([ + ("distribution_type", distribution.to_string()), + ("source", pool.to_string()), + ]); let value = u64::try_from(data.len()); if let Ok(val) = value { diff --git a/crates/analytics/src/query.rs b/crates/analytics/src/query.rs index 9a0d4ec62c69..1ab79f07e434 100644 --- a/crates/analytics/src/query.rs +++ b/crates/analytics/src/query.rs @@ -317,6 +317,7 @@ impl std::fmt::Display for Order { // "count", // Order::Descending, // ) +#[allow(dead_code)] #[derive(Debug)] pub struct TopN { pub columns: String, diff --git a/crates/analytics/src/refunds/core.rs b/crates/analytics/src/refunds/core.rs index b53d482e620a..57480c7cec77 100644 --- a/crates/analytics/src/refunds/core.rs +++ b/crates/analytics/src/refunds/core.rs @@ -11,6 +11,7 @@ use api_models::analytics::{ use error_stack::ResultExt; use router_env::{ logger, + metrics::add_attributes, tracing::{self, Instrument}, }; @@ -69,10 +70,10 @@ pub async fn get_metrics( .change_context(AnalyticsError::UnknownError)? { let data = data?; - let attributes = &[ - metrics::request::add_attributes("metric_type", metric.to_string()), - metrics::request::add_attributes("source", pool.to_string()), - ]; + let attributes = &add_attributes([ + ("metric_type", metric.to_string()), + ("source", pool.to_string()), + ]); let value = u64::try_from(data.len()); if let Ok(val) = value { diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs index dc802ff69486..8810dc1e3a1e 100644 --- a/crates/analytics/src/search.rs +++ b/crates/analytics/src/search.rs @@ -4,7 +4,7 @@ use api_models::analytics::search::{ }; use common_utils::errors::{CustomResult, ReportSwitchExt}; use error_stack::ResultExt; -use serde_json::Value; +use router_env::tracing; use strum::IntoEnumIterator; use crate::opensearch::{ @@ -22,27 +22,59 @@ pub async fn msearch_results( .add_filter_clause("merchant_id".to_string(), merchant_id.to_string()) .switch()?; - let response_body = client + let response_text: OpenMsearchOutput = client .execute(query_builder) .await .change_context(OpenSearchError::ConnectionError)? - .json::>() + .text() .await - .change_context(OpenSearchError::ResponseError)?; + .change_context(OpenSearchError::ResponseError) + .and_then(|body: String| { + serde_json::from_str::(&body) + .change_context(OpenSearchError::DeserialisationError) + .attach_printable(body.clone()) + })?; + + let response_body: OpenMsearchOutput = response_text; Ok(response_body .responses .into_iter() .zip(SearchIndex::iter()) - .map(|(index_hit, index)| GetSearchResponse { - count: index_hit.hits.total.value, - index, - hits: index_hit - .hits - .hits - .into_iter() - .map(|hit| hit._source) - .collect(), + .map(|(index_hit, index)| match index_hit { + OpensearchOutput::Success(success) => { + if success.status == 200 { + GetSearchResponse { + count: success.hits.total.value, + index, + hits: success + .hits + .hits + .into_iter() + .map(|hit| hit.source) + .collect(), + } + } else { + tracing::error!("Unexpected status code: {}", success.status,); + GetSearchResponse { + count: 0, + index, + hits: Vec::new(), + } + } + } + OpensearchOutput::Error(error) => { + tracing::error!( + index = ?index, + error_response = ?error, + "Search error" + ); + GetSearchResponse { + count: 0, + index, + hits: Vec::new(), + } + } }) .collect()) } @@ -65,22 +97,54 @@ pub async fn search_results( .set_offset_n_count(search_req.offset, search_req.count) .switch()?; - let response_body = client + let response_text: OpensearchOutput = client .execute(query_builder) .await .change_context(OpenSearchError::ConnectionError)? - .json::>() + .text() .await - .change_context(OpenSearchError::ResponseError)?; + .change_context(OpenSearchError::ResponseError) + .and_then(|body: String| { + serde_json::from_str::(&body) + .change_context(OpenSearchError::DeserialisationError) + .attach_printable(body.clone()) + })?; + + let response_body: OpensearchOutput = response_text; - Ok(GetSearchResponse { - count: response_body.hits.total.value, - index: req.index, - hits: response_body - .hits - .hits - .into_iter() - .map(|hit| hit._source) - .collect(), - }) + match response_body { + OpensearchOutput::Success(success) => { + if success.status == 200 { + Ok(GetSearchResponse { + count: success.hits.total.value, + index: req.index, + hits: success + .hits + .hits + .into_iter() + .map(|hit| hit.source) + .collect(), + }) + } else { + tracing::error!("Unexpected status code: {}", success.status); + Ok(GetSearchResponse { + count: 0, + index: req.index, + hits: Vec::new(), + }) + } + } + OpensearchOutput::Error(error) => { + tracing::error!( + index = ?req.index, + error_response = ?error, + "Search error" + ); + Ok(GetSearchResponse { + count: 0, + index: req.index, + hits: Vec::new(), + }) + } + } } diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs index 6f6a3f228128..c29bb0e9f711 100644 --- a/crates/api_models/src/analytics/search.rs +++ b/crates/api_models/src/analytics/search.rs @@ -48,19 +48,40 @@ pub struct GetSearchResponse { } #[derive(Debug, serde::Deserialize)] -pub struct OpenMsearchOutput { - pub responses: Vec>, +pub struct OpenMsearchOutput { + pub responses: Vec, } #[derive(Debug, serde::Deserialize)] -pub struct OpensearchOutput { - pub hits: OpensearchResults, +#[serde(untagged)] +pub enum OpensearchOutput { + Success(OpensearchSuccess), + Error(OpensearchError), } #[derive(Debug, serde::Deserialize)] -pub struct OpensearchResults { +pub struct OpensearchError { + pub error: OpensearchErrorDetails, + pub status: u16, +} + +#[derive(Debug, serde::Deserialize)] +pub struct OpensearchErrorDetails { + #[serde(rename = "type")] + pub error_type: String, + pub reason: String, +} + +#[derive(Debug, serde::Deserialize)] +pub struct OpensearchSuccess { + pub status: u16, + pub hits: OpensearchHits, +} + +#[derive(Debug, serde::Deserialize)] +pub struct OpensearchHits { pub total: OpensearchResultsTotal, - pub hits: Vec>, + pub hits: Vec, } #[derive(Debug, serde::Deserialize)] @@ -69,6 +90,7 @@ pub struct OpensearchResultsTotal { } #[derive(Debug, serde::Deserialize)] -pub struct OpensearchHits { - pub _source: T, +pub struct OpensearchHit { + #[serde(rename = "_source")] + pub source: Value, } diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 1b976d68a197..514e44337c17 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -89,6 +89,7 @@ pub enum Connector { Coinbase, Cryptopay, Cybersource, + // Datatrans, Dlocal, Ebanx, Fiserv, @@ -251,6 +252,7 @@ impl Connector { | Self::Plaid | Self::Riskified | Self::Threedsecureio + // | Self::Datatrans | Self::Netcetera | Self::Noon | Self::Stripe => false, diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index c41f52a93ea5..287bafaace8f 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -24,7 +24,6 @@ use crate::user::{ impl ApiEventMetric for DashboardEntryResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::User { - merchant_id: self.merchant_id.clone(), user_id: self.user_id.clone(), }) } @@ -34,7 +33,6 @@ impl ApiEventMetric for DashboardEntryResponse { impl ApiEventMetric for VerifyTokenResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::User { - merchant_id: self.merchant_id.clone(), user_id: self.user_email.peek().to_string(), }) } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 5d6b9c14895b..f3a3b02155d3 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -128,6 +128,7 @@ pub enum RoutableConnectors { Coinbase, Cryptopay, Cybersource, + // Datatrans, Dlocal, Ebanx, Fiserv, diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index def64e35db07..58a881db5905 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -9,8 +9,9 @@ license.workspace = true [features] signals = ["dep:signal-hook-tokio", "dep:signal-hook", "dep:tokio", "dep:router_env", "dep:futures"] -async_ext = ["dep:futures", "dep:async-trait"] +async_ext = ["dep:async-trait", "dep:futures"] logs = ["dep:router_env"] +metrics = ["dep:router_env", "dep:futures"] [dependencies] async-trait = { version = "0.1.79", optional = true } @@ -29,7 +30,9 @@ rand = "0.8.5" regex = "1.10.4" reqwest = { version = "0.11.27", features = ["json", "native-tls", "gzip", "multipart"] } ring = { version = "0.17.8", features = ["std", "wasm32_unknown_unknown_js"] } +rust_decimal = "1.35" rustc-hash = "1.1.0" +semver = { version = "1.0.22", features = ["serde"] } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" serde_urlencoded = "0.7.1" @@ -38,12 +41,10 @@ strum = { version = "0.26.2", features = ["derive"] } thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"], optional = true } -semver = { version = "1.0.22", features = ["serde"] } utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] } uuid = { version = "1.8.0", features = ["v7"] } # First party crates -rust_decimal = "1.35" rusty-money = { git = "https://github.com/varunsrin/rusty_money", rev = "bbc0150742a0fff905225ff11ee09388e9babdcc", features = ["iso", "crypto"] } common_enums = { version = "0.1.0", path = "../common_enums" } masking = { version = "0.1.0", path = "../masking" } diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 968ef22bf976..38c8997358dd 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -87,8 +87,8 @@ pub const MAX_TTL_FOR_EXTENDED_CARD_INFO: u16 = 60 * 60 * 2; /// Default tenant to be used when multitenancy is disabled pub const DEFAULT_TENANT: &str = "public"; -/// Global tenant to be used when multitenancy is enabled -pub const GLOBAL_TENANT: &str = "global"; +/// Default tenant to be used when multitenancy is disabled +pub const TENANT_HEADER: &str = "x-tenant-id"; /// Max Length for MerchantReferenceId pub const MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH: u8 = 64; diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 1052840dbc88..ef5502d8b5b2 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -29,8 +29,6 @@ pub enum ApiEventsType { customer_id: id_type::CustomerId, }, User { - //specified merchant_id will overridden on global defined - merchant_id: String, user_id: String, }, PaymentMethodList { diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index 4e515e3d6a74..ee0a474bcde0 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -30,6 +30,9 @@ pub mod static_cache; pub mod types; pub mod validation; +#[cfg(feature = "metrics")] +pub mod metrics; + /// Date-time utilities. pub mod date_time { #[cfg(feature = "async_ext")] diff --git a/crates/common_utils/src/metrics.rs b/crates/common_utils/src/metrics.rs new file mode 100644 index 000000000000..36e531c8556a --- /dev/null +++ b/crates/common_utils/src/metrics.rs @@ -0,0 +1,2 @@ +//! Utilities for metrics +pub mod utils; diff --git a/crates/common_utils/src/metrics/utils.rs b/crates/common_utils/src/metrics/utils.rs new file mode 100644 index 000000000000..71244ecc4fe4 --- /dev/null +++ b/crates/common_utils/src/metrics/utils.rs @@ -0,0 +1,33 @@ +//! metric utility functions + +use std::time; + +use router_env::opentelemetry; + +/// Record the time taken by the future to execute +#[inline] +pub async fn time_future(future: F) -> (R, time::Duration) +where + F: futures::Future, +{ + let start = time::Instant::now(); + let result = future.await; + let time_spent = start.elapsed(); + (result, time_spent) +} + +/// Record the time taken (in seconds) by the operation for the given context +#[inline] +pub async fn record_operation_time( + future: F, + metric: &opentelemetry::metrics::Histogram, + metric_context: &opentelemetry::Context, + key_value: &[opentelemetry::KeyValue], +) -> R +where + F: futures::Future, +{ + let (result, time) = time_future(future).await; + metric.record(metric_context, time.as_secs_f64(), key_value); + result +} diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 6bb286b42a95..fbafd889618a 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -8,9 +8,8 @@ use crate::{ }; #[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Serialize, Deserialize)] -#[diesel(table_name = payment_attempt)] +#[diesel(table_name = payment_attempt, primary_key(attempt_id, merchant_id))] pub struct PaymentAttempt { - pub id: i32, pub payment_id: String, pub merchant_id: String, pub attempt_id: String, diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index c54ff2a429e1..7fad83e7a0f1 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -7,9 +7,8 @@ use time::PrimitiveDateTime; use crate::{enums as storage_enums, schema::payment_intent}; #[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Serialize, Deserialize)] -#[diesel(table_name = payment_intent)] +#[diesel(table_name = payment_intent, primary_key(payment_id, merchant_id))] pub struct PaymentIntent { - pub id: i32, pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, @@ -233,10 +232,8 @@ pub struct PaymentIntentUpdateInternal { #[diesel(deserialize_as = super::OptionalDieselArray)] pub order_details: Option>, pub attempt_count: Option, - pub profile_id: Option, - merchant_decision: Option, - payment_confirm_source: Option, - + pub merchant_decision: Option, + pub payment_confirm_source: Option, pub updated_by: String, pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, @@ -244,7 +241,6 @@ pub struct PaymentIntentUpdateInternal { pub session_expiry: Option, pub fingerprint_id: Option, pub request_external_three_ds_authentication: Option, - pub charges: Option, pub frm_metadata: Option, } @@ -271,7 +267,6 @@ impl PaymentIntentUpdate { statement_descriptor_suffix, order_details, attempt_count, - profile_id, merchant_decision, payment_confirm_source, updated_by, @@ -281,7 +276,6 @@ impl PaymentIntentUpdate { session_expiry, fingerprint_id, request_external_three_ds_authentication, - charges, frm_metadata, } = self.into(); PaymentIntent { @@ -307,7 +301,6 @@ impl PaymentIntentUpdate { .or(source.statement_descriptor_suffix), order_details: order_details.or(source.order_details), attempt_count: attempt_count.unwrap_or(source.attempt_count), - profile_id: profile_id.or(source.profile_id), merchant_decision: merchant_decision.or(source.merchant_decision), payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source), updated_by, @@ -320,8 +313,6 @@ impl PaymentIntentUpdate { session_expiry: session_expiry.or(source.session_expiry), request_external_three_ds_authentication: request_external_three_ds_authentication .or(source.request_external_three_ds_authentication), - charges: charges.or(source.charges), - frm_metadata: frm_metadata.or(source.frm_metadata), ..source } diff --git a/crates/diesel_models/src/process_tracker.rs b/crates/diesel_models/src/process_tracker.rs index cd0dbed2c641..135c8e2b0554 100644 --- a/crates/diesel_models/src/process_tracker.rs +++ b/crates/diesel_models/src/process_tracker.rs @@ -76,8 +76,6 @@ impl ProcessTrackerNew { where T: Serialize + std::fmt::Debug, { - const BUSINESS_STATUS_PENDING: &str = "Pending"; - let current_time = common_utils::date_time::now(); Ok(Self { id: process_tracker_id.into(), @@ -91,7 +89,7 @@ impl ProcessTrackerNew { .encode_to_value() .change_context(errors::DatabaseError::Others) .attach_printable("Failed to serialize process tracker tracking data")?, - business_status: String::from(BUSINESS_STATUS_PENDING), + business_status: String::from(business_status::PENDING), status: storage_enums::ProcessTrackerStatus::New, event: vec![], created_at: current_time, @@ -227,3 +225,42 @@ mod tests { assert_eq!(enum_format, ProcessTrackerRunner::PaymentsSyncWorkflow); } } + +pub mod business_status { + /// Indicates that an irrecoverable error occurred during the workflow execution. + pub const GLOBAL_FAILURE: &str = "GLOBAL_FAILURE"; + + /// Task successfully completed by consumer. + /// A task that reaches this status should not be retried (rescheduled for execution) later. + pub const COMPLETED_BY_PT: &str = "COMPLETED_BY_PT"; + + /// An error occurred during the workflow execution which prevents further execution and + /// retries. + /// A task that reaches this status should not be retried (rescheduled for execution) later. + pub const FAILURE: &str = "FAILURE"; + + /// The resource associated with the task was removed, due to which further retries can/should + /// not be done. + pub const REVOKED: &str = "Revoked"; + + /// The task was executed for the maximum possible number of times without a successful outcome. + /// A task that reaches this status should not be retried (rescheduled for execution) later. + pub const RETRIES_EXCEEDED: &str = "RETRIES_EXCEEDED"; + + /// The outgoing webhook was successfully delivered in the initial attempt. + /// Further retries of the task are not required. + pub const INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL: &str = "INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL"; + + /// Indicates that an error occurred during the workflow execution. + /// This status is typically set by the workflow error handler. + /// A task that reaches this status should not be retried (rescheduled for execution) later. + pub const GLOBAL_ERROR: &str = "GLOBAL_ERROR"; + + /// The resource associated with the task has been significantly modified since the task was + /// created, due to which further retries of the current task are not required. + /// A task that reaches this status should not be retried (rescheduled for execution) later. + pub const RESOURCE_STATUS_MISMATCH: &str = "RESOURCE_STATUS_MISMATCH"; + + /// Business status set for newly created tasks. + pub const PENDING: &str = "Pending"; +} diff --git a/crates/diesel_models/src/query/user.rs b/crates/diesel_models/src/query/user.rs index dac515cb279d..2bd403a847b3 100644 --- a/crates/diesel_models/src/query/user.rs +++ b/crates/diesel_models/src/query/user.rs @@ -1,23 +1,9 @@ -use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::pii; -use diesel::{ - associations::HasTable, debug_query, result::Error as DieselError, ExpressionMethods, - JoinOnDsl, QueryDsl, -}; -use error_stack::report; -use router_env::logger; +use diesel::{associations::HasTable, ExpressionMethods}; pub mod sample_data; use crate::{ - errors::{self}, - query::generics, - schema::{ - user_roles::{self, dsl as user_roles_dsl}, - users::dsl as users_dsl, - }, - user::*, - user_role::UserRole, - PgPooledConn, StorageResult, + query::generics, schema::users::dsl as users_dsl, user::*, PgPooledConn, StorageResult, }; impl UserNew { @@ -90,27 +76,6 @@ impl User { .await } - pub async fn find_joined_users_and_roles_by_merchant_id( - conn: &PgPooledConn, - mid: &str, - ) -> StorageResult> { - let query = Self::table() - .inner_join(user_roles::table.on(user_roles_dsl::user_id.eq(users_dsl::user_id))) - .filter(user_roles_dsl::merchant_id.eq(mid.to_owned())); - - logger::debug!(query = %debug_query::(&query).to_string()); - - query - .get_results_async::<(Self, UserRole)>(conn) - .await - .map_err(|err| match err { - DieselError::NotFound => { - report!(err).change_context(errors::DatabaseError::NotFound) - } - _ => report!(err).change_context(errors::DatabaseError::Others), - }) - } - pub async fn find_users_by_user_ids( conn: &PgPooledConn, user_ids: Vec, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index ea356650c134..08d8f423b727 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -712,8 +712,7 @@ diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; - payment_attempt (id) { - id -> Int4, + payment_attempt (attempt_id, merchant_id) { #[max_length = 64] payment_id -> Varchar, #[max_length = 64] @@ -802,8 +801,7 @@ diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; - payment_intent (id) { - id -> Int4, + payment_intent (payment_id, merchant_id) { #[max_length = 64] payment_id -> Varchar, #[max_length = 64] diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index e64b5dbd4ad5..57c17f577b11 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -143,6 +143,7 @@ pub struct Tenant { pub base_url: String, pub schema: String, pub redis_key_prefix: String, + pub clickhouse_database: String, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/euclid/src/enums.rs b/crates/euclid/src/enums.rs index 8e65d23d5ea9..1aba6338d9d2 100644 --- a/crates/euclid/src/enums.rs +++ b/crates/euclid/src/enums.rs @@ -79,8 +79,6 @@ pub enum MandateAcceptanceType { pub enum PaymentType { SetupMandate, NonMandate, - NewMandate, - UpdateMandate, } #[derive( diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index c1db2a4fb995..0bbf0fe9bc2e 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -16,11 +16,12 @@ payouts = ["api_models/payouts"] # First party deps api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } common_enums = { version = "0.1.0", path = "../common_enums" } -common_utils = { version = "0.1.0", path = "../common_utils" } +common_utils = { version = "0.1.0", path = "../common_utils", features = ["async_ext", "metrics"] } masking = { version = "0.1.0", path = "../masking" } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } -cards = {version = "0.1.0", path = "../cards"} -router_derive = {version = "0.1.0", path = "../router_derive"} +cards = { version = "0.1.0", path = "../cards" } +router_derive = { version = "0.1.0", path = "../router_derive" } +router_env = { version = "0.1.0", path = "../router_env" } # Third party deps actix-web = "4.5.1" @@ -35,4 +36,4 @@ thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } url = { version = "2.5.0", features = ["serde"] } utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } - +futures = "0.3.30" diff --git a/crates/router/src/types/domain/behaviour.rs b/crates/hyperswitch_domain_models/src/behaviour.rs similarity index 97% rename from crates/router/src/types/domain/behaviour.rs rename to crates/hyperswitch_domain_models/src/behaviour.rs index db7159fad5aa..9976dd25a985 100644 --- a/crates/router/src/types/domain/behaviour.rs +++ b/crates/hyperswitch_domain_models/src/behaviour.rs @@ -1,6 +1,5 @@ use common_utils::errors::{CustomResult, ValidationError}; - -use crate::pii::Secret; +use masking::Secret; /// Trait for converting domain types to storage models #[async_trait::async_trait] diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index d9778353cdaa..00d5929a22ad 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -6,10 +6,15 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod router_data; +pub mod router_data_new; pub mod router_flow_types; pub mod router_request_types; pub mod router_response_types; +pub mod behaviour; +pub mod merchant_key_store; +pub mod type_encryption; + #[cfg(not(feature = "payouts"))] pub trait PayoutAttemptInterface {} diff --git a/crates/router/src/types/domain/merchant_key_store.rs b/crates/hyperswitch_domain_models/src/merchant_key_store.rs similarity index 96% rename from crates/router/src/types/domain/merchant_key_store.rs rename to crates/hyperswitch_domain_models/src/merchant_key_store.rs index bf5e90399957..29050b7eb496 100644 --- a/crates/router/src/types/domain/merchant_key_store.rs +++ b/crates/hyperswitch_domain_models/src/merchant_key_store.rs @@ -1,15 +1,13 @@ use common_utils::{ crypto::{Encryptable, GcmAes256}, custom_serde, date_time, + errors::{CustomResult, ValidationError}, }; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; -use crate::{ - errors::{CustomResult, ValidationError}, - types::domain::types::TypeEncryption, -}; +use crate::type_encryption::TypeEncryption; #[derive(Clone, Debug, serde::Serialize)] pub struct MerchantKeyStore { diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 089cb29aec5e..c09df82f87c9 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -11,7 +11,6 @@ use crate::RemoteStorageObject; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct PaymentIntent { - pub id: i32, pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ac0c74ea9134..c36e29bfe680 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1,14 +1,17 @@ use api_models::enums::Connector; use common_enums as storage_enums; -use common_utils::types::MinorUnit; +use common_utils::{ + errors::{CustomResult, ValidationError}, + types::MinorUnit, +}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use super::PaymentIntent; use crate::{ - errors, + behaviour, errors, mandates::{MandateDataType, MandateDetails}, - ForeignIDRef, + ForeignIDRef, RemoteStorageObject, }; #[async_trait::async_trait] @@ -107,7 +110,6 @@ pub trait PaymentAttemptInterface { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PaymentAttempt { - pub id: i32, pub payment_id: String, pub merchant_id: String, pub attempt_id: String, @@ -458,3 +460,164 @@ impl ForeignIDRef for PaymentAttempt { self.attempt_id.clone() } } + +use diesel_models::{ + PaymentIntent as DieselPaymentIntent, PaymentIntentNew as DieselPaymentIntentNew, +}; + +#[async_trait::async_trait] +impl behaviour::Conversion for PaymentIntent { + type DstType = DieselPaymentIntent; + type NewDstType = DieselPaymentIntentNew; + + async fn convert(self) -> CustomResult { + Ok(DieselPaymentIntent { + payment_id: self.payment_id, + merchant_id: self.merchant_id, + status: self.status, + amount: self.amount, + currency: self.currency, + amount_captured: self.amount_captured, + customer_id: self.customer_id, + description: self.description, + return_url: self.return_url, + metadata: self.metadata, + connector_id: self.connector_id, + shipping_address_id: self.shipping_address_id, + billing_address_id: self.billing_address_id, + statement_descriptor_name: self.statement_descriptor_name, + statement_descriptor_suffix: self.statement_descriptor_suffix, + created_at: self.created_at, + modified_at: self.modified_at, + last_synced: self.last_synced, + setup_future_usage: self.setup_future_usage, + off_session: self.off_session, + client_secret: self.client_secret, + active_attempt_id: self.active_attempt.get_id(), + business_country: self.business_country, + business_label: self.business_label, + order_details: self.order_details, + allowed_payment_method_types: self.allowed_payment_method_types, + connector_metadata: self.connector_metadata, + feature_metadata: self.feature_metadata, + attempt_count: self.attempt_count, + profile_id: self.profile_id, + merchant_decision: self.merchant_decision, + payment_link_id: self.payment_link_id, + payment_confirm_source: self.payment_confirm_source, + updated_by: self.updated_by, + surcharge_applicable: self.surcharge_applicable, + request_incremental_authorization: self.request_incremental_authorization, + incremental_authorization_allowed: self.incremental_authorization_allowed, + authorization_count: self.authorization_count, + fingerprint_id: self.fingerprint_id, + session_expiry: self.session_expiry, + request_external_three_ds_authentication: self.request_external_three_ds_authentication, + charges: self.charges, + frm_metadata: self.frm_metadata, + }) + } + + async fn convert_back( + storage_model: Self::DstType, + _key: &masking::Secret>, + ) -> CustomResult + where + Self: Sized, + { + Ok(Self { + payment_id: storage_model.payment_id, + merchant_id: storage_model.merchant_id, + status: storage_model.status, + amount: storage_model.amount, + currency: storage_model.currency, + amount_captured: storage_model.amount_captured, + customer_id: storage_model.customer_id, + description: storage_model.description, + return_url: storage_model.return_url, + metadata: storage_model.metadata, + connector_id: storage_model.connector_id, + shipping_address_id: storage_model.shipping_address_id, + billing_address_id: storage_model.billing_address_id, + statement_descriptor_name: storage_model.statement_descriptor_name, + statement_descriptor_suffix: storage_model.statement_descriptor_suffix, + created_at: storage_model.created_at, + modified_at: storage_model.modified_at, + last_synced: storage_model.last_synced, + setup_future_usage: storage_model.setup_future_usage, + off_session: storage_model.off_session, + client_secret: storage_model.client_secret, + active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), + business_country: storage_model.business_country, + business_label: storage_model.business_label, + order_details: storage_model.order_details, + allowed_payment_method_types: storage_model.allowed_payment_method_types, + connector_metadata: storage_model.connector_metadata, + feature_metadata: storage_model.feature_metadata, + attempt_count: storage_model.attempt_count, + profile_id: storage_model.profile_id, + merchant_decision: storage_model.merchant_decision, + payment_link_id: storage_model.payment_link_id, + payment_confirm_source: storage_model.payment_confirm_source, + updated_by: storage_model.updated_by, + surcharge_applicable: storage_model.surcharge_applicable, + request_incremental_authorization: storage_model.request_incremental_authorization, + incremental_authorization_allowed: storage_model.incremental_authorization_allowed, + authorization_count: storage_model.authorization_count, + fingerprint_id: storage_model.fingerprint_id, + session_expiry: storage_model.session_expiry, + request_external_three_ds_authentication: storage_model + .request_external_three_ds_authentication, + charges: storage_model.charges, + frm_metadata: storage_model.frm_metadata, + }) + } + + async fn construct_new(self) -> CustomResult { + Ok(DieselPaymentIntentNew { + payment_id: self.payment_id, + merchant_id: self.merchant_id, + status: self.status, + amount: self.amount, + currency: self.currency, + amount_captured: self.amount_captured, + customer_id: self.customer_id, + description: self.description, + return_url: self.return_url, + metadata: self.metadata, + connector_id: self.connector_id, + shipping_address_id: self.shipping_address_id, + billing_address_id: self.billing_address_id, + statement_descriptor_name: self.statement_descriptor_name, + statement_descriptor_suffix: self.statement_descriptor_suffix, + created_at: Some(self.created_at), + modified_at: Some(self.modified_at), + last_synced: self.last_synced, + setup_future_usage: self.setup_future_usage, + off_session: self.off_session, + client_secret: self.client_secret, + active_attempt_id: self.active_attempt.get_id(), + business_country: self.business_country, + business_label: self.business_label, + order_details: self.order_details, + allowed_payment_method_types: self.allowed_payment_method_types, + connector_metadata: self.connector_metadata, + feature_metadata: self.feature_metadata, + attempt_count: self.attempt_count, + profile_id: self.profile_id, + merchant_decision: self.merchant_decision, + payment_link_id: self.payment_link_id, + payment_confirm_source: self.payment_confirm_source, + updated_by: self.updated_by, + surcharge_applicable: self.surcharge_applicable, + request_incremental_authorization: self.request_incremental_authorization, + incremental_authorization_allowed: self.incremental_authorization_allowed, + authorization_count: self.authorization_count, + fingerprint_id: self.fingerprint_id, + session_expiry: self.session_expiry, + request_external_three_ds_authentication: self.request_external_three_ds_authentication, + charges: self.charges, + frm_metadata: self.frm_metadata, + }) + } +} diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index d413e97289af..2c99360d4f94 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -8,19 +8,21 @@ use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use super::{payment_attempt::PaymentAttempt, PaymentIntent}; -use crate::{errors, RemoteStorageObject}; +use crate::{errors, merchant_key_store::MerchantKeyStore, RemoteStorageObject}; #[async_trait::async_trait] pub trait PaymentIntentInterface { async fn update_payment_intent( &self, this: PaymentIntent, payment_intent: PaymentIntentUpdate, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; async fn insert_payment_intent( &self, - new: PaymentIntentNew, + new: PaymentIntent, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; @@ -28,6 +30,7 @@ pub trait PaymentIntentInterface { &self, payment_id: &str, merchant_id: &str, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; @@ -42,6 +45,7 @@ pub trait PaymentIntentInterface { &self, merchant_id: &str, filters: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; @@ -50,6 +54,7 @@ pub trait PaymentIntentInterface { &self, merchant_id: &str, time_range: &api_models::payments::TimeRange, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; @@ -58,6 +63,7 @@ pub trait PaymentIntentInterface { &self, merchant_id: &str, constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; @@ -440,6 +446,247 @@ impl From for PaymentIntentUpdateInternal { } } +use diesel_models::PaymentIntentUpdate as DieselPaymentIntentUpdate; + +impl From for DieselPaymentIntentUpdate { + fn from(value: PaymentIntentUpdate) -> Self { + match value { + PaymentIntentUpdate::ResponseUpdate { + status, + amount_captured, + fingerprint_id, + return_url, + updated_by, + incremental_authorization_allowed, + } => Self::ResponseUpdate { + status, + amount_captured, + fingerprint_id, + return_url, + updated_by, + incremental_authorization_allowed, + }, + PaymentIntentUpdate::MetadataUpdate { + metadata, + updated_by, + } => Self::MetadataUpdate { + metadata, + updated_by, + }, + PaymentIntentUpdate::ReturnUrlUpdate { + return_url, + status, + customer_id, + shipping_address_id, + billing_address_id, + updated_by, + } => Self::ReturnUrlUpdate { + return_url, + status, + customer_id, + shipping_address_id, + billing_address_id, + updated_by, + }, + PaymentIntentUpdate::MerchantStatusUpdate { + status, + shipping_address_id, + billing_address_id, + updated_by, + } => Self::MerchantStatusUpdate { + status, + shipping_address_id, + billing_address_id, + updated_by, + }, + PaymentIntentUpdate::PGStatusUpdate { + status, + updated_by, + incremental_authorization_allowed, + } => Self::PGStatusUpdate { + status, + updated_by, + incremental_authorization_allowed, + }, + PaymentIntentUpdate::Update { + amount, + currency, + setup_future_usage, + status, + customer_id, + shipping_address_id, + billing_address_id, + return_url, + business_country, + business_label, + description, + statement_descriptor_name, + statement_descriptor_suffix, + order_details, + metadata, + payment_confirm_source, + updated_by, + fingerprint_id, + session_expiry, + request_external_three_ds_authentication, + frm_metadata, + } => Self::Update { + amount, + currency, + setup_future_usage, + status, + customer_id, + shipping_address_id, + billing_address_id, + return_url, + business_country, + business_label, + description, + statement_descriptor_name, + statement_descriptor_suffix, + order_details, + metadata, + payment_confirm_source, + updated_by, + fingerprint_id, + session_expiry, + request_external_three_ds_authentication, + frm_metadata, + }, + PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + } => Self::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + }, + PaymentIntentUpdate::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + } => Self::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + }, + PaymentIntentUpdate::ApproveUpdate { + status, + merchant_decision, + updated_by, + } => Self::ApproveUpdate { + status, + merchant_decision, + updated_by, + }, + PaymentIntentUpdate::RejectUpdate { + status, + merchant_decision, + updated_by, + } => Self::RejectUpdate { + status, + merchant_decision, + updated_by, + }, + PaymentIntentUpdate::SurchargeApplicableUpdate { + surcharge_applicable, + updated_by, + } => Self::SurchargeApplicableUpdate { + surcharge_applicable: Some(surcharge_applicable), + updated_by, + }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => { + Self::IncrementalAuthorizationAmountUpdate { amount } + } + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self::AuthorizationCountUpdate { + authorization_count, + }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + } => Self::CompleteAuthorizeUpdate { + shipping_address_id, + }, + } + } +} + +impl From for diesel_models::PaymentIntentUpdateInternal { + fn from(value: PaymentIntentUpdateInternal) -> Self { + let modified_at = Some(common_utils::date_time::now()); + + let PaymentIntentUpdateInternal { + amount, + currency, + status, + amount_captured, + customer_id, + return_url, + setup_future_usage, + off_session, + metadata, + billing_address_id, + shipping_address_id, + modified_at: _, + active_attempt_id, + business_country, + business_label, + description, + statement_descriptor_name, + statement_descriptor_suffix, + order_details, + attempt_count, + merchant_decision, + payment_confirm_source, + updated_by, + surcharge_applicable, + incremental_authorization_allowed, + authorization_count, + session_expiry, + fingerprint_id, + request_external_three_ds_authentication, + frm_metadata, + } = value; + + Self { + amount, + currency, + status, + amount_captured, + customer_id, + return_url, + setup_future_usage, + off_session, + metadata, + billing_address_id, + shipping_address_id, + modified_at, + active_attempt_id, + business_country, + business_label, + description, + statement_descriptor_name, + statement_descriptor_suffix, + order_details, + attempt_count, + merchant_decision, + payment_confirm_source, + updated_by, + surcharge_applicable, + incremental_authorization_allowed, + authorization_count, + session_expiry, + fingerprint_id, + request_external_three_ds_authentication, + frm_metadata, + } + } +} + pub enum PaymentIntentFetchConstraints { Single { payment_intent_id: String }, List(Box), diff --git a/crates/hyperswitch_domain_models/src/router_data_new.rs b/crates/hyperswitch_domain_models/src/router_data_new.rs new file mode 100644 index 000000000000..fb5291933b2e --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_data_new.rs @@ -0,0 +1,27 @@ +pub mod flow_common_types; + +use std::{marker::PhantomData, ops::Deref}; + +pub use flow_common_types::*; + +use crate::router_data::{ConnectorAuthType, ErrorResponse}; + +#[derive(Debug, Clone)] +pub struct RouterDataNew { + pub flow: PhantomData, + pub resource_common_data: ResourceCommonData, + pub connector_auth_type: ConnectorAuthType, + /// Contains flow-specific data required to construct a request and send it to the connector. + pub request: FlowSpecificRequest, + /// Contains flow-specific data that the connector responds with. + pub response: Result, +} + +impl Deref + for RouterDataNew +{ + type Target = ResourceCommonData; + fn deref(&self) -> &Self::Target { + &self.resource_common_data + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data_new/flow_common_types.rs b/crates/hyperswitch_domain_models/src/router_data_new/flow_common_types.rs new file mode 100644 index 000000000000..f4b21f3ef0f7 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_data_new/flow_common_types.rs @@ -0,0 +1,150 @@ +use common_utils::{pii, types::MinorUnit}; + +use crate::{ + payment_address::PaymentAddress, + payment_method_data::ApplePayFlow, + router_data::{ + AccessToken, ConnectorResponseData, PaymentMethodBalance, PaymentMethodToken, + RecurringMandatePaymentData, + }, +}; + +#[derive(Debug, Clone)] +pub struct PaymentFlowData { + pub merchant_id: String, + pub customer_id: Option, + pub connector_customer: Option, + pub payment_id: String, + pub attempt_id: String, + pub status: common_enums::AttemptStatus, + pub payment_method: common_enums::PaymentMethod, + pub description: Option, + pub return_url: Option, + pub address: PaymentAddress, + pub auth_type: common_enums::AuthenticationType, + pub connector_meta_data: Option, + pub amount_captured: Option, + // minor amount for amount framework + pub minor_amount_captured: Option, + pub access_token: Option, + pub session_token: Option, + pub reference_id: Option, + pub payment_method_token: Option, + pub recurring_mandate_payment_data: Option, + pub preprocessing_id: Option, + /// This is the balance amount for gift cards or voucher + pub payment_method_balance: Option, + + ///for switching between two different versions of the same connector + pub connector_api_version: Option, + /// Contains a reference ID that should be sent in the connector request + pub connector_request_reference_id: String, + + pub test_mode: Option, + pub connector_http_status_code: Option, + pub external_latency: Option, + /// Contains apple pay flow type simplified or manual + pub apple_pay_flow: Option, + + /// This field is used to store various data regarding the response from connector + pub connector_response: Option, + pub payment_method_status: Option, +} + +#[derive(Debug, Clone)] +pub struct RefundFlowData { + pub merchant_id: String, + pub customer_id: Option, + pub payment_id: String, + pub attempt_id: String, + pub status: common_enums::AttemptStatus, + pub payment_method: common_enums::PaymentMethod, + pub return_url: Option, + pub connector_meta_data: Option, + pub amount_captured: Option, + // minor amount for amount framework + pub minor_amount_captured: Option, + /// Contains a reference ID that should be sent in the connector request + pub connector_request_reference_id: String, + pub refund_id: String, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Clone)] +pub struct PayoutFlowData { + pub merchant_id: String, + pub customer_id: Option, + pub connector_customer: Option, + pub return_url: Option, + pub address: PaymentAddress, + pub connector_meta_data: Option, + pub connector_wallets_details: Option, + /// Contains a reference ID that should be sent in the connector request + pub connector_request_reference_id: String, + pub payout_method_data: Option, + pub quote_id: Option, +} + +#[derive(Debug, Clone)] +pub struct FrmFlowData { + pub merchant_id: String, + pub payment_id: String, + pub attempt_id: String, + pub payment_method: common_enums::enums::PaymentMethod, + pub connector_request_reference_id: String, + pub return_url: Option, + pub auth_type: common_enums::enums::AuthenticationType, + pub connector_wallets_details: Option, + pub connector_meta_data: Option, + pub amount_captured: Option, + // minor amount for amount framework + pub minor_amount_captured: Option, +} + +#[derive(Debug, Clone)] +pub struct ExternalAuthenticationFlowData { + pub merchant_id: String, + pub connector_meta_data: Option, + pub address: PaymentAddress, +} + +#[derive(Debug, Clone)] +pub struct DisputesFlowData { + pub merchant_id: String, + pub payment_id: String, + pub attempt_id: String, + pub payment_method: common_enums::enums::PaymentMethod, + pub return_url: Option, + pub connector_meta_data: Option, + pub amount_captured: Option, + // minor amount for amount framework + pub minor_amount_captured: Option, + /// Contains a reference ID that should be sent in the connector request + pub connector_request_reference_id: String, + pub dispute_id: String, +} + +#[derive(Debug, Clone)] +pub struct MandateRevokeFlowData { + pub merchant_id: String, + pub customer_id: String, + pub payment_id: Option, +} + +#[derive(Debug, Clone)] +pub struct WebhookSourceVerifyData { + pub merchant_id: String, +} + +#[derive(Debug, Clone)] +pub struct AccessTokenFlowData {} + +#[derive(Debug, Clone)] +pub struct FilesFlowData { + pub merchant_id: String, + pub payment_id: String, + pub attempt_id: String, + pub return_url: Option, + pub connector_meta_data: Option, + pub connector_request_reference_id: String, +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 9a05a176b320..f91bd9d890a5 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -367,6 +367,9 @@ pub struct PaymentsCancelData { pub browser_info: Option, pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. + + // minor amount data for amount framework + pub minor_amount: Option, } #[derive(Debug, Default, Clone)] diff --git a/crates/hyperswitch_domain_models/src/type_encryption.rs b/crates/hyperswitch_domain_models/src/type_encryption.rs new file mode 100644 index 000000000000..82d904ccf49f --- /dev/null +++ b/crates/hyperswitch_domain_models/src/type_encryption.rs @@ -0,0 +1,237 @@ +use async_trait::async_trait; +use common_utils::{ + crypto, + errors::{self, CustomResult}, + ext_traits::AsyncExt, + metrics::utils::record_operation_time, +}; +use diesel_models::encryption::Encryption; +use error_stack::ResultExt; +use masking::{PeekInterface, Secret}; +use router_env::{instrument, tracing}; + +#[async_trait] +pub trait TypeEncryption< + T, + V: crypto::EncodeMessage + crypto::DecodeMessage, + S: masking::Strategy, +>: Sized +{ + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult; + + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + ) -> CustomResult; +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy + Send, + > TypeEncryption for crypto::Encryptable> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted_data = crypt_algo.encode_message(key, masked_data.peek().as_bytes())?; + + Ok(Self::new(masked_data, encrypted_data.into())) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + let data = crypt_algo.decode_message(key, encrypted.clone())?; + + let value: String = std::str::from_utf8(&data) + .change_context(errors::CryptoError::DecodingFailed)? + .to_string(); + + Ok(Self::new(value.into(), encrypted)) + } +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy + Send, + > TypeEncryption + for crypto::Encryptable> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let data = serde_json::to_vec(&masked_data.peek()) + .change_context(errors::CryptoError::DecodingFailed)?; + let encrypted_data = crypt_algo.encode_message(key, &data)?; + + Ok(Self::new(masked_data, encrypted_data.into())) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + let data = crypt_algo.decode_message(key, encrypted.clone())?; + + let value: serde_json::Value = + serde_json::from_slice(&data).change_context(errors::CryptoError::DecodingFailed)?; + + Ok(Self::new(value.into(), encrypted)) + } +} + +#[async_trait] +impl< + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy> + Send, + > TypeEncryption, V, S> for crypto::Encryptable, S>> +{ + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, S>, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted_data = crypt_algo.encode_message(key, masked_data.peek())?; + + Ok(Self::new(masked_data, encrypted_data.into())) + } + + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let encrypted = encrypted_data.into_inner(); + let data = crypt_algo.decode_message(key, encrypted.clone())?; + + Ok(Self::new(data.into(), encrypted)) + } +} + +pub trait Lift { + type SelfWrapper; + type OtherWrapper; + + fn lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper; +} + +impl Lift for Option { + type SelfWrapper = Option; + type OtherWrapper = CustomResult, E>; + + fn lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper, + { + func(self) + } +} + +#[async_trait] +pub trait AsyncLift { + type SelfWrapper; + type OtherWrapper; + + async fn async_lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> F + Send + Sync, + F: futures::Future> + Send; +} + +#[async_trait] +impl + Lift = V> + Send> AsyncLift for V { + type SelfWrapper = >::SelfWrapper; + type OtherWrapper = >::OtherWrapper; + + async fn async_lift(self, func: Func) -> Self::OtherWrapper + where + Func: Fn(Self::SelfWrapper) -> F + Send + Sync, + F: futures::Future> + Send, + { + func(self).await + } +} + +#[inline] +pub async fn encrypt( + inner: Secret, + key: &[u8], +) -> CustomResult>, errors::CryptoError> +where + S: masking::Strategy, + crypto::Encryptable>: TypeEncryption, +{ + record_operation_time( + crypto::Encryptable::encrypt(inner, key, crypto::GcmAes256), + &metrics::ENCRYPTION_TIME, + &metrics::CONTEXT, + &[], + ) + .await +} + +#[inline] +pub async fn encrypt_optional( + inner: Option>, + key: &[u8], +) -> CustomResult>>, errors::CryptoError> +where + Secret: Send, + S: masking::Strategy, + crypto::Encryptable>: TypeEncryption, +{ + inner.async_map(|f| encrypt(f, key)).await.transpose() +} + +#[inline] +pub async fn decrypt>( + inner: Option, + key: &[u8], +) -> CustomResult>>, errors::CryptoError> +where + crypto::Encryptable>: TypeEncryption, +{ + record_operation_time( + inner.async_map(|item| crypto::Encryptable::decrypt(item, key, crypto::GcmAes256)), + &metrics::DECRYPTION_TIME, + &metrics::CONTEXT, + &[], + ) + .await + .transpose() +} + +pub(crate) mod metrics { + use router_env::{global_meter, histogram_metric, metrics_context, once_cell}; + + metrics_context!(CONTEXT); + global_meter!(GLOBAL_METER, "ROUTER_API"); + + // Encryption and Decryption metrics + histogram_metric!(ENCRYPTION_TIME, GLOBAL_METER); + histogram_metric!(DECRYPTION_TIME, GLOBAL_METER); +} diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 034e0a67d92e..95b3b98af05a 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -6,12 +6,28 @@ rust-version.workspace = true readme = "README.md" license.workspace = true +[features] +default = ["dummy_connector", "payouts"] +dummy_connector = [] +payouts = [] + [dependencies] async-trait = "0.1.79" +bytes = "1.6.0" dyn-clone = "1.0.17" +http = "0.2.12" +mime = "0.3.17" +once_cell = "1.19.0" +reqwest = "0.11.27" serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" thiserror = "1.0.58" +time = "0.3.35" # First party crates common_utils = { version = "0.1.0", path = "../common_utils" } +hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } masking = { version = "0.1.0", path = "../masking" } +router_derive = { version = "0.1.0", path = "../router_derive" } +router_env = { version = "0.1.0", path = "../router_env" } +storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs new file mode 100644 index 000000000000..ac397cfa0754 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -0,0 +1,184 @@ +//! API interface + +use common_utils::{ + errors::CustomResult, + request::{Method, Request, RequestContent}, +}; +use hyperswitch_domain_models::router_data::{ErrorResponse, RouterData}; +use masking::Maskable; +use router_env::metrics::add_attributes; +use serde_json::json; + +use crate::{ + configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, metrics, types, +}; + +/// type BoxedConnectorIntegration +pub type BoxedConnectorIntegration<'a, T, Req, Resp> = + Box<&'a (dyn ConnectorIntegration + Send + Sync)>; + +/// trait ConnectorIntegrationAny +pub trait ConnectorIntegrationAny: Send + Sync + 'static { + /// fn get_connector_integration + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>; +} + +impl ConnectorIntegrationAny for S +where + S: ConnectorIntegration + Send + Sync, +{ + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> { + Box::new(self) + } +} + +/// trait ConnectorIntegration +pub trait ConnectorIntegration: ConnectorIntegrationAny + Sync { + /// fn get_headers + fn get_headers( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![]) + } + + /// fn get_content_type + fn get_content_type(&self) -> &'static str { + mime::APPLICATION_JSON.essence_str() + } + + /// primarily used when creating signature based on request method of payment flow + fn get_http_method(&self) -> Method { + Method::Post + } + + /// fn get_url + fn get_url( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + Ok(String::new()) + } + + /// fn get_request_body + fn get_request_body( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + Ok(RequestContent::Json(Box::new(json!(r#"{}"#)))) + } + + /// fn get_request_form_data + fn get_request_form_data( + &self, + _req: &RouterData, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } + + /// fn build_request + fn build_request( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + metrics::UNIMPLEMENTED_FLOW.add( + &metrics::CONTEXT, + 1, + &add_attributes([("connector", req.connector.clone())]), + ); + Ok(None) + } + + /// fn handle_response + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + _res: types::Response, + ) -> CustomResult, errors::ConnectorError> + where + T: Clone, + Req: Clone, + Resp: Clone, + { + event_builder.map(|e| e.set_error(json!({"error": "Not Implemented"}))); + Ok(data.clone()) + } + + /// fn get_error_response + fn get_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); + Ok(ErrorResponse::get_not_implemented()) + } + + /// fn get_5xx_error_response + fn get_5xx_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); + let error_message = match res.status_code { + 500 => "internal_server_error", + 501 => "not_implemented", + 502 => "bad_gateway", + 503 => "service_unavailable", + 504 => "gateway_timeout", + 505 => "http_version_not_supported", + 506 => "variant_also_negotiates", + 507 => "insufficient_storage", + 508 => "loop_detected", + 510 => "not_extended", + 511 => "network_authentication_required", + _ => "unknown_error", + }; + Ok(ErrorResponse { + code: res.status_code.to_string(), + message: error_message.to_string(), + reason: String::from_utf8(res.response.to_vec()).ok(), + status_code: res.status_code, + attempt_status: None, + connector_transaction_id: None, + }) + } + + /// whenever capture sync is implemented at the connector side, this method should be overridden + fn get_multiple_capture_sync_method( + &self, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("multiple capture sync".into()).into()) + } + + /// fn get_certificate + fn get_certificate( + &self, + _req: &RouterData, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } + + /// fn get_certificate_key + fn get_certificate_key( + &self, + _req: &RouterData, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } +} + +/// Sync Methods for multiple captures +#[derive(Debug)] +pub enum CaptureSyncMethod { + /// For syncing multiple captures individually + Individual, + /// For syncing multiple captures together + Bulk, +} diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs new file mode 100644 index 000000000000..8f3b5600f089 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -0,0 +1,132 @@ +//! Configs interface +use router_derive; +use serde::Deserialize; +use storage_impl::errors::ApplicationError; + +// struct Connectors +#[allow(missing_docs, missing_debug_implementations)] +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct Connectors { + pub aci: ConnectorParams, + #[cfg(feature = "payouts")] + pub adyen: ConnectorParamsWithSecondaryBaseUrl, + pub adyenplatform: ConnectorParams, + #[cfg(not(feature = "payouts"))] + pub adyen: ConnectorParams, + pub airwallex: ConnectorParams, + pub applepay: ConnectorParams, + pub authorizedotnet: ConnectorParams, + pub bambora: ConnectorParams, + pub bankofamerica: ConnectorParams, + pub billwerk: ConnectorParams, + pub bitpay: ConnectorParams, + pub bluesnap: ConnectorParamsWithSecondaryBaseUrl, + pub boku: ConnectorParams, + pub braintree: ConnectorParams, + pub cashtocode: ConnectorParams, + pub checkout: ConnectorParams, + pub coinbase: ConnectorParams, + pub cryptopay: ConnectorParams, + pub cybersource: ConnectorParams, + pub datatrans: ConnectorParams, + pub dlocal: ConnectorParams, + #[cfg(feature = "dummy_connector")] + pub dummyconnector: ConnectorParams, + pub ebanx: ConnectorParams, + pub fiserv: ConnectorParams, + pub forte: ConnectorParams, + pub globalpay: ConnectorParams, + pub globepay: ConnectorParams, + pub gocardless: ConnectorParams, + pub gpayments: ConnectorParams, + pub helcim: ConnectorParams, + pub iatapay: ConnectorParams, + pub klarna: ConnectorParams, + pub mifinity: ConnectorParams, + pub mollie: ConnectorParams, + pub multisafepay: ConnectorParams, + pub netcetera: ConnectorParams, + pub nexinets: ConnectorParams, + pub nmi: ConnectorParams, + pub noon: ConnectorParamsWithModeType, + pub nuvei: ConnectorParams, + pub opayo: ConnectorParams, + pub opennode: ConnectorParams, + pub payeezy: ConnectorParams, + pub payme: ConnectorParams, + pub payone: ConnectorParams, + pub paypal: ConnectorParams, + pub payu: ConnectorParams, + pub placetopay: ConnectorParams, + pub powertranz: ConnectorParams, + pub prophetpay: ConnectorParams, + pub rapyd: ConnectorParams, + pub riskified: ConnectorParams, + pub shift4: ConnectorParams, + pub signifyd: ConnectorParams, + pub square: ConnectorParams, + pub stax: ConnectorParams, + pub stripe: ConnectorParamsWithFileUploadUrl, + pub threedsecureio: ConnectorParams, + pub trustpay: ConnectorParamsWithMoreUrls, + pub tsys: ConnectorParams, + pub volt: ConnectorParams, + pub wise: ConnectorParams, + pub worldline: ConnectorParams, + pub worldpay: ConnectorParams, + pub zen: ConnectorParams, + pub zsl: ConnectorParams, +} + +/// struct ConnectorParams +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParams { + /// base url + pub base_url: String, + /// secondary base url + pub secondary_base_url: Option, +} + +/// struct ConnectorParamsWithModeType +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithModeType { + /// base url + pub base_url: String, + /// secondary base url + pub secondary_base_url: Option, + /// Can take values like Test or Live for Noon + pub key_mode: String, +} + +/// struct ConnectorParamsWithMoreUrls +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithMoreUrls { + /// base url + pub base_url: String, + /// base url for bank redirects + pub base_url_bank_redirects: String, +} + +/// struct ConnectorParamsWithFileUploadUrl +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithFileUploadUrl { + /// base url + pub base_url: String, + /// base url for file upload + pub base_url_file_upload: String, +} + +/// struct ConnectorParamsWithSecondaryBaseUrl +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithSecondaryBaseUrl { + /// base url + pub base_url: String, + /// secondary base url + pub secondary_base_url: String, +} diff --git a/crates/hyperswitch_interfaces/src/errors.rs b/crates/hyperswitch_interfaces/src/errors.rs new file mode 100644 index 000000000000..e36707af6b05 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/errors.rs @@ -0,0 +1,149 @@ +//! Errors interface + +use common_utils::errors::ErrorSwitch; +use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; + +/// Connector Errors +#[allow(missing_docs, missing_debug_implementations)] +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum ConnectorError { + #[error("Error while obtaining URL for the integration")] + FailedToObtainIntegrationUrl, + #[error("Failed to encode connector request")] + RequestEncodingFailed, + #[error("Request encoding failed : {0}")] + RequestEncodingFailedWithReason(String), + #[error("Parsing failed")] + ParsingFailed, + #[error("Failed to deserialize connector response")] + ResponseDeserializationFailed, + #[error("Failed to execute a processing step: {0:?}")] + ProcessingStepFailed(Option), + #[error("The connector returned an unexpected response: {0:?}")] + UnexpectedResponseError(bytes::Bytes), + #[error("Failed to parse custom routing rules from merchant account")] + RoutingRulesParsingError, + #[error("Failed to obtain preferred connector from merchant account")] + FailedToObtainPreferredConnector, + #[error("An invalid connector name was provided")] + InvalidConnectorName, + #[error("An invalid Wallet was used")] + InvalidWallet, + #[error("Failed to handle connector response")] + ResponseHandlingFailed, + #[error("Missing required field: {field_name}")] + MissingRequiredField { field_name: &'static str }, + #[error("Missing required fields: {field_names:?}")] + MissingRequiredFields { field_names: Vec<&'static str> }, + #[error("Failed to obtain authentication type")] + FailedToObtainAuthType, + #[error("Failed to obtain certificate")] + FailedToObtainCertificate, + #[error("Connector meta data not found")] + NoConnectorMetaData, + #[error("Failed to obtain certificate key")] + FailedToObtainCertificateKey, + #[error("This step has not been implemented for: {0}")] + NotImplemented(String), + #[error("{message} is not supported by {connector}")] + NotSupported { + message: String, + connector: &'static str, + }, + #[error("{flow} flow not supported by {connector} connector")] + FlowNotSupported { flow: String, connector: String }, + #[error("Capture method not supported")] + CaptureMethodNotSupported, + #[error("Missing connector mandate ID")] + MissingConnectorMandateID, + #[error("Missing connector transaction ID")] + MissingConnectorTransactionID, + #[error("Missing connector refund ID")] + MissingConnectorRefundID, + #[error("Missing apple pay tokenization data")] + MissingApplePayTokenData, + #[error("Webhooks not implemented for this connector")] + WebhooksNotImplemented, + #[error("Failed to decode webhook event body")] + WebhookBodyDecodingFailed, + #[error("Signature not found for incoming webhook")] + WebhookSignatureNotFound, + #[error("Failed to verify webhook source")] + WebhookSourceVerificationFailed, + #[error("Could not find merchant secret in DB for incoming webhook source verification")] + WebhookVerificationSecretNotFound, + #[error("Merchant secret found for incoming webhook source verification is invalid")] + WebhookVerificationSecretInvalid, + #[error("Incoming webhook object reference ID not found")] + WebhookReferenceIdNotFound, + #[error("Incoming webhook event type not found")] + WebhookEventTypeNotFound, + #[error("Incoming webhook event resource object not found")] + WebhookResourceObjectNotFound, + #[error("Could not respond to the incoming webhook event")] + WebhookResponseEncodingFailed, + #[error("Invalid Date/time format")] + InvalidDateFormat, + #[error("Date Formatting Failed")] + DateFormattingFailed, + #[error("Invalid Data format")] + InvalidDataFormat { field_name: &'static str }, + #[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")] + MismatchedPaymentData, + #[error("Failed to parse {wallet_name} wallet token")] + InvalidWalletToken { wallet_name: String }, + #[error("Missing Connector Related Transaction ID")] + MissingConnectorRelatedTransactionID { id: String }, + #[error("File Validation failed")] + FileValidationFailed { reason: String }, + #[error("Missing 3DS redirection payload: {field_name}")] + MissingConnectorRedirectionPayload { field_name: &'static str }, + #[error("Failed at connector's end with code '{code}'")] + FailedAtConnector { message: String, code: String }, + #[error("Payment Method Type not found")] + MissingPaymentMethodType, + #[error("Balance in the payment method is low")] + InSufficientBalanceInPaymentMethod, + #[error("Server responded with Request Timeout")] + RequestTimeoutReceived, + #[error("The given currency method is not configured with the given connector")] + CurrencyNotSupported { + message: String, + connector: &'static str, + }, + #[error("Invalid Configuration")] + InvalidConnectorConfig { config: &'static str }, + #[error("Failed to convert amount to required type")] + AmountConversionFailed, +} + +impl ConnectorError { + /// fn is_connector_timeout + pub fn is_connector_timeout(&self) -> bool { + self == &Self::RequestTimeoutReceived + } +} + +impl ErrorSwitch for common_utils::errors::ParsingError { + fn switch(&self) -> ConnectorError { + ConnectorError::ParsingFailed + } +} + +impl ErrorSwitch for ConnectorError { + fn switch(&self) -> ApiErrorResponse { + match self { + Self::WebhookSourceVerificationFailed => ApiErrorResponse::WebhookAuthenticationFailed, + Self::WebhookSignatureNotFound + | Self::WebhookReferenceIdNotFound + | Self::WebhookResourceObjectNotFound + | Self::WebhookBodyDecodingFailed + | Self::WebhooksNotImplemented => ApiErrorResponse::WebhookBadRequest, + Self::WebhookEventTypeNotFound => ApiErrorResponse::WebhookUnprocessableEntity, + Self::WebhookVerificationSecretInvalid => { + ApiErrorResponse::WebhookInvalidMerchantSecret + } + _ => ApiErrorResponse::InternalServerError, + } + } +} diff --git a/crates/hyperswitch_interfaces/src/events.rs b/crates/hyperswitch_interfaces/src/events.rs new file mode 100644 index 000000000000..3dcb75195451 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/events.rs @@ -0,0 +1,3 @@ +//! Events interface + +pub mod connector_api_logs; diff --git a/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs b/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs new file mode 100644 index 000000000000..79b172e9ea70 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs @@ -0,0 +1,96 @@ +//! Connector API logs interface + +use common_utils::request::Method; +use router_env::tracing_actix_web::RequestId; +use serde::Serialize; +use serde_json::json; +use time::OffsetDateTime; + +/// struct ConnectorEvent +#[derive(Debug, Serialize)] +pub struct ConnectorEvent { + connector_name: String, + flow: String, + request: String, + masked_response: Option, + error: Option, + url: String, + method: String, + payment_id: String, + merchant_id: String, + created_at: i128, + /// Connector Event Request ID + pub request_id: String, + latency: u128, + refund_id: Option, + dispute_id: Option, + status_code: u16, +} + +impl ConnectorEvent { + /// fn new ConnectorEvent + #[allow(clippy::too_many_arguments)] + pub fn new( + connector_name: String, + flow: &str, + request: serde_json::Value, + url: String, + method: Method, + payment_id: String, + merchant_id: String, + request_id: Option<&RequestId>, + latency: u128, + refund_id: Option, + dispute_id: Option, + status_code: u16, + ) -> Self { + Self { + connector_name, + flow: flow + .rsplit_once("::") + .map(|(_, s)| s) + .unwrap_or(flow) + .to_string(), + request: request.to_string(), + masked_response: None, + error: None, + url, + method: method.to_string(), + payment_id, + merchant_id, + created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000, + request_id: request_id + .map(|i| i.as_hyphenated().to_string()) + .unwrap_or("NO_REQUEST_ID".to_string()), + latency, + refund_id, + dispute_id, + status_code, + } + } + + /// fn set_response_body + pub fn set_response_body(&mut self, response: &T) { + match masking::masked_serialize(response) { + Ok(masked) => { + self.masked_response = Some(masked.to_string()); + } + Err(er) => self.set_error(json!({"error": er.to_string()})), + } + } + + /// fn set_error_response_body + pub fn set_error_response_body(&mut self, response: &T) { + match masking::masked_serialize(response) { + Ok(masked) => { + self.error = Some(masked.to_string()); + } + Err(er) => self.set_error(json!({"error": er.to_string()})), + } + } + + /// fn set_error + pub fn set_error(&mut self, error: serde_json::Value) { + self.error = Some(error.to_string()); + } +} diff --git a/crates/hyperswitch_interfaces/src/lib.rs b/crates/hyperswitch_interfaces/src/lib.rs index 3f7b8d41c3e3..7d3b319df83b 100644 --- a/crates/hyperswitch_interfaces/src/lib.rs +++ b/crates/hyperswitch_interfaces/src/lib.rs @@ -1,7 +1,11 @@ //! Hyperswitch interface - #![warn(missing_docs, missing_debug_implementations)] -pub mod secrets_interface; - +pub mod api; +pub mod configs; pub mod encryption_interface; +pub mod errors; +pub mod events; +pub mod metrics; +pub mod secrets_interface; +pub mod types; diff --git a/crates/hyperswitch_interfaces/src/metrics.rs b/crates/hyperswitch_interfaces/src/metrics.rs new file mode 100644 index 000000000000..fc374eba8e24 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/metrics.rs @@ -0,0 +1,8 @@ +//! Metrics interface + +use router_env::{counter_metric, global_meter, metrics_context}; + +metrics_context!(CONTEXT); +global_meter!(GLOBAL_METER, "ROUTER_API"); + +counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER); diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs new file mode 100644 index 000000000000..2f0c5c2fbda1 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -0,0 +1,12 @@ +//! Types interface + +/// struct Response +#[derive(Clone, Debug)] +pub struct Response { + /// headers + pub headers: Option, + /// response + pub response: bytes::Bytes, + /// status code + pub status_code: u16, +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index c8eff1153029..871eed868937 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -71,7 +71,7 @@ qrcode = "0.14.0" rand = "0.8.5" rand_chacha = "0.3.1" regex = "1.10.4" -reqwest = { version = "0.11.27", features = ["json", "native-tls","__rustls", "gzip", "multipart"] } +reqwest = { version = "0.11.27", features = ["json", "native-tls", "__rustls", "gzip", "multipart"] } ring = "0.17.8" roxmltree = "0.19.0" rust_decimal = { version = "1.35.0", features = ["serde-with-float", "serde-with-str"] } @@ -102,7 +102,7 @@ api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] analytics = { version = "0.1.0", path = "../analytics", optional = true } cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } -common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs"] } +common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics"] } hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" } currency_conversion = { version = "0.1.0", path = "../currency_conversion" } hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } @@ -126,7 +126,7 @@ isocountry = "0.3.2" iso_currency = "0.4.4" actix-http = "3.6.0" events = { version = "0.1.0", path = "../events" } -totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"]} +totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] } serde_repr = "0.1.19" unidecode = "0.3.0" diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index 7e78a355e5c4..167e7a6b6ede 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, str::FromStr, sync::Arc}; use actix_web::{dev::Server, web, Scope}; use api_models::health_check::SchedulerHealthCheckResponse; use common_utils::ext_traits::{OptionExt, StringExt}; -use diesel_models::process_tracker as storage; +use diesel_models::process_tracker::{self as storage, business_status}; use error_stack::ResultExt; use router::{ configs::settings::{CmdLineConf, Settings}, @@ -329,10 +329,13 @@ impl ProcessTrackerWorkflows for WorkflowRunner { let status = state .get_db() .as_scheduler() - .finish_process_with_business_status(process, "GLOBAL_FAILURE".to_string()) + .finish_process_with_business_status( + process, + business_status::GLOBAL_FAILURE, + ) .await; if let Err(err) = status { - logger::error!(%err, "Failed while performing database operation: GLOBAL_FAILURE"); + logger::error!(%err, "Failed while performing database operation: {}", business_status::GLOBAL_FAILURE); } } }, diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 65abb012b9a7..264f205c907b 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -195,6 +195,7 @@ pub async fn list_customer_payment_method_api( auth.key_store, Some(req), Some(&customer_id), + None, ) }, &auth::ApiKeyAuth, diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index b8ef4137f9d3..79b6b0fc0085 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -497,7 +497,9 @@ pub async fn payment_intent_list( state.into_inner(), &req, payload, - |state, auth, req, _| payments::list_payments(state, auth.merchant_account, req), + |state, auth, req, _| { + payments::list_payments(state, auth.merchant_account, auth.key_store, req) + }, &auth::ApiKeyAuth, api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 3c7562d50781..6b3054e7c388 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -946,7 +946,7 @@ fn get_pmd_based_on_payment_method_type( .and_then(|billing_data| billing_data.get_optional_country()), }), ), - Some(api_enums::PaymentMethodType::LocalBankTransfer) => { + Some(api_enums::PaymentMethodType::LocalBankRedirect) => { Some(payments::PaymentMethodData::BankRedirect( payments::BankRedirectData::LocalBankRedirect {}, )) diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index e99e216463a1..daba006a56c3 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -17,6 +17,7 @@ use external_services::{ secrets_management::SecretsManagementConfig, }, }; +pub use hyperswitch_interfaces::configs::Connectors; use hyperswitch_interfaces::secrets_interface::secret_state::{ RawSecret, SecretState, SecretStateContainer, SecuredSecret, }; @@ -128,6 +129,7 @@ pub struct Settings { pub struct Multitenancy { pub tenants: TenantConfig, pub enabled: bool, + pub global_tenant: GlobalTenant, } impl Multitenancy { @@ -152,6 +154,7 @@ pub struct Tenant { pub base_url: String, pub schema: String, pub redis_key_prefix: String, + pub clickhouse_database: String, } impl storage_impl::config::TenantConfig for Tenant { @@ -163,6 +166,12 @@ impl storage_impl::config::TenantConfig for Tenant { } } +impl storage_impl::config::ClickHouseConfig for Tenant { + fn get_clickhouse_database(&self) -> &str { + self.clickhouse_database.as_str() + } +} + #[derive(Debug, Deserialize, Clone, Default)] pub struct GlobalTenant { pub schema: String, @@ -544,116 +553,6 @@ pub struct SupportedConnectors { pub wallets: Vec, } -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct Connectors { - pub aci: ConnectorParams, - #[cfg(feature = "payouts")] - pub adyen: ConnectorParamsWithSecondaryBaseUrl, - pub adyenplatform: ConnectorParams, - #[cfg(not(feature = "payouts"))] - pub adyen: ConnectorParams, - pub airwallex: ConnectorParams, - pub applepay: ConnectorParams, - pub authorizedotnet: ConnectorParams, - pub bambora: ConnectorParams, - pub bankofamerica: ConnectorParams, - pub billwerk: ConnectorParams, - pub bitpay: ConnectorParams, - pub bluesnap: ConnectorParamsWithSecondaryBaseUrl, - pub boku: ConnectorParams, - pub braintree: ConnectorParams, - pub cashtocode: ConnectorParams, - pub checkout: ConnectorParams, - pub coinbase: ConnectorParams, - pub cryptopay: ConnectorParams, - pub cybersource: ConnectorParams, - pub dlocal: ConnectorParams, - #[cfg(feature = "dummy_connector")] - pub dummyconnector: ConnectorParams, - pub ebanx: ConnectorParams, - pub fiserv: ConnectorParams, - pub forte: ConnectorParams, - pub globalpay: ConnectorParams, - pub globepay: ConnectorParams, - pub gocardless: ConnectorParams, - pub gpayments: ConnectorParams, - pub helcim: ConnectorParams, - pub iatapay: ConnectorParams, - pub klarna: ConnectorParams, - pub mifinity: ConnectorParams, - pub mollie: ConnectorParams, - pub multisafepay: ConnectorParams, - pub netcetera: ConnectorParams, - pub nexinets: ConnectorParams, - pub nmi: ConnectorParams, - pub noon: ConnectorParamsWithModeType, - pub nuvei: ConnectorParams, - pub opayo: ConnectorParams, - pub opennode: ConnectorParams, - pub payeezy: ConnectorParams, - pub payme: ConnectorParams, - pub payone: ConnectorParams, - pub paypal: ConnectorParams, - pub payu: ConnectorParams, - pub placetopay: ConnectorParams, - pub powertranz: ConnectorParams, - pub prophetpay: ConnectorParams, - pub rapyd: ConnectorParams, - pub riskified: ConnectorParams, - pub shift4: ConnectorParams, - pub signifyd: ConnectorParams, - pub square: ConnectorParams, - pub stax: ConnectorParams, - pub stripe: ConnectorParamsWithFileUploadUrl, - pub threedsecureio: ConnectorParams, - pub trustpay: ConnectorParamsWithMoreUrls, - pub tsys: ConnectorParams, - pub volt: ConnectorParams, - pub wise: ConnectorParams, - pub worldline: ConnectorParams, - pub worldpay: ConnectorParams, - pub zen: ConnectorParams, - pub zsl: ConnectorParams, -} - -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct ConnectorParams { - pub base_url: String, - pub secondary_base_url: Option, -} - -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct ConnectorParamsWithModeType { - pub base_url: String, - pub secondary_base_url: Option, - /// Can take values like Test or Live for Noon - pub key_mode: String, -} - -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct ConnectorParamsWithMoreUrls { - pub base_url: String, - pub base_url_bank_redirects: String, -} - -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct ConnectorParamsWithFileUploadUrl { - pub base_url: String, - pub base_url_file_upload: String, -} - -#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] -#[serde(default)] -pub struct ConnectorParamsWithSecondaryBaseUrl { - pub base_url: String, - pub secondary_base_url: String, -} - #[cfg(feature = "kv_store")] #[derive(Debug, Clone, Deserialize)] #[serde(default)] diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index a423decdb9bd..4b89b12734e2 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -15,6 +15,7 @@ pub mod checkout; pub mod coinbase; pub mod cryptopay; pub mod cybersource; +pub mod datatrans; pub mod dlocal; #[cfg(feature = "dummy_connector")] pub mod dummyconnector; @@ -71,12 +72,12 @@ pub use self::{ authorizedotnet::Authorizedotnet, bambora::Bambora, bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, braintree::Braintree, cashtocode::Cashtocode, checkout::Checkout, coinbase::Coinbase, cryptopay::Cryptopay, - cybersource::Cybersource, dlocal::Dlocal, ebanx::Ebanx, fiserv::Fiserv, forte::Forte, - globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, gpayments::Gpayments, - helcim::Helcim, iatapay::Iatapay, klarna::Klarna, mifinity::Mifinity, mollie::Mollie, - multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nmi::Nmi, noon::Noon, - nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, payone::Payone, - paypal::Paypal, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, + cybersource::Cybersource, datatrans::Datatrans, dlocal::Dlocal, ebanx::Ebanx, fiserv::Fiserv, + forte::Forte, globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, + gpayments::Gpayments, helcim::Helcim, iatapay::Iatapay, klarna::Klarna, mifinity::Mifinity, + mollie::Mollie, multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nmi::Nmi, + noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, + payone::Payone, paypal::Paypal, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, threedsecureio::Threedsecureio, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, worldpay::Worldpay, zen::Zen, diff --git a/crates/router/src/connector/boku.rs b/crates/router/src/connector/boku.rs index 7c456d2ec1c2..d565cc6567eb 100644 --- a/crates/router/src/connector/boku.rs +++ b/crates/router/src/connector/boku.rs @@ -6,6 +6,7 @@ use diesel_models::enums; use error_stack::{report, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret, WithType}; use ring::hmac; +use router_env::metrics::add_attributes; use roxmltree; use time::OffsetDateTime; use transformers as boku; @@ -665,7 +666,7 @@ fn get_xml_deserialized( metrics::RESPONSE_DESERIALIZATION_FAILURE.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("connector", "boku")], + &add_attributes([("connector", "boku")]), ); let response_data = String::from_utf8(res.response.to_vec()) diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index f7e142a9af9d..750e2e6e3cc2 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -155,7 +155,7 @@ impl ConnectorCommon for Braintree { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); logger::error!(deserialization_error =? error_msg); - utils::handle_json_response_deserialization_failure(res, "braintree".to_owned()) + utils::handle_json_response_deserialization_failure(res, "braintree") } } } diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 1f8de468846b..7bd3fa08dda7 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -229,10 +229,7 @@ impl ConnectorCommon for Cybersource { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); router_env::logger::error!(deserialization_error =? error_msg); - crate::utils::handle_json_response_deserialization_failure( - res, - "cybersource".to_owned(), - ) + crate::utils::handle_json_response_deserialization_failure(res, "cybersource") } } } diff --git a/crates/router/src/connector/datatrans.rs b/crates/router/src/connector/datatrans.rs new file mode 100644 index 000000000000..90c6b38d2fbf --- /dev/null +++ b/crates/router/src/connector/datatrans.rs @@ -0,0 +1,563 @@ +pub mod transformers; + +use std::fmt::Debug; + +use error_stack::{report, ResultExt}; +use masking::ExposeInterface; +use transformers as datatrans; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + events::connector_api_logs::ConnectorEvent, + headers, + services::{ + self, + request::{self, Mask}, + ConnectorIntegration, ConnectorValidation, + }, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + ErrorResponse, RequestContent, Response, + }, + utils::BytesExt, +}; + +#[derive(Debug, Clone)] +pub struct Datatrans; + +impl api::Payment for Datatrans {} +impl api::PaymentSession for Datatrans {} +impl api::ConnectorAccessToken for Datatrans {} +impl api::MandateSetup for Datatrans {} +impl api::PaymentAuthorize for Datatrans {} +impl api::PaymentSync for Datatrans {} +impl api::PaymentCapture for Datatrans {} +impl api::PaymentVoid for Datatrans {} +impl api::Refund for Datatrans {} +impl api::RefundExecute for Datatrans {} +impl api::RefundSync for Datatrans {} +impl api::PaymentToken for Datatrans {} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Datatrans +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Datatrans +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Datatrans { + fn id(&self) -> &'static str { + "datatrans" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.datatrans.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = datatrans::DatatransAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: datatrans::DatatransErrorResponse = res + .response + .parse_struct("DatatransErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Datatrans { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration + for Datatrans +{ + //TODO: implement sessions flow +} + +impl ConnectorIntegration + for Datatrans +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Datatrans +{ +} + +impl ConnectorIntegration + for Datatrans +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = datatrans::DatatransRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = datatrans::DatatransPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: datatrans::DatatransPaymentsResponse = res + .response + .parse_struct("Datatrans PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Datatrans +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: datatrans::DatatransPaymentsResponse = res + .response + .parse_struct("datatrans PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Datatrans +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: datatrans::DatatransPaymentsResponse = res + .response + .parse_struct("Datatrans PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Datatrans +{ +} + +impl ConnectorIntegration + for Datatrans +{ + fn get_headers( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = datatrans::DatatransRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let connector_req = datatrans::DatatransRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: datatrans::RefundResponse = res + .response + .parse_struct("datatrans RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Datatrans +{ + fn get_headers( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: datatrans::RefundResponse = res + .response + .parse_struct("datatrans RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Datatrans { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} diff --git a/crates/router/src/connector/datatrans/transformers.rs b/crates/router/src/connector/datatrans/transformers.rs new file mode 100644 index 000000000000..b079708e233c --- /dev/null +++ b/crates/router/src/connector/datatrans/transformers.rs @@ -0,0 +1,235 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + connector::utils::PaymentsAuthorizeRequestData, + core::errors, + types::{self, api, domain, storage::enums}, +}; + +//TODO: Fill the struct with respective fields +pub struct DatatransRouterData { + pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for DatatransRouterData { + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), + ) -> Result { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Ok(Self { + amount, + router_data: item, + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct DatatransPaymentsRequest { + amount: i64, + card: DatatransCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct DatatransCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> + for DatatransPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + domain::PaymentMethodData::Card(req_card) => { + let card = DatatransCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.to_owned(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct DatatransAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for DatatransAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum DatatransPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::AttemptStatus { + fn from(item: DatatransPaymentStatus) -> Self { + match item { + DatatransPaymentStatus::Succeeded => Self::Charged, + DatatransPaymentStatus::Failed => Self::Failure, + DatatransPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DatatransPaymentsResponse { + status: DatatransPaymentStatus, + id: String, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + DatatransPaymentsResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct DatatransRefundRequest { + pub amount: i64, +} + +impl TryFrom<&DatatransRouterData<&types::RefundsRouterData>> for DatatransRefundRequest { + type Error = error_stack::Report; + fn try_from( + item: &DatatransRouterData<&types::RefundsRouterData>, + ) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct DatatransErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/connector/globalpay/response.rs b/crates/router/src/connector/globalpay/response.rs index 7d5efadaff8b..95f8c33728e0 100644 --- a/crates/router/src/connector/globalpay/response.rs +++ b/crates/router/src/connector/globalpay/response.rs @@ -377,31 +377,11 @@ pub enum GlobalpayPaymentStatus { Reversed, } -#[derive(Debug, Deserialize)] -pub struct GlobalpayWebhoookResourceObject { - pub data: GlobalpayWebhookDataResource, -} - -#[derive(Debug, Deserialize)] -pub struct GlobalpayWebhookDataResource { - pub object: serde_json::Value, -} - #[derive(Debug, Deserialize)] pub struct GlobalpayWebhookObjectId { pub id: String, } -#[derive(Debug, Deserialize)] -pub struct GlobalpayWebhookDataId { - pub object: GlobalpayWebhookObjectDataId, -} - -#[derive(Debug, Deserialize)] -pub struct GlobalpayWebhookObjectDataId { - pub id: String, -} - #[derive(Debug, Deserialize)] pub struct GlobalpayWebhookObjectEventType { pub status: GlobalpayWebhookStatus, diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index f733dcdc96ce..ec1deb34cee0 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -153,32 +153,7 @@ impl (common_enums::CountryAlpha2::NL, None, None) } domain::BankRedirectData::LocalBankRedirect {} => { - let billing_country = item.router_data.get_billing_country()?; - ( - if matches!( - billing_country, - common_enums::CountryAlpha2::AT - | common_enums::CountryAlpha2::BE - | common_enums::CountryAlpha2::DE - | common_enums::CountryAlpha2::EE - | common_enums::CountryAlpha2::ES - | common_enums::CountryAlpha2::FI - | common_enums::CountryAlpha2::FR - | common_enums::CountryAlpha2::IE - | common_enums::CountryAlpha2::IT - | common_enums::CountryAlpha2::LU - | common_enums::CountryAlpha2::LV - | common_enums::CountryAlpha2::LT - | common_enums::CountryAlpha2::NL - | common_enums::CountryAlpha2::PT - ) { - billing_country - } else { - common_enums::CountryAlpha2::AT - }, - None, - None, - ) + (common_enums::CountryAlpha2::AT, None, None) } domain::BankRedirectData::BancontactCard { .. } | domain::BankRedirectData::Bizum {} diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index 23e15b2cfdcc..d9e476ac1f90 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -1,9 +1,12 @@ pub mod transformers; -use std::fmt::Debug; - use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +use common_utils::{ + crypto, + ext_traits::ByteSliceExt, + request::RequestContent, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, +}; use diesel_models::enums; use error_stack::{Report, ResultExt}; use masking::PeekInterface; @@ -16,6 +19,7 @@ use crate::{ consts, core::{ errors::{self, CustomResult}, + mandate::MandateBehaviour, payments, }, events::connector_api_logs::ConnectorEvent, @@ -33,8 +37,18 @@ use crate::{ utils::{self, BytesExt}, }; -#[derive(Debug, Clone)] -pub struct Noon; +#[derive(Clone)] +pub struct Noon { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Noon { + pub const fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } +} impl api::Payment for Noon {} impl api::PaymentSession for Noon {} @@ -155,7 +169,7 @@ impl ConnectorCommon for Noon { Err(error_message) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); logger::error!(deserialization_error =? error_message); - utils::handle_json_response_deserialization_failure(res, "noon".to_owned()) + utils::handle_json_response_deserialization_failure(res, "noon") } } } @@ -260,7 +274,26 @@ impl ConnectorIntegration CustomResult { - let connector_req = noon::NoonPaymentsRequest::try_from(req)?; + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let mandate_details = + connector_utils::get_mandate_details(req.request.get_setup_mandate_details())?; + let mandate_amount = mandate_details + .map(|mandate| { + connector_utils::convert_amount( + self.amount_converter, + mandate.amount, + mandate.currency, + ) + }) + .transpose()?; + + let connector_router_data = noon::NoonRouterData::from((amount, req, mandate_amount)); + let connector_req = noon::NoonPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -416,7 +449,13 @@ impl ConnectorIntegration CustomResult { - let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = noon::NoonRouterData::from((amount, req, None)); + let connector_req = noon::NoonPaymentsActionRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -656,7 +695,13 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; + let refund_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_router_data = noon::NoonRouterData::from((refund_amount, req, None)); + let connector_req = noon::NoonPaymentsActionRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 0624e3cf957e..d79774b2e879 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -1,4 +1,4 @@ -use common_utils::{ext_traits::Encode, pii}; +use common_utils::{ext_traits::Encode, pii, types::StringMajorUnit}; use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -8,7 +8,7 @@ use crate::{ self as conn_utils, is_refund_failure, CardData, PaymentsAuthorizeRequestData, RevokeMandateRequestData, RouterData, WalletData, }, - core::{errors, mandate::MandateBehaviour}, + core::errors, services, types::{self, api, domain, storage::enums, transformers::ForeignFrom, ErrorResponse}, }; @@ -17,6 +17,25 @@ use crate::{ const GOOGLEPAY_API_VERSION_MINOR: u8 = 0; const GOOGLEPAY_API_VERSION: u8 = 2; +#[derive(Debug, Serialize)] +pub struct NoonRouterData { + pub amount: StringMajorUnit, + pub router_data: T, + pub mandate_amount: Option, +} + +impl From<(StringMajorUnit, T, Option)> for NoonRouterData { + fn from( + (amount, router_data, mandate_amount): (StringMajorUnit, T, Option), + ) -> Self { + Self { + amount, + router_data, + mandate_amount, + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum NoonChannels { @@ -36,7 +55,7 @@ pub struct NoonSubscriptionData { subscription_type: NoonSubscriptionType, //Short description about the subscription. name: String, - max_amount: String, + max_amount: StringMajorUnit, } #[derive(Debug, Serialize)] @@ -59,7 +78,7 @@ pub struct NoonBilling { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct NoonOrder { - amount: String, + amount: StringMajorUnit, currency: Option, channel: NoonChannels, category: Option, @@ -226,9 +245,15 @@ pub struct NoonPaymentsRequest { billing: Option, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { +impl TryFrom<&NoonRouterData<&types::PaymentsAuthorizeRouterData>> for NoonPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + data: &NoonRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let item = data.router_data; + let amount = &data.amount; + let mandate_amount = &data.mandate_amount; + let (payment_data, currency, category) = match item.request.connector_mandate_id() { Some(mandate_id) => ( NoonPaymentData::Subscription(NoonSubscription { @@ -342,15 +367,6 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { ), }; - // The description should not have leading or trailing whitespaces, also it should not have double whitespaces and a max 50 chars according to Noon's Docs - let name: String = item - .get_description()? - .trim() - .replace(" ", " ") - .chars() - .take(50) - .collect(); - let ip_address = item.request.get_ip_address_as_optional(); let channel = NoonChannels::Web; @@ -370,47 +386,27 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { }, }); - let subscription: Option = item - .request - .get_setup_mandate_details() - .map(|mandate_data| { - let max_amount = match &mandate_data.mandate_type { - Some(hyperswitch_domain_models::mandates::MandateDataType::SingleUse( - mandate, - )) - | Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some( - mandate, - ))) => conn_utils::to_currency_base_unit( - mandate.amount.get_amount_as_i64(), - mandate.currency, - ), - Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None)) => { - Err(errors::ConnectorError::MissingRequiredField { - field_name: - "setup_future_usage.mandate_data.mandate_type.multi_use.amount", - } - .into()) - } - None => Err(errors::ConnectorError::MissingRequiredField { - field_name: "setup_future_usage.mandate_data.mandate_type", - } - .into()), - }?; - - Ok::>( - NoonSubscriptionData { - subscription_type: NoonSubscriptionType::Unscheduled, - name: name.clone(), - max_amount, - }, - ) - }) - .transpose()?; + // The description should not have leading or trailing whitespaces, also it should not have double whitespaces and a max 50 chars according to Noon's Docs + let name: String = item + .get_description()? + .trim() + .replace(" ", " ") + .chars() + .take(50) + .collect(); + + let subscription = mandate_amount + .as_ref() + .map(|mandate_max_amount| NoonSubscriptionData { + subscription_type: NoonSubscriptionType::Unscheduled, + name: name.clone(), + max_amount: mandate_max_amount.to_owned(), + }); let tokenize_c_c = subscription.is_some().then_some(true); let order = NoonOrder { - amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?, + amount: amount.to_owned(), currency, channel, category, @@ -613,7 +609,7 @@ impl #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct NoonActionTransaction { - amount: String, + amount: StringMajorUnit, currency: diesel_models::enums::Currency, transaction_reference: Option, } @@ -632,17 +628,18 @@ pub struct NoonPaymentsActionRequest { transaction: NoonActionTransaction, } -impl TryFrom<&types::PaymentsCaptureRouterData> for NoonPaymentsActionRequest { +impl TryFrom<&NoonRouterData<&types::PaymentsCaptureRouterData>> for NoonPaymentsActionRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + fn try_from( + data: &NoonRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { + let item = data.router_data; + let amount = &data.amount; let order = NoonActionOrder { id: item.request.connector_transaction_id.clone(), }; let transaction = NoonActionTransaction { - amount: conn_utils::to_currency_base_unit( - item.request.amount_to_capture, - item.request.currency, - )?, + amount: amount.to_owned(), currency: item.request.currency, transaction_reference: None, }; @@ -693,17 +690,16 @@ impl TryFrom<&types::MandateRevokeRouterData> for NoonRevokeMandateRequest { } } -impl TryFrom<&types::RefundsRouterData> for NoonPaymentsActionRequest { +impl TryFrom<&NoonRouterData<&types::RefundsRouterData>> for NoonPaymentsActionRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(data: &NoonRouterData<&types::RefundsRouterData>) -> Result { + let item = data.router_data; + let refund_amount = &data.amount; let order = NoonActionOrder { id: item.request.connector_transaction_id.clone(), }; let transaction = NoonActionTransaction { - amount: conn_utils::to_currency_base_unit( - item.request.refund_amount, - item.request.currency, - )?, + amount: refund_amount.to_owned(), currency: item.request.currency, transaction_reference: Some(item.request.refund_id.clone()), }; diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 397715d102db..88b2ec675959 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -111,7 +111,7 @@ impl ConnectorCommon for Payme { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); router_env::logger::error!(deserialization_error =? error_msg); - handle_json_response_deserialization_failure(res, "payme".to_owned()) + handle_json_response_deserialization_failure(res, "payme") } } } diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index 5d4579fffa75..f4bd09976271 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -114,7 +114,7 @@ impl ConnectorCommon for Rapyd { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); logger::error!(deserialization_error =? error_msg); - utils::handle_json_response_deserialization_failure(res, "rapyd".to_owned()) + utils::handle_json_response_deserialization_failure(res, "rapyd") } } } diff --git a/crates/router/src/connector/threedsecureio.rs b/crates/router/src/connector/threedsecureio.rs index 75a746648f10..a051cf471f19 100644 --- a/crates/router/src/connector/threedsecureio.rs +++ b/crates/router/src/connector/threedsecureio.rs @@ -126,10 +126,7 @@ impl ConnectorCommon for Threedsecureio { } Err(err) => { router_env::logger::error!(deserialization_error =? err); - utils::handle_json_response_deserialization_failure( - res, - "threedsecureio".to_owned(), - ) + utils::handle_json_response_deserialization_failure(res, "threedsecureio") } } } diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 1f9e77de9cd2..e2904221e59c 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -153,7 +153,7 @@ impl ConnectorCommon for Trustpay { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); logger::error!(deserialization_error =? error_msg); - utils::handle_json_response_deserialization_failure(res, "trustpay".to_owned()) + utils::handle_json_response_deserialization_failure(res, "trustpay") } } } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index e1ef11265c09..5c01a483c891 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -17,7 +17,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; +use hyperswitch_domain_models::{mandates, payments::payment_attempt::PaymentAttempt}; use masking::{ExposeInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; @@ -1837,12 +1837,6 @@ where json.parse_value(std::any::type_name::()).switch() } -impl common_utils::errors::ErrorSwitch for errors::ParsingError { - fn switch(&self) -> errors::ConnectorError { - errors::ConnectorError::ParsingFailed - } -} - pub fn base64_decode(data: String) -> Result, Error> { consts::BASE64_ENGINE .decode(data) @@ -2838,6 +2832,27 @@ impl From for PaymentMethodDataType { } } +pub fn get_mandate_details( + setup_mandate_details: Option<&mandates::MandateData>, +) -> Result, error_stack::Report> { + setup_mandate_details + .map(|mandate_data| match &mandate_data.mandate_type { + Some(mandates::MandateDataType::SingleUse(mandate)) + | Some(mandates::MandateDataType::MultiUse(Some(mandate))) => Ok(mandate), + Some(mandates::MandateDataType::MultiUse(None)) => { + Err(errors::ConnectorError::MissingRequiredField { + field_name: "setup_future_usage.mandate_data.mandate_type.multi_use.amount", + } + .into()) + } + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "setup_future_usage.mandate_data.mandate_type", + } + .into()), + }) + .transpose() +} + pub fn convert_amount( amount_convertor: &dyn AmountConvertor, amount: MinorUnit, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 180852c8a375..b9ac5a02b7e0 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -15,6 +15,7 @@ use error_stack::{report, FutureExt, ResultExt}; use futures::future::try_join_all; use masking::{PeekInterface, Secret}; use pm_auth::connector::plaid::transformers::PlaidAuthType; +use router_env::metrics::add_attributes; use uuid::Uuid; use crate::{ @@ -1009,10 +1010,10 @@ pub async fn create_payment_connector( metrics::MCA_CREATE.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes("connector", req.connector_name.to_string()), - metrics::request::add_attributes("merchant", merchant_id.to_string()), - ], + &add_attributes([ + ("connector", req.connector_name.to_string()), + ("merchant", merchant_id.to_string()), + ]), ); let mca_response = mca.try_into()?; @@ -1900,6 +1901,11 @@ pub(crate) fn validate_auth_and_metadata_type_with_connector( cybersource::transformers::CybersourceAuthType::try_from(val)?; Ok(()) } + // api_enums::Connector::Datatrans => { + // datatrans::transformers::DatatransAuthType::try_from(val)?; + // Ok(()) + // } + // added for future use api_enums::Connector::Dlocal => { dlocal::transformers::DlocalAuthType::try_from(val)?; Ok(()) diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs index 8fe536731d3d..5d41b9c18541 100644 --- a/crates/router/src/core/api_keys.rs +++ b/crates/router/src/core/api_keys.rs @@ -3,7 +3,7 @@ use common_utils::date_time; use diesel_models::{api_keys::ApiKey, enums as storage_enums}; use error_stack::{report, ResultExt}; use masking::{PeekInterface, StrongSecret}; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use crate::{ configs::settings, @@ -151,7 +151,7 @@ pub async fn create_api_key( metrics::API_KEY_CREATED.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("merchant", merchant_id)], + &add_attributes([("merchant", merchant_id)]), ); // Add process to process_tracker for email reminder, only if expiry is set to future date @@ -236,7 +236,7 @@ pub async fn add_api_key_expiry_task( metrics::TASKS_ADDED_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("flow", "ApiKeyExpiry")], + &add_attributes([("flow", "ApiKeyExpiry")]), ); Ok(()) @@ -381,7 +381,9 @@ pub async fn update_api_key_expiry_task( retry_count: Some(0), schedule_time, tracking_data: Some(updated_api_key_expiry_workflow_model), - business_status: Some("Pending".to_string()), + business_status: Some(String::from( + diesel_models::process_tracker::business_status::PENDING, + )), status: Some(storage_enums::ProcessTrackerStatus::New), updated_at: Some(current_time), }; @@ -450,7 +452,7 @@ pub async fn revoke_api_key_expiry_task( let task_ids = vec![task_id]; let updated_process_tracker_data = storage::ProcessTrackerUpdate::StatusUpdate { status: storage_enums::ProcessTrackerStatus::Finish, - business_status: Some("Revoked".to_string()), + business_status: Some(String::from(diesel_models::business_status::REVOKED)), }; store diff --git a/crates/router/src/core/blocklist/utils.rs b/crates/router/src/core/blocklist/utils.rs index 6c0d3fcd6c3b..118dbb3f66d3 100644 --- a/crates/router/src/core/blocklist/utils.rs +++ b/crates/router/src/core/blocklist/utils.rs @@ -303,6 +303,7 @@ async fn delete_card_bin_blocklist_entry( pub async fn validate_data_for_blocklist( state: &SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, ) -> CustomResult where @@ -407,6 +408,7 @@ where merchant_decision: Some(MerchantDecision::Rejected.to_string()), updated_by: merchant_account.storage_scheme.to_string(), }, + key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/cards_info.rs b/crates/router/src/core/cards_info.rs index bc01a786d154..b0c1aca53228 100644 --- a/crates/router/src/core/cards_info.rs +++ b/crates/router/src/core/cards_info.rs @@ -23,6 +23,7 @@ fn verify_iin_length(card_iin: &str) -> Result<(), errors::ApiErrorResponse> { pub async fn retrieve_card_info( state: routes::SessionState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, request: api_models::cards_info::CardsInfoRequest, ) -> RouterResponse { let db = state.store.as_ref(); @@ -31,6 +32,7 @@ pub async fn retrieve_card_info( helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, + &key_store, request.client_secret, ) .await?; diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 883612869039..f42f3ae6a11e 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -91,6 +91,7 @@ pub async fn accept_dispute( .find_payment_intent_by_payment_id_merchant_id( &dispute.payment_id, &merchant_account.merchant_id, + &key_store, merchant_account.storage_scheme, ) .await @@ -204,6 +205,7 @@ pub async fn submit_evidence( .find_payment_intent_by_payment_id_merchant_id( &dispute.payment_id, &merchant_account.merchant_id, + &key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 0e9e40227985..83fa21436485 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -14,6 +14,7 @@ pub use hyperswitch_domain_models::errors::{ api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage}, StorageError as DataStorageError, }; +pub use hyperswitch_interfaces::errors::ConnectorError; pub use redis_interface::errors::RedisError; use scheduler::errors as sch_errors; use storage_impl::errors as storage_impl_errors; @@ -111,118 +112,6 @@ pub fn http_not_implemented() -> actix_web::HttpResponse { .error_response() } -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum ConnectorError { - #[error("Error while obtaining URL for the integration")] - FailedToObtainIntegrationUrl, - #[error("Failed to encode connector request")] - RequestEncodingFailed, - #[error("Request encoding failed : {0}")] - RequestEncodingFailedWithReason(String), - #[error("Parsing failed")] - ParsingFailed, - #[error("Failed to deserialize connector response")] - ResponseDeserializationFailed, - #[error("Failed to execute a processing step: {0:?}")] - ProcessingStepFailed(Option), - #[error("The connector returned an unexpected response: {0:?}")] - UnexpectedResponseError(bytes::Bytes), - #[error("Failed to parse custom routing rules from merchant account")] - RoutingRulesParsingError, - #[error("Failed to obtain preferred connector from merchant account")] - FailedToObtainPreferredConnector, - #[error("An invalid connector name was provided")] - InvalidConnectorName, - #[error("An invalid Wallet was used")] - InvalidWallet, - #[error("Failed to handle connector response")] - ResponseHandlingFailed, - #[error("Missing required field: {field_name}")] - MissingRequiredField { field_name: &'static str }, - #[error("Missing required fields: {field_names:?}")] - MissingRequiredFields { field_names: Vec<&'static str> }, - #[error("Failed to obtain authentication type")] - FailedToObtainAuthType, - #[error("Failed to obtain certificate")] - FailedToObtainCertificate, - #[error("Connector meta data not found")] - NoConnectorMetaData, - #[error("Failed to obtain certificate key")] - FailedToObtainCertificateKey, - #[error("This step has not been implemented for: {0}")] - NotImplemented(String), - #[error("{message} is not supported by {connector}")] - NotSupported { - message: String, - connector: &'static str, - }, - #[error("{flow} flow not supported by {connector} connector")] - FlowNotSupported { flow: String, connector: String }, - #[error("Capture method not supported")] - CaptureMethodNotSupported, - #[error("Missing connector mandate ID")] - MissingConnectorMandateID, - #[error("Missing connector transaction ID")] - MissingConnectorTransactionID, - #[error("Missing connector refund ID")] - MissingConnectorRefundID, - #[error("Missing apple pay tokenization data")] - MissingApplePayTokenData, - #[error("Webhooks not implemented for this connector")] - WebhooksNotImplemented, - #[error("Failed to decode webhook event body")] - WebhookBodyDecodingFailed, - #[error("Signature not found for incoming webhook")] - WebhookSignatureNotFound, - #[error("Failed to verify webhook source")] - WebhookSourceVerificationFailed, - #[error("Could not find merchant secret in DB for incoming webhook source verification")] - WebhookVerificationSecretNotFound, - #[error("Merchant secret found for incoming webhook source verification is invalid")] - WebhookVerificationSecretInvalid, - #[error("Incoming webhook object reference ID not found")] - WebhookReferenceIdNotFound, - #[error("Incoming webhook event type not found")] - WebhookEventTypeNotFound, - #[error("Incoming webhook event resource object not found")] - WebhookResourceObjectNotFound, - #[error("Could not respond to the incoming webhook event")] - WebhookResponseEncodingFailed, - #[error("Invalid Date/time format")] - InvalidDateFormat, - #[error("Date Formatting Failed")] - DateFormattingFailed, - #[error("Invalid Data format")] - InvalidDataFormat { field_name: &'static str }, - #[error("Payment Method data / Payment Method Type / Payment Experience Mismatch ")] - MismatchedPaymentData, - #[error("Failed to parse {wallet_name} wallet token")] - InvalidWalletToken { wallet_name: String }, - #[error("Missing Connector Related Transaction ID")] - MissingConnectorRelatedTransactionID { id: String }, - #[error("File Validation failed")] - FileValidationFailed { reason: String }, - #[error("Missing 3DS redirection payload: {field_name}")] - MissingConnectorRedirectionPayload { field_name: &'static str }, - #[error("Failed at connector's end with code '{code}'")] - FailedAtConnector { message: String, code: String }, - #[error("Payment Method Type not found")] - MissingPaymentMethodType, - #[error("Balance in the payment method is low")] - InSufficientBalanceInPaymentMethod, - #[error("Server responded with Request Timeout")] - RequestTimeoutReceived, - #[error("The given currency method is not configured with the given connector")] - CurrencyNotSupported { - message: String, - connector: &'static str, - }, - #[error("Invalid Configuration")] - InvalidConnectorConfig { config: &'static str }, - #[error("Failed to convert amount to required type")] - AmountConversionFailed, -} - #[derive(Debug, thiserror::Error)] pub enum HealthCheckOutGoing { #[error("Outgoing call failed with error: {message}")] @@ -335,12 +224,6 @@ pub enum ApplePayDecryptionError { DerivingSharedSecretKeyFailed, } -impl ConnectorError { - pub fn is_connector_timeout(&self) -> bool { - self == &Self::RequestTimeoutReceived - } -} - #[cfg(feature = "detailed_errors")] pub mod error_stack_parsing { diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index df529f818034..f5e90771c147 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -1,25 +1,7 @@ use common_utils::errors::ErrorSwitch; use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; -use super::{ConnectorError, CustomersErrorResponse, StorageError}; - -impl ErrorSwitch for ConnectorError { - fn switch(&self) -> ApiErrorResponse { - match self { - Self::WebhookSourceVerificationFailed => ApiErrorResponse::WebhookAuthenticationFailed, - Self::WebhookSignatureNotFound - | Self::WebhookReferenceIdNotFound - | Self::WebhookResourceObjectNotFound - | Self::WebhookBodyDecodingFailed - | Self::WebhooksNotImplemented => ApiErrorResponse::WebhookBadRequest, - Self::WebhookEventTypeNotFound => ApiErrorResponse::WebhookUnprocessableEntity, - Self::WebhookVerificationSecretInvalid => { - ApiErrorResponse::WebhookInvalidMerchantSecret - } - _ => ApiErrorResponse::InternalServerError, - } - } -} +use super::{CustomersErrorResponse, StorageError}; impl ErrorSwitch for CustomersErrorResponse { fn switch(&self) -> api_models::errors::types::ApiErrorResponse { diff --git a/crates/router/src/core/files/helpers.rs b/crates/router/src/core/files/helpers.rs index 0be198377b86..98d13812adce 100644 --- a/crates/router/src/core/files/helpers.rs +++ b/crates/router/src/core/files/helpers.rs @@ -264,6 +264,7 @@ pub async fn upload_and_get_provider_provider_file_id_profile_id( .find_payment_intent_by_payment_id_merchant_id( &dispute.payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index f7f7108c2baa..a0a7ca3bf3c5 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -417,6 +417,7 @@ where .to_update_tracker()? .update_tracker( &*state.store, + &key_store, frm_data.clone(), payment_data, None, @@ -491,6 +492,7 @@ where .to_update_tracker()? .update_tracker( &*state.store, + &key_store, frm_data.to_owned(), payment_data, None, @@ -514,7 +516,7 @@ where merchant_account, frm_configs, &mut frm_suggestion, - key_store, + key_store.clone(), payment_data, customer, should_continue_capture, @@ -525,6 +527,7 @@ where .to_update_tracker()? .update_tracker( &*state.store, + &key_store, frm_data.to_owned(), payment_data, frm_suggestion, @@ -653,6 +656,7 @@ pub async fn frm_fulfillment_core( .find_payment_intent_by_payment_id_merchant_id( &req.payment_id.clone(), &merchant_account.merchant_id, + &key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/fraud_check/operation.rs b/crates/router/src/core/fraud_check/operation.rs index 33a370ac8499..8c6f8b7bf52f 100644 --- a/crates/router/src/core/fraud_check/operation.rs +++ b/crates/router/src/core/fraud_check/operation.rs @@ -102,6 +102,7 @@ pub trait UpdateTracker: Send { async fn update_tracker<'b>( &'b self, db: &dyn StorageInterface, + key_store: &domain::MerchantKeyStore, frm_data: D, payment_data: &mut payments::PaymentData, _frm_suggestion: Option, diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index cc4fc0370e97..7c97eee8b490 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -330,6 +330,7 @@ impl UpdateTracker for FraudCheckPost { async fn update_tracker<'b>( &'b self, db: &dyn StorageInterface, + key_store: &domain::MerchantKeyStore, mut frm_data: FrmData, payment_data: &mut payments::PaymentData, frm_suggestion: Option, @@ -520,6 +521,7 @@ impl UpdateTracker for FraudCheckPost { merchant_decision, updated_by: frm_data.merchant_account.storage_scheme.to_string(), }, + key_store, frm_data.merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs index 272cd5aa7044..c153acc75446 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -219,6 +219,7 @@ impl UpdateTracker for FraudCheckPre { async fn update_tracker<'b>( &'b self, db: &dyn StorageInterface, + _key_store: &domain::MerchantKeyStore, mut frm_data: FrmData, payment_data: &mut payments::PaymentData, _frm_suggestion: Option, diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index f484467f3547..3301c75b53f3 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -5,7 +5,7 @@ use common_utils::{ext_traits::Encode, id_type}; use diesel_models::{enums as storage_enums, Mandate}; use error_stack::{report, ResultExt}; use futures::future; -use router_env::{instrument, logger, tracing}; +use router_env::{instrument, logger, metrics::add_attributes, tracing}; use super::payments::helpers as payment_helper; use crate::{ @@ -78,9 +78,13 @@ pub async fn revoke_mandate( common_enums::MandateStatus::Active | common_enums::MandateStatus::Inactive | common_enums::MandateStatus::Pending => { - let profile_id = - helpers::get_profile_id_for_mandate(&state, &merchant_account, mandate.clone()) - .await?; + let profile_id = helpers::get_profile_id_for_mandate( + &state, + &merchant_account, + &key_store, + mandate.clone(), + ) + .await?; let merchant_connector_account = payment_helper::get_merchant_connector_account( &state, @@ -404,10 +408,7 @@ where metrics::SUBSEQUENT_MANDATE_PAYMENT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - mandate.connector, - )], + &add_attributes([("connector", mandate.connector)]), ); Ok(Some(mandate_id.clone())) } @@ -463,7 +464,7 @@ where metrics::MANDATE_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("connector", connector)], + &add_attributes([("connector", connector)]), ); Ok(Some(res_mandate_id)) } diff --git a/crates/router/src/core/mandate/helpers.rs b/crates/router/src/core/mandate/helpers.rs index b0c727925df5..7fb0cfa28af5 100644 --- a/crates/router/src/core/mandate/helpers.rs +++ b/crates/router/src/core/mandate/helpers.rs @@ -14,6 +14,7 @@ use crate::{ pub async fn get_profile_id_for_mandate( state: &SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, mandate: Mandate, ) -> CustomResult { let profile_id = if let Some(ref payment_id) = mandate.original_payment_id { @@ -22,6 +23,7 @@ pub async fn get_profile_id_for_mandate( .find_payment_intent_by_payment_id_merchant_id( payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index e4ef38ae3512..99bff1fd69dc 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -49,6 +49,7 @@ pub async fn retrieve_payment_link( pub async fn initiate_payment_link_flow( state: SessionState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, merchant_id: String, payment_id: String, ) -> RouterResponse { @@ -57,6 +58,7 @@ pub async fn initiate_payment_link_flow( .find_payment_intent_by_payment_id_merchant_id( &payment_id, &merchant_id, + &key_store, merchant_account.storage_scheme, ) .await @@ -503,6 +505,7 @@ fn check_payment_link_invalid_conditions( pub async fn get_payment_link_status( state: SessionState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, merchant_id: String, payment_id: String, ) -> RouterResponse { @@ -511,6 +514,7 @@ pub async fn get_payment_link_status( .find_payment_intent_by_payment_id_merchant_id( &payment_id, &merchant_id, + &key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 4bde9f42c814..f0aa0480d09c 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1,7 +1,6 @@ pub mod cards; pub mod surcharge_decision_configs; pub mod transformers; -pub mod utils; pub mod vault; pub use api_models::enums::Connector; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 04983ed8b191..fad3e6d918d4 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -5,7 +5,7 @@ use std::{ }; use api_models::{ - admin::PaymentMethodsEnabled, + admin::{self, PaymentMethodsEnabled}, enums::{self as api_enums}, payment_methods::{ BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, @@ -19,24 +19,20 @@ use api_models::{ pm_auth::PaymentMethodAuthConfig, surcharge_decision_configs as api_surcharge_decision_configs, }; -use cgraph::ConstraintGraph; use common_enums::enums::MerchantStorageScheme; use common_utils::{ consts, ext_traits::{AsyncExt, Encode, StringExt, ValueExt}, generate_id, id_type, }; -use diesel_models::{business_profile::BusinessProfile, encryption::Encryption, payment_method}; +use diesel_models::{ + business_profile::BusinessProfile, encryption::Encryption, enums as storage_enums, + payment_method, +}; use domain::CustomerUpdate; use error_stack::{report, ResultExt}; -use euclid::{ - dssa::graph::{AnalysisContext, CgraphExt}, - frontend::dir, -}; -use hyperswitch_constraint_graph as cgraph; -use kgraph_utils::transformers::IntoDirValue; use masking::Secret; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use strum::IntoEnumIterator; use super::surcharge_decision_configs::{ @@ -49,11 +45,7 @@ use crate::{ configs::settings, core::{ errors::{self, StorageErrorExt}, - payment_methods::{ - transformers as payment_methods, - utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, - vault, - }, + payment_methods::{transformers as payment_methods, vault}, payments::{ helpers, routing::{self, SessionFlowRoutingInput}, @@ -62,11 +54,7 @@ use crate::{ }, db, logger, pii::prelude::*, - routes::{ - self, - metrics::{self, request}, - payment_methods::ParentPaymentMethodToken, - }, + routes::{self, app::SessionStateInfo, metrics, payment_methods::ParentPaymentMethodToken}, services, types::{ api::{self, routing as routing_types, PaymentMethodCreateExt}, @@ -652,9 +640,10 @@ pub async fn add_payment_method( }; let updated_card = Some(api::CardDetailFromLocker { - scheme: None, + scheme: existing_pm.scheme.clone(), last4_digits: Some(card.card_number.get_last4()), - issuer_country: None, + issuer_country: card.card_issuing_country, + card_isin: Some(card.card_number.get_card_isin()), card_number: Some(card.card_number), expiry_month: Some(card.card_exp_month), expiry_year: Some(card.card_exp_year), @@ -662,10 +651,9 @@ pub async fn add_payment_method( card_fingerprint: None, card_holder_name: card.card_holder_name, nick_name: card.nick_name, - card_network: None, - card_isin: None, - card_issuer: None, - card_type: None, + card_network: card.card_network, + card_issuer: card.card_issuer, + card_type: card.card_type, saved_to_locker: true, }); @@ -1104,7 +1092,7 @@ pub async fn add_card_to_locker( errors::VaultError, > { metrics::STORED_TO_LOCKER.add(&metrics::CONTEXT, 1, &[]); - let add_card_to_hs_resp = request::record_operation_time( + let add_card_to_hs_resp = common_utils::metrics::utils::record_operation_time( async { add_card_hs( state, @@ -1129,6 +1117,7 @@ pub async fn add_card_to_locker( }) }, &metrics::CARD_ADD_TIME, + &metrics::CONTEXT, &[router_env::opentelemetry::KeyValue::new("locker", "rust")], ) .await?; @@ -1145,7 +1134,7 @@ pub async fn get_card_from_locker( ) -> errors::RouterResult { metrics::GET_FROM_LOCKER.add(&metrics::CONTEXT, 1, &[]); - let get_card_from_rs_locker_resp = request::record_operation_time( + let get_card_from_rs_locker_resp = common_utils::metrics::utils::record_operation_time( async { get_card_from_hs_locker( state, @@ -1170,6 +1159,7 @@ pub async fn get_card_from_locker( }) }, &metrics::CARD_GET_TIME, + &metrics::CONTEXT, &[router_env::opentelemetry::KeyValue::new("locker", "rust")], ) .await?; @@ -1186,7 +1176,7 @@ pub async fn delete_card_from_locker( ) -> errors::RouterResult { metrics::DELETE_FROM_LOCKER.add(&metrics::CONTEXT, 1, &[]); - request::record_operation_time( + common_utils::metrics::utils::record_operation_time( async move { delete_card_from_hs_locker(state, customer_id, merchant_id, card_reference) .await @@ -1196,6 +1186,7 @@ pub async fn delete_card_from_locker( }) }, &metrics::CARD_DELETE_TIME, + &metrics::CONTEXT, &[], ) .await @@ -1758,6 +1749,7 @@ pub async fn list_payment_methods( helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, + &key_store, req.client_secret.clone(), ) .await? @@ -1885,94 +1877,31 @@ pub async fn list_payment_methods( .await?; // filter out connectors based on the business country - let filtered_mcas = - helpers::filter_mca_based_on_business_profile(all_mcas.clone(), profile_id.clone()); + let filtered_mcas = helpers::filter_mca_based_on_business_profile(all_mcas, profile_id); logger::debug!(mca_before_filtering=?filtered_mcas); let mut response: Vec = vec![]; + for mca in &filtered_mcas { + let payment_methods = match &mca.payment_methods_enabled { + Some(pm) => pm.clone(), + None => continue, + }; - // Key creation for storing PM_FILTER_CGRAPH - #[cfg(feature = "business_profile_routing")] - let key = { - let profile_id = profile_id - .clone() - .get_required_value("profile_id") - .change_context(errors::ApiErrorResponse::GenericNotFoundError { - message: "Profile id not found".to_string(), - })?; - format!( - "pm_filters_cgraph_{}_{}", - &merchant_account.merchant_id, profile_id + filter_payment_methods( + payment_methods, + &mut req, + &mut response, + payment_intent.as_ref(), + payment_attempt.as_ref(), + billing_address.as_ref(), + mca.connector_name.clone(), + pm_config_mapping, + &state.conf.mandates.supported_payment_methods, + &state.conf.mandates.update_mandate_supported, + &state.conf.saved_payment_methods, ) - }; - - #[cfg(not(feature = "business_profile_routing"))] - let key = { format!("pm_filters_cgraph_{}", &merchant_account.merchant_id) }; - - if let Some(graph) = get_merchant_pm_filter_graph(&state, &key).await { - // Derivation of PM_FILTER_CGRAPH from MokaCache successful - for mca in &filtered_mcas { - let payment_methods = match &mca.payment_methods_enabled { - Some(pm) => pm, - None => continue, - }; - filter_payment_methods( - &graph, - payment_methods, - &mut req, - &mut response, - payment_intent.as_ref(), - payment_attempt.as_ref(), - billing_address.as_ref(), - mca.connector_name.clone(), - &state.conf.saved_payment_methods, - ) - .await?; - } - } else { - // No PM_FILTER_CGRAPH Cache present in MokaCache - let mut builder = cgraph::ConstraintGraphBuilder::<'static, _>::new(); - for mca in &filtered_mcas { - let payment_methods = match &mca.payment_methods_enabled { - Some(pm) => pm, - None => continue, - }; - if let Err(e) = make_pm_graph( - &mut builder, - payment_methods, - mca.connector_name.clone(), - pm_config_mapping, - &state.conf.mandates.supported_payment_methods, - &state.conf.mandates.update_mandate_supported, - ) { - logger::error!( - "Failed to construct constraint graph for list payment methods {e:?}" - ); - } - } - - // Refreshing our CGraph cache - let graph = refresh_pm_filters_cache(&state, &key, builder.build()).await; - - for mca in &filtered_mcas { - let payment_methods = match &mca.payment_methods_enabled { - Some(pm) => pm, - None => continue, - }; - filter_payment_methods( - &graph, - payment_methods, - &mut req, - &mut response, - payment_intent.as_ref(), - payment_attempt.as_ref(), - billing_address.as_ref(), - mca.connector_name.clone(), - &state.conf.saved_payment_methods, - ) - .await?; - } + .await?; } // Filter out wallet payment method from mca if customer has already saved it @@ -2684,6 +2613,7 @@ pub async fn list_payment_methods( Box::pin(call_surcharge_decision_management( state, &merchant_account, + &key_store, &business_profile, payment_attempt, payment_intent, @@ -2762,9 +2692,11 @@ async fn validate_payment_method_and_client_secret( Ok(()) } +#[allow(clippy::too_many_arguments)] pub async fn call_surcharge_decision_management( state: routes::SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, business_profile: &BusinessProfile, payment_attempt: &storage::PaymentAttempt, payment_intent: storage::PaymentIntent, @@ -2803,6 +2735,7 @@ pub async fn call_surcharge_decision_management( surcharge_applicable: true, updated_by: merchant_account.storage_scheme.to_string(), }, + key_store, merchant_account.storage_scheme, ) .await @@ -2815,6 +2748,7 @@ pub async fn call_surcharge_decision_management( pub async fn call_surcharge_decision_management_for_saved_card( state: &routes::SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, business_profile: &BusinessProfile, payment_attempt: &storage::PaymentAttempt, payment_intent: storage::PaymentIntent, @@ -2850,6 +2784,7 @@ pub async fn call_surcharge_decision_management_for_saved_card( surcharge_applicable: true, updated_by: merchant_account.storage_scheme.to_string(), }, + key_store, merchant_account.storage_scheme, ) .await @@ -2861,18 +2796,20 @@ pub async fn call_surcharge_decision_management_for_saved_card( #[allow(clippy::too_many_arguments)] pub async fn filter_payment_methods( - graph: &ConstraintGraph<'_, dir::DirValue>, - payment_methods: &[serde_json::Value], + payment_methods: Vec, req: &mut api::PaymentMethodListRequest, resp: &mut Vec, payment_intent: Option<&storage::PaymentIntent>, payment_attempt: Option<&storage::PaymentAttempt>, address: Option<&domain::Address>, connector: String, + config: &settings::ConnectorFilters, + supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, + supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, saved_payment_methods: &settings::EligiblePaymentMethods, ) -> errors::CustomResult<(), errors::ApiErrorResponse> { - for payment_method in payment_methods.iter() { - let parse_result = serde_json::from_value::(payment_method.clone()); + for payment_method in payment_methods.into_iter() { + let parse_result = serde_json::from_value::(payment_method); if let Ok(payment_methods_enabled) = parse_result { let payment_method = payment_methods_enabled.payment_method; @@ -2903,13 +2840,57 @@ pub async fn filter_payment_methods( .map(|minor_amount| minor_amount.get_amount_as_i64()), ) { - let payment_method_object = payment_method_type_info.clone(); + let mut payment_method_object = payment_method_type_info; + + let filter; + ( + payment_method_object.accepted_countries, + req.accepted_countries, + filter, + ) = filter_pm_country_based( + &payment_method_object.accepted_countries, + &req.accepted_countries, + ); + let filter2; + ( + payment_method_object.accepted_currencies, + req.accepted_currencies, + filter2, + ) = filter_pm_currencies_based( + &payment_method_object.accepted_currencies, + &req.accepted_currencies, + ); - let pm_dir_value: dir::DirValue = - (payment_method_type_info.payment_method_type, payment_method) - .into_dir_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("pm_value_node not created")?; + let filter4 = filter_pm_card_network_based( + payment_method_object.card_networks.as_ref(), + req.card_networks.as_ref(), + &payment_method_object.payment_method_type, + ); + + let filter3 = if let Some(payment_intent) = payment_intent { + filter_payment_country_based(&payment_method_object, address).await? + && filter_payment_currency_based(payment_intent, &payment_method_object) + && filter_payment_amount_based(payment_intent, &payment_method_object) + && filter_payment_mandate_based(payment_attempt, &payment_method_object) + .await? + } else { + true + }; + + let filter5 = filter_pm_based_on_config( + config, + &connector, + &payment_method_object.payment_method_type, + payment_attempt, + &mut payment_method_object.card_networks, + &address.and_then(|inner| inner.country), + payment_attempt.and_then(|value| value.currency), + ); + + let filter6 = filter_pm_based_on_allowed_types( + allowed_payment_method_types.as_ref(), + &payment_method_object.payment_method_type, + ); let connector_variant = api_enums::Connector::from_str(connector.as_str()) .change_context(errors::ConnectorError::InvalidConnectorName) @@ -2919,85 +2900,35 @@ pub async fn filter_payment_methods( .attach_printable_lazy(|| { format!("unable to parse connector name {connector:?}") })?; - - let mut context_values: Vec = Vec::new(); - context_values.push(pm_dir_value.clone()); - - payment_intent.map(|intent| { - intent.currency.map(|currency| { - context_values.push(dir::DirValue::PaymentCurrency(currency)) - }) - }); - address.map(|address| { - address.country.map(|country| { - context_values.push(dir::DirValue::BillingCountry( - common_enums::Country::from_alpha2(country), - )) - }) - }); - - // Addition of Connector to context - if let Ok(connector) = api_enums::RoutableConnectors::from_str( - connector_variant.to_string().as_str(), - ) { - context_values.push(dir::DirValue::Connector(Box::new( - api_models::routing::ast::ConnectorChoice { - connector, - #[cfg(not(feature = "connector_choice_mca_id"))] - sub_label: None, - }, - ))); - }; - - let filter_pm_based_on_allowed_types = filter_pm_based_on_allowed_types( - allowed_payment_method_types.as_ref(), - &payment_method_object.payment_method_type, - ); - - if payment_attempt + let filter7 = payment_attempt .and_then(|attempt| attempt.mandate_details.as_ref()) - .is_some() - { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NewMandate, - )); - }; + .map(|_mandate_details| { + filter_pm_based_on_supported_payments_for_mandate( + supported_payment_methods_for_mandate, + &payment_method, + &payment_method_object.payment_method_type, + connector_variant, + ) + }) + .unwrap_or(true); - payment_attempt + let filter8 = payment_attempt .and_then(|attempt| attempt.mandate_data.as_ref()) .map(|mandate_detail| { if mandate_detail.update_mandate_id.is_some() { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::UpdateMandate, - )); + filter_pm_based_on_update_mandate_support_for_connector( + supported_payment_methods_for_update_mandate, + &payment_method, + &payment_method_object.payment_method_type, + connector_variant, + ) + } else { + true } - }); - - payment_attempt - .map(|attempt| { - attempt.mandate_data.is_none() && attempt.mandate_details.is_none() }) - .and_then(|res| { - res.then(|| { - context_values.push(dir::DirValue::PaymentType( - euclid::enums::PaymentType::NonMandate, - )) - }) - }); - - payment_attempt - .and_then(|inner| inner.capture_method) - .map(|capture_method| { - context_values.push(dir::DirValue::CaptureMethod(capture_method)); - }); - - let filter_pm_card_network_based = filter_pm_card_network_based( - payment_method_object.card_networks.as_ref(), - req.card_networks.as_ref(), - &payment_method_object.payment_method_type, - ); + .unwrap_or(true); - let saved_payment_methods_filter = req + let filter9 = req .client_secret .as_ref() .map(|cs| { @@ -3011,28 +2942,25 @@ pub async fn filter_payment_methods( }) .unwrap_or(true); - let context = AnalysisContext::from_dir_values(context_values.clone()); + let connector = connector.clone(); - let result = graph.key_value_analysis( - pm_dir_value.clone(), - &context, - &mut cgraph::Memoization::new(), - &mut cgraph::CycleCheck::new(), - None, + let response_pm_type = ResponsePaymentMethodIntermediate::new( + payment_method_object, + connector, + payment_method, ); - if filter_pm_based_on_allowed_types - && filter_pm_card_network_based - && saved_payment_methods_filter - && matches!(result, Ok(())) + + if filter + && filter2 + && filter3 + && filter4 + && filter5 + && filter6 + && filter7 + && filter8 + && filter9 { - let response_pm_type = ResponsePaymentMethodIntermediate::new( - payment_method_object, - connector.clone(), - payment_method, - ); resp.push(response_pm_type); - } else { - logger::error!("Filtering Payment Methods Failed"); } } } @@ -3040,12 +2968,287 @@ pub async fn filter_payment_methods( } Ok(()) } +pub fn filter_pm_based_on_update_mandate_support_for_connector( + supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, + payment_method: &api_enums::PaymentMethod, + payment_method_type: &api_enums::PaymentMethodType, + connector: api_enums::Connector, +) -> bool { + if payment_method == &api_enums::PaymentMethod::Card { + supported_payment_methods_for_mandate + .0 + .get(payment_method) + .map(|payment_method_type_hm| { + let pm_credit = payment_method_type_hm + .0 + .get(&api_enums::PaymentMethodType::Credit) + .map(|conn| conn.connector_list.clone()) + .unwrap_or_default(); + let pm_debit = payment_method_type_hm + .0 + .get(&api_enums::PaymentMethodType::Debit) + .map(|conn| conn.connector_list.clone()) + .unwrap_or_default(); + &pm_credit | &pm_debit + }) + .map(|supported_connectors| supported_connectors.contains(&connector)) + .unwrap_or(false) + } else { + supported_payment_methods_for_mandate + .0 + .get(payment_method) + .and_then(|payment_method_type_hm| payment_method_type_hm.0.get(payment_method_type)) + .map(|supported_connectors| supported_connectors.connector_list.contains(&connector)) + .unwrap_or(false) + } +} -fn filter_pm_based_on_allowed_types( - allowed_types: Option<&Vec>, +fn filter_pm_based_on_supported_payments_for_mandate( + supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, + payment_method: &api_enums::PaymentMethod, payment_method_type: &api_enums::PaymentMethodType, + connector: api_enums::Connector, ) -> bool { - allowed_types.map_or(true, |pm| pm.contains(payment_method_type)) + supported_payment_methods_for_mandate + .0 + .get(payment_method) + .and_then(|payment_method_type_hm| payment_method_type_hm.0.get(payment_method_type)) + .map(|supported_connectors| supported_connectors.connector_list.contains(&connector)) + .unwrap_or(false) +} + +fn filter_pm_based_on_config<'a>( + config: &'a settings::ConnectorFilters, + connector: &'a str, + payment_method_type: &'a api_enums::PaymentMethodType, + payment_attempt: Option<&storage::PaymentAttempt>, + card_network: &mut Option>, + country: &Option, + currency: Option, +) -> bool { + config + .0 + .get(connector) + .or_else(|| config.0.get("default")) + .and_then(|inner| match payment_method_type { + api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { + let country_currency_filter = inner + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + .map(|value| global_country_currency_filter(value, country, currency)); + + card_network_filter(country, currency, card_network, inner); + + let capture_method_filter = payment_attempt + .and_then(|inner| inner.capture_method) + .and_then(|capture_method| { + (capture_method == storage_enums::CaptureMethod::Manual).then(|| { + filter_pm_based_on_capture_method_used(inner, payment_method_type) + }) + }); + + Some( + country_currency_filter.unwrap_or(true) + && capture_method_filter.unwrap_or(true), + ) + } + payment_method_type => inner + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + .map(|value| global_country_currency_filter(value, country, currency)), + }) + .unwrap_or(true) +} + +///Filters the payment method list on basis of Capture methods, checks whether the connector issues Manual payments using cards or not if not it won't be visible in payment methods list +fn filter_pm_based_on_capture_method_used( + payment_method_filters: &settings::PaymentMethodFilters, + payment_method_type: &api_enums::PaymentMethodType, +) -> bool { + payment_method_filters + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + .and_then(|v| v.not_available_flows) + .and_then(|v| v.capture_method) + .map(|v| !matches!(v, api_enums::CaptureMethod::Manual)) + .unwrap_or(true) +} + +fn card_network_filter( + country: &Option, + currency: Option, + card_network: &mut Option>, + payment_method_filters: &settings::PaymentMethodFilters, +) { + if let Some(value) = card_network.as_mut() { + let filtered_card_networks = value + .iter() + .filter(|&element| { + let key = settings::PaymentMethodFilterKey::CardNetwork(element.clone()); + payment_method_filters + .0 + .get(&key) + .map(|value| global_country_currency_filter(value, country, currency)) + .unwrap_or(true) + }) + .cloned() + .collect::>(); + *value = filtered_card_networks; + } +} + +fn global_country_currency_filter( + item: &settings::CurrencyCountryFlowFilter, + country: &Option, + currency: Option, +) -> bool { + let country_condition = item + .country + .as_ref() + .zip(country.as_ref()) + .map(|(lhs, rhs)| lhs.contains(rhs)); + let currency_condition = item + .currency + .as_ref() + .zip(currency) + .map(|(lhs, rhs)| lhs.contains(&rhs)); + country_condition.unwrap_or(true) && currency_condition.unwrap_or(true) +} + +fn filter_pm_card_network_based( + pm_card_networks: Option<&Vec>, + request_card_networks: Option<&Vec>, + pm_type: &api_enums::PaymentMethodType, +) -> bool { + match pm_type { + api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { + match (pm_card_networks, request_card_networks) { + (Some(pm_card_networks), Some(request_card_networks)) => request_card_networks + .iter() + .all(|card_network| pm_card_networks.contains(card_network)), + (None, Some(_)) => false, + _ => true, + } + } + _ => true, + } +} +fn filter_pm_country_based( + accepted_countries: &Option, + req_country_list: &Option>, +) -> ( + Option, + Option>, + bool, +) { + match (accepted_countries, req_country_list) { + (None, None) => (None, None, true), + (None, Some(ref r)) => ( + Some(admin::AcceptedCountries::EnableOnly(r.to_vec())), + Some(r.to_vec()), + true, + ), + (Some(l), None) => (Some(l.to_owned()), None, true), + (Some(l), Some(ref r)) => { + let updated = match l { + admin::AcceptedCountries::EnableOnly(acc) => { + filter_accepted_enum_based(&Some(acc.clone()), &Some(r.to_owned())) + .map(admin::AcceptedCountries::EnableOnly) + } + + admin::AcceptedCountries::DisableOnly(den) => { + filter_disabled_enum_based(&Some(den.clone()), &Some(r.to_owned())) + .map(admin::AcceptedCountries::DisableOnly) + } + + admin::AcceptedCountries::AllAccepted => { + Some(admin::AcceptedCountries::AllAccepted) + } + }; + + (updated, Some(r.to_vec()), true) + } + } +} + +fn filter_pm_currencies_based( + accepted_currency: &Option, + req_currency_list: &Option>, +) -> ( + Option, + Option>, + bool, +) { + match (accepted_currency, req_currency_list) { + (None, None) => (None, None, true), + (None, Some(ref r)) => ( + Some(admin::AcceptedCurrencies::EnableOnly(r.to_vec())), + Some(r.to_vec()), + true, + ), + (Some(l), None) => (Some(l.to_owned()), None, true), + (Some(l), Some(ref r)) => { + let updated = match l { + admin::AcceptedCurrencies::EnableOnly(acc) => { + filter_accepted_enum_based(&Some(acc.clone()), &Some(r.to_owned())) + .map(admin::AcceptedCurrencies::EnableOnly) + } + + admin::AcceptedCurrencies::DisableOnly(den) => { + filter_disabled_enum_based(&Some(den.clone()), &Some(r.to_owned())) + .map(admin::AcceptedCurrencies::DisableOnly) + } + + admin::AcceptedCurrencies::AllAccepted => { + Some(admin::AcceptedCurrencies::AllAccepted) + } + }; + + (updated, Some(r.to_vec()), true) + } + } +} + +fn filter_accepted_enum_based( + left: &Option>, + right: &Option>, +) -> Option> { + match (left, right) { + (Some(ref l), Some(ref r)) => { + let a: HashSet<&T> = HashSet::from_iter(l.iter()); + let b: HashSet<&T> = HashSet::from_iter(r.iter()); + + let y: Vec = a.intersection(&b).map(|&i| i.to_owned()).collect(); + Some(y) + } + (Some(ref l), None) => Some(l.to_vec()), + (_, _) => None, + } +} + +fn filter_disabled_enum_based( + left: &Option>, + right: &Option>, +) -> Option> { + match (left, right) { + (Some(ref l), Some(ref r)) => { + let mut enabled = Vec::new(); + for element in r { + if !l.contains(element) { + enabled.push(element.to_owned()); + } + } + Some(enabled) + } + (None, Some(r)) => Some(r.to_vec()), + (_, _) => None, + } } fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Option) -> bool { @@ -3063,9 +3266,24 @@ fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Optio .map(|max_amt| amt <= max_amt.into()) }) .unwrap_or(true); + // let min_check = match (amount, payment_method.minimum_amount) { + // (Some(amt), Some(min_amt)) => amt >= min_amt, + // (_, _) => true, + // }; + // let max_check = match (amount, payment_method.maximum_amount) { + // (Some(amt), Some(max_amt)) => amt <= max_amt, + // (_, _) => true, + // }; (min_check && max_check) || amount == Some(0) } +fn filter_pm_based_on_allowed_types( + allowed_types: Option<&Vec>, + payment_method_type: &api_enums::PaymentMethodType, +) -> bool { + allowed_types.map_or(true, |pm| pm.contains(payment_method_type)) +} + fn filter_recurring_based( payment_method: &RequestPaymentMethodTypes, recurring_enabled: Option, @@ -3082,23 +3300,54 @@ fn filter_installment_based( }) } -fn filter_pm_card_network_based( - pm_card_networks: Option<&Vec>, - request_card_networks: Option<&Vec>, - pm_type: &api_enums::PaymentMethodType, +async fn filter_payment_country_based( + pm: &RequestPaymentMethodTypes, + address: Option<&domain::Address>, +) -> errors::CustomResult { + Ok(address.map_or(true, |address| { + address.country.as_ref().map_or(true, |country| { + pm.accepted_countries.as_ref().map_or(true, |ac| match ac { + admin::AcceptedCountries::EnableOnly(acc) => acc.contains(country), + admin::AcceptedCountries::DisableOnly(den) => !den.contains(country), + admin::AcceptedCountries::AllAccepted => true, + }) + }) + })) +} + +fn filter_payment_currency_based( + payment_intent: &storage::PaymentIntent, + pm: &RequestPaymentMethodTypes, ) -> bool { - match pm_type { - api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { - match (pm_card_networks, request_card_networks) { - (Some(pm_card_networks), Some(request_card_networks)) => request_card_networks - .iter() - .all(|card_network| pm_card_networks.contains(card_network)), - (None, Some(_)) => false, - _ => true, - } - } - _ => true, - } + payment_intent.currency.map_or(true, |currency| { + pm.accepted_currencies.as_ref().map_or(true, |ac| match ac { + admin::AcceptedCurrencies::EnableOnly(acc) => acc.contains(¤cy), + admin::AcceptedCurrencies::DisableOnly(den) => !den.contains(¤cy), + admin::AcceptedCurrencies::AllAccepted => true, + }) + }) +} + +fn filter_payment_amount_based( + payment_intent: &storage::PaymentIntent, + pm: &RequestPaymentMethodTypes, +) -> bool { + let amount = payment_intent.amount.get_amount_as_i64(); + (pm.maximum_amount.map_or(true, |amt| amount <= amt.into()) + && pm.minimum_amount.map_or(true, |amt| amount >= amt.into())) + || payment_intent.amount.get_amount_as_i64() == 0 +} + +async fn filter_payment_mandate_based( + payment_attempt: Option<&storage::PaymentAttempt>, + pm: &RequestPaymentMethodTypes, +) -> errors::CustomResult { + let recurring_filter = if !pm.recurring_enabled { + payment_attempt.map_or(true, |pa| pa.mandate_id.is_none()) + } else { + true + }; + Ok(recurring_filter) } pub async fn do_list_customer_pm_fetch_customer_if_not_passed( @@ -3107,10 +3356,25 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( key_store: domain::MerchantKeyStore, req: Option, customer_id: Option<&id_type::CustomerId>, + ephemeral_api_key: Option<&str>, ) -> errors::RouterResponse { let db = state.store.as_ref(); let limit = req.clone().and_then(|pml_req| pml_req.limit); + let auth_cust = if let Some(key) = ephemeral_api_key { + let key = state + .store() + .get_ephemeral_key(key) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + Some(key.customer_id.clone()) + } else { + None + }; + + let customer_id = customer_id.or(auth_cust.as_ref()); + if let Some(customer_id) = customer_id { Box::pin(list_customer_payment_method( &state, @@ -3127,6 +3391,7 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, + &key_store, cloned_secret, ) .await?; @@ -3462,6 +3727,7 @@ pub async fn list_customer_payment_method( call_surcharge_decision_management_for_saved_card( state, &merchant_account, + &key_store, &business_profile, &payment_attempt, payment_intent, @@ -3942,7 +4208,7 @@ impl TempLockerCardSupport { metrics::TASKS_ADDED_COUNT.add( &metrics::CONTEXT, 1, - &[request::add_attributes("flow", "DeleteTokenizeData")], + &add_attributes([("flow", "DeleteTokenizeData")]), ); Ok(card) } diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 21aa51b46a33..ca0cbb2a0f8d 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -346,7 +346,7 @@ pub fn mk_add_card_response_hs( let card = api::CardDetailFromLocker { scheme: None, last4_digits: Some(last4_digits), - issuer_country: None, + issuer_country: card.card_issuing_country, card_number: Some(card.card_number.clone()), expiry_month: Some(card.card_exp_month.clone()), expiry_year: Some(card.card_exp_year.clone()), diff --git a/crates/router/src/core/payment_methods/utils.rs b/crates/router/src/core/payment_methods/utils.rs index f732f8a7926c..1b9897f9f050 100644 --- a/crates/router/src/core/payment_methods/utils.rs +++ b/crates/router/src/core/payment_methods/utils.rs @@ -1,5 +1,5 @@ use std::{str::FromStr, sync::Arc}; - +: use api_models::{ admin::{self, PaymentMethodsEnabled}, enums as api_enums, diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 9e20017d57fb..4e17ec6abd3a 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -7,7 +7,7 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use masking::PeekInterface; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use scheduler::{types::process_data, utils as process_tracker_utils}; #[cfg(feature = "payouts")] @@ -1178,7 +1178,7 @@ pub async fn start_tokenize_data_workflow( db.as_scheduler() .finish_process_with_business_status( tokenize_tracker.clone(), - "COMPLETED_BY_PT".to_string(), + diesel_models::process_tracker::business_status::COMPLETED_BY_PT, ) .await?; } @@ -1232,16 +1232,16 @@ pub async fn retry_delete_tokenize( metrics::TASKS_RESET_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "flow", - "DeleteTokenizeData", - )], + &add_attributes([("flow", "DeleteTokenizeData")]), ); retry_schedule } None => db .as_scheduler() - .finish_process_with_business_status(pt, "RETRIES_EXCEEDED".to_string()) + .finish_process_with_business_status( + pt, + diesel_models::process_tracker::business_status::RETRIES_EXCEEDED, + ) .await .map_err(Into::into), } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 50eedcfb5680..9e9156992f59 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -42,7 +42,7 @@ pub use hyperswitch_domain_models::{ }; use masking::{ExposeInterface, Secret}; use redis_interface::errors::RedisError; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; #[cfg(feature = "olap")] use router_types::transformers::ForeignFrom; use scheduler::utils as pt_utils; @@ -330,6 +330,7 @@ where &validate_result.payment_id, payment_data, router_data, + &key_store, merchant_account.storage_scheme, ) .await? @@ -434,6 +435,7 @@ where &validate_result.payment_id, payment_data, router_data, + &key_store, merchant_account.storage_scheme, ) .await? @@ -857,16 +859,13 @@ pub trait PaymentRedirectFlow: Sync { metrics::REDIRECTION_TRIGGERED.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", req.connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes( - "merchant_id", - merchant_account.merchant_id.to_owned(), - ), - ], + ("merchant_id", merchant_account.merchant_id.to_owned()), + ]), ); let connector = req.connector.clone().get_required_value("connector")?; @@ -1161,6 +1160,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { .find_payment_intent_by_payment_id_merchant_id( &payment_id, &merchant_id, + &merchant_key_store, merchant_account.storage_scheme, ) .await @@ -1440,7 +1440,7 @@ where *payment_data = pd; // Validating the blocklist guard and generate the fingerprint - blocklist_guard(state, merchant_account, operation, payment_data).await?; + blocklist_guard(state, merchant_account, key_store, operation, payment_data).await?; let updated_customer = call_create_connector_customer_if_required( state, @@ -1464,7 +1464,12 @@ where .await?; let add_access_token_result = router_data - .add_access_token(state, &connector, merchant_account) + .add_access_token( + state, + &connector, + merchant_account, + payment_data.creds_identifier.as_ref(), + ) .await?; router_data = router_data.add_session_token(state, &connector).await?; @@ -1620,6 +1625,7 @@ where async fn blocklist_guard( state: &SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, operation: &BoxedOperation<'_, F, ApiRequest>, payment_data: &mut PaymentData, ) -> CustomResult @@ -1648,7 +1654,7 @@ where if blocklist_guard_enabled { Ok(operation .to_domain()? - .guard_payment_against_blocklist(state, merchant_account, payment_data) + .guard_payment_against_blocklist(state, merchant_account, key_store, payment_data) .await?) } else { Ok(false) @@ -1743,7 +1749,7 @@ where if let Ok(router_types::PaymentsResponseData::SessionResponse { session_token, .. - }) = connector_response.response + }) = connector_response.response.clone() { // If session token is NoSessionTokenReceived, it is not pushed into the sessions_token as there is no response or there can be some error // In case of error, that error is already logged @@ -1754,13 +1760,16 @@ where payment_data.sessions_token.push(session_token); } } + if let Err(connector_error_response) = connector_response.response { + logger::error!( + "sessions_connector_error {} {:?}", + connector_name, + connector_error_response + ); + } } - Err(connector_error) => { - logger::error!( - "sessions_connector_error {} {:?}", - connector_name, - connector_error - ); + Err(api_error) => { + logger::error!("sessions_api_error {} {:?}", connector_name, api_error); } } } @@ -2655,16 +2664,22 @@ pub fn is_operation_complete_authorize(operation: &Op) -> bool { pub async fn list_payments( state: SessionState, merchant: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, constraints: api::PaymentListConstraints, ) -> RouterResponse { use hyperswitch_domain_models::errors::StorageError; helpers::validate_payment_list_request(&constraints)?; let merchant_id = &merchant.merchant_id; let db = state.store.as_ref(); - let payment_intents = - helpers::filter_by_constraints(db, &constraints, merchant_id, merchant.storage_scheme) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let payment_intents = helpers::filter_by_constraints( + db, + &constraints, + merchant_id, + &key_store, + merchant.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; let collected_futures = payment_intents.into_iter().map(|pi| { async { @@ -2721,6 +2736,7 @@ pub async fn list_payments( pub async fn apply_filters_on_payments( state: SessionState, merchant: domain::MerchantAccount, + merchant_key_store: domain::MerchantKeyStore, constraints: api::PaymentListFilterConstraints, ) -> RouterResponse { let limit = &constraints.limit; @@ -2730,6 +2746,7 @@ pub async fn apply_filters_on_payments( .get_filtered_payment_intents_attempt( &merchant.merchant_id, &constraints.clone().into(), + &merchant_key_store, merchant.storage_scheme, ) .await @@ -2774,6 +2791,7 @@ pub async fn apply_filters_on_payments( pub async fn get_filters_for_payments( state: SessionState, merchant: domain::MerchantAccount, + merchant_key_store: domain::MerchantKeyStore, time_range: api::TimeRange, ) -> RouterResponse { let db = state.store.as_ref(); @@ -2781,6 +2799,7 @@ pub async fn get_filters_for_payments( .filter_payment_intents_by_time_range_constraints( &merchant.merchant_id, &time_range, + &merchant_key_store, merchant.storage_scheme, ) .await @@ -3558,6 +3577,25 @@ pub async fn decide_multiplex_connector_for_normal_or_recurring_payment { + let skip_saving_wallet_at_connector_optional = + helpers::config_skip_saving_wallet_at_connector( + &*state.store, + &payment_data.payment_intent.merchant_id, + ) + .await?; + + if let Some(skip_saving_wallet_at_connector) = skip_saving_wallet_at_connector_optional + { + if let Some(payment_method_type) = payment_data.payment_attempt.payment_method_type + { + if skip_saving_wallet_at_connector.contains(&payment_method_type) { + logger::debug!("Override setup_future_usage from off_session to on_session based on the merchant's skip_saving_wallet_at_connector configuration to avoid creating a connector mandate."); + payment_data.payment_intent.setup_future_usage = + Some(enums::FutureUsage::OnSession); + } + } + }; + let first_choice = connectors .first() .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration) @@ -3867,7 +3905,12 @@ pub async fn payment_external_authentication( let storage_scheme = merchant_account.storage_scheme; let payment_id = req.payment_id; let payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + &key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; let attempt_id = payment_intent.active_attempt.get_id().clone(); diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index 4241db6742da..6166c256af8e 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use common_utils::ext_traits::AsyncExt; use error_stack::ResultExt; +use router_env::metrics::add_attributes; use crate::{ consts, @@ -57,6 +58,7 @@ pub async fn add_access_token< connector: &api_types::ConnectorData, merchant_account: &domain::MerchantAccount, router_data: &types::RouterData, + creds_identifier: Option<&String>, ) -> RouterResult { if connector .connector_name @@ -74,6 +76,7 @@ pub async fn add_access_token< let merchant_connector_id_or_connector_name = connector .merchant_connector_id .clone() + .or(creds_identifier.cloned()) .unwrap_or(connector.connector_name.to_string()); let old_access_token = store @@ -94,10 +97,7 @@ pub async fn add_access_token< metrics::ACCESS_TOKEN_CACHE_HIT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); Ok(Some(access_token)) } @@ -105,10 +105,7 @@ pub async fn add_access_token< metrics::ACCESS_TOKEN_CACHE_MISS.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); let cloned_router_data = router_data.clone(); @@ -247,10 +244,7 @@ pub async fn refresh_connector_auth( metrics::ACCESS_TOKEN_CREATION.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); Ok(access_token_router_data) } diff --git a/crates/router/src/core/payments/customers.rs b/crates/router/src/core/payments/customers.rs index 448e5fedb8b1..67fdd7746af1 100644 --- a/crates/router/src/core/payments/customers.rs +++ b/crates/router/src/core/payments/customers.rs @@ -1,4 +1,4 @@ -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use crate::{ core::{ @@ -54,10 +54,7 @@ pub async fn create_connector_customer( metrics::CONNECTOR_CUSTOMER_CREATE.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); let connector_customer_id = match resp.response { diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index bc5bc3ff0efa..f46094652000 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -58,6 +58,7 @@ pub trait Feature { state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult where F: Clone, @@ -166,6 +167,7 @@ default_imp_for_complete_authorize!( connector::Checkout, connector::Coinbase, connector::Cryptopay, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -247,6 +249,7 @@ default_imp_for_webhook_source_verification!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -338,6 +341,7 @@ default_imp_for_create_customer!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -423,6 +427,7 @@ default_imp_for_connector_redirect_response!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -490,6 +495,7 @@ default_imp_for_connector_request_id!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -581,6 +587,7 @@ default_imp_for_accept_dispute!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -694,6 +701,7 @@ default_imp_for_file_upload!( connector::Coinbase, connector::Cryptopay, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -784,6 +792,7 @@ default_imp_for_submit_evidence!( connector::Cybersource, connector::Coinbase, connector::Cryptopay, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -874,6 +883,7 @@ default_imp_for_defend_dispute!( connector::Cybersource, connector::Coinbase, connector::Cryptopay, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -962,6 +972,7 @@ default_imp_for_pre_processing_steps!( connector::Checkout, connector::Coinbase, connector::Cryptopay, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Iatapay, @@ -1027,6 +1038,7 @@ default_imp_for_payouts!( connector::Checkout, connector::Cryptopay, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1114,6 +1126,7 @@ default_imp_for_payouts_create!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1205,6 +1218,7 @@ default_imp_for_payouts_eligibility!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1293,6 +1307,7 @@ default_imp_for_payouts_fulfill!( connector::Checkout, connector::Cryptopay, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1380,6 +1395,7 @@ default_imp_for_payouts_cancel!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1470,6 +1486,7 @@ default_imp_for_payouts_quote!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1561,6 +1578,7 @@ default_imp_for_payouts_recipient!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -1654,6 +1672,7 @@ default_imp_for_payouts_recipient_account!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -1745,6 +1764,7 @@ default_imp_for_approve!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -1837,6 +1857,7 @@ default_imp_for_reject!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -1913,6 +1934,7 @@ default_imp_for_fraud_check!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2005,6 +2027,7 @@ default_imp_for_frm_sale!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2097,6 +2120,7 @@ default_imp_for_frm_checkout!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2189,6 +2213,7 @@ default_imp_for_frm_transaction!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2281,6 +2306,7 @@ default_imp_for_frm_fulfillment!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2373,6 +2399,7 @@ default_imp_for_frm_record_return!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2462,6 +2489,7 @@ default_imp_for_incremental_authorization!( connector::Checkout, connector::Cryptopay, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2551,6 +2579,7 @@ default_imp_for_revoking_mandates!( connector::Checkout, connector::Cryptopay, connector::Coinbase, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2701,6 +2730,7 @@ default_imp_for_connector_authentication!( connector::Cryptopay, connector::Coinbase, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, @@ -2787,6 +2817,7 @@ default_imp_for_authorize_session_token!( connector::Cryptopay, connector::Coinbase, connector::Cybersource, + connector::Datatrans, connector::Dlocal, connector::Ebanx, connector::Fiserv, diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index 6d4a88c0bf0e..f15274ba5be5 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -64,8 +64,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 7497ca6a9e5e..26b643248a6b 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use router_env::metrics::add_attributes; // use router_env::tracing::Instrument; use super::{ConstructFlowSpecificData, Feature}; @@ -96,8 +97,10 @@ impl Feature for types::PaymentsAu state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn add_session_token<'a>( @@ -209,13 +212,10 @@ impl Feature for types::PaymentsAu metrics::EXECUTE_PRETASK_COUNT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - ), - metrics::request::add_attributes("flow", format!("{:?}", api::Authorize)), - ], + &add_attributes([ + ("connector", connector.connector_name.to_string()), + ("flow", format!("{:?}", api::Authorize)), + ]), ); logger::debug!(completed_pre_tasks=?true); @@ -333,13 +333,10 @@ pub async fn authorize_preprocessing_steps( metrics::PREPROCESSING_STEPS_COUNT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes("connector", connector.connector_name.to_string()), - metrics::request::add_attributes( - "payment_method", - router_data.payment_method.to_string(), - ), - metrics::request::add_attributes( + &add_attributes([ + ("connector", connector.connector_name.to_string()), + ("payment_method", router_data.payment_method.to_string()), + ( "payment_method_type", router_data .request @@ -348,7 +345,7 @@ pub async fn authorize_preprocessing_steps( .map(|inner| inner.to_string()) .unwrap_or("null".to_string()), ), - ], + ]), ); let mut authorize_router_data = helpers::router_data_type_conversion::<_, F, _, _, _, _>( resp.clone(), diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index d7e8cc3d9b57..7af317bc403f 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use router_env::metrics::add_attributes; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -55,10 +56,7 @@ impl Feature metrics::PAYMENT_CANCEL_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); let connector_integration: services::BoxedConnectorIntegration< @@ -86,8 +84,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index 268281e51573..00ff6c2d1062 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -78,8 +78,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 5d0cb9f9f381..3a976219b61c 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use router_env::metrics::add_attributes; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -91,8 +92,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn add_payment_method_token<'a>( @@ -191,13 +194,10 @@ pub async fn complete_authorize_preprocessing_steps( metrics::PREPROCESSING_STEPS_COUNT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes("connector", connector.connector_name.to_string()), - metrics::request::add_attributes( - "payment_method", - router_data.payment_method.to_string(), - ), - ], + &add_attributes([ + ("connector", connector.connector_name.to_string()), + ("payment_method", router_data.payment_method.to_string()), + ]), ); let mut router_data_request = router_data.request.to_owned(); diff --git a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs index 58e5db8fbdef..6785203e409c 100644 --- a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs +++ b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs @@ -85,8 +85,10 @@ impl Feature, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index bec29f97e93f..cc8e15260462 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -104,8 +104,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index 865c6ea201d0..020e6ff101dc 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -63,8 +63,10 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn build_flow_specific_connector_request( diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 14a3b9327dc0..ef2116a68530 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{Report, ResultExt}; use masking::ExposeInterface; +use router_env::metrics::add_attributes; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -64,10 +65,7 @@ impl Feature for types::PaymentsSessio metrics::SESSION_TOKEN_CREATED.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); self.decide_flow( state, @@ -84,8 +82,10 @@ impl Feature for types::PaymentsSessio state: &routes::SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } } 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 987d3f33803a..bf5d0c047d8d 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -81,8 +81,10 @@ impl Feature for types::Setup state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&String>, ) -> RouterResult { - access_token::add_access_token(state, connector, merchant_account, self).await + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await } async fn add_payment_method_token<'a>( diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 7eada733aed5..e9a793efb8e0 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -26,7 +26,7 @@ use openssl::{ pkey::PKey, symm::{decrypt_aead, Cipher}, }; -use router_env::{instrument, logger, tracing}; +use router_env::{instrument, logger, metrics::add_attributes, tracing}; use uuid::Uuid; use x509_parser::parse_x509_certificate; @@ -604,6 +604,7 @@ pub async fn get_token_for_recurring_mandate( db.find_payment_intent_by_payment_id_merchant_id( payment_id, &mandate.merchant_id, + merchant_key_store, merchant_account.storage_scheme, ) .await @@ -1219,10 +1220,7 @@ where metrics::TASKS_ADDED_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "flow", - format!("{:#?}", operation), - )], + &add_attributes([("flow", format!("{:#?}", operation))]), ); super::add_process_sync_task(&*state.store, payment_attempt, stime) .await @@ -1233,10 +1231,7 @@ where metrics::TASKS_RESET_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "flow", - format!("{:#?}", operation), - )], + &add_attributes([("flow", format!("{:#?}", operation))]), ); super::reset_process_sync_task(&*state.store, payment_attempt, stime) .await @@ -2418,12 +2413,14 @@ pub(super) async fn filter_by_constraints( db: &dyn StorageInterface, constraints: &api::PaymentListConstraints, merchant_id: &str, + key_store: &domain::MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { let result = db .filter_payment_intent_by_constraints( merchant_id, &constraints.clone().into(), + key_store, storage_scheme, ) .await?; @@ -2839,6 +2836,7 @@ pub(crate) fn validate_pm_or_token_given( pub async fn verify_payment_intent_time_and_client_secret( db: &dyn StorageInterface, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, client_secret: Option, ) -> error_stack::Result, errors::ApiErrorResponse> { client_secret @@ -2849,6 +2847,7 @@ pub async fn verify_payment_intent_time_and_client_secret( .find_payment_intent_by_payment_id_merchant_id( &payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -2944,7 +2943,6 @@ mod tests { #[test] fn test_authenticate_client_secret_fulfillment_time_not_expired() { let payment_intent = PaymentIntent { - id: 21, payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, @@ -3004,7 +3002,6 @@ mod tests { #[test] fn test_authenticate_client_secret_fulfillment_time_expired() { let payment_intent = PaymentIntent { - id: 21, payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, @@ -3063,7 +3060,6 @@ mod tests { #[test] fn test_authenticate_client_secret_expired() { let payment_intent = PaymentIntent { - id: 21, payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, @@ -3406,10 +3402,7 @@ pub fn get_attempt_type( metrics::MANUAL_RETRY_REQUEST_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "merchant_id", - payment_attempt.merchant_id.clone(), - )], + &add_attributes([("merchant_id", payment_attempt.merchant_id.clone())]), ); match payment_attempt.status { enums::AttemptStatus::Started @@ -3433,10 +3426,7 @@ pub fn get_attempt_type( metrics::MANUAL_RETRY_VALIDATION_FAILED.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "merchant_id", - payment_attempt.merchant_id.clone(), - )], + &add_attributes([("merchant_id", payment_attempt.merchant_id.clone())]), ); Err(errors::ApiErrorResponse::InternalServerError) .attach_printable("Payment Attempt unexpected state") @@ -3448,10 +3438,7 @@ pub fn get_attempt_type( metrics::MANUAL_RETRY_VALIDATION_FAILED.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "merchant_id", - payment_attempt.merchant_id.clone(), - )], + &add_attributes([("merchant_id", payment_attempt.merchant_id.clone())]), ); Err(report!(errors::ApiErrorResponse::PreconditionFailed { message: @@ -3466,10 +3453,7 @@ pub fn get_attempt_type( metrics::MANUAL_RETRY_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "merchant_id", - payment_attempt.merchant_id.clone(), - )], + &add_attributes([("merchant_id", payment_attempt.merchant_id.clone())]), ); Ok(AttemptType::New) } @@ -3607,6 +3591,7 @@ impl AttemptType { fetched_payment_intent: PaymentIntent, fetched_payment_attempt: PaymentAttempt, db: &dyn StorageInterface, + key_store: &domain::MerchantKeyStore, storage_scheme: storage::enums::MerchantStorageScheme, ) -> RouterResult<(PaymentIntent, PaymentAttempt)> { match self { @@ -3648,6 +3633,7 @@ impl AttemptType { attempt_count: new_attempt_count, updated_by: storage_scheme.to_string(), }, + key_store, storage_scheme, ) .await @@ -4793,3 +4779,26 @@ pub async fn get_payment_external_authentication_flow_during_confirm( pub fn get_redis_key_for_extended_card_info(merchant_id: &str, payment_id: &str) -> String { format!("{merchant_id}_{payment_id}_extended_card_info") } + +pub async fn config_skip_saving_wallet_at_connector( + db: &dyn StorageInterface, + merchant_id: &String, +) -> CustomResult>, errors::ApiErrorResponse> { + let config = db + .find_config_by_key_unwrap_or( + format!("skip_saving_wallet_at_connector_{}", merchant_id).as_str(), + Some("[]".to_string()), + ) + .await; + Ok(match config { + Ok(conf) => Some( + serde_json::from_str::>(&conf.config) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("skip_save_wallet_at_connector config parsing failed")?, + ), + Err(err) => { + logger::error!("{err}"); + None + } + }) +} diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 45e45bbe9cb6..b7eaf5fc0e6d 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -183,6 +183,7 @@ pub trait Domain: Send + Sync { &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -226,6 +227,7 @@ pub trait PostUpdateTracker: Send { payment_id: &api::PaymentIdType, payment_data: D, response: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult where @@ -322,6 +324,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -394,6 +397,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -467,6 +471,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -529,6 +534,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index a492cb760389..172c5943c0c9 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -52,7 +52,12 @@ impl GetTracker, api::PaymentsCaptureRequest> .change_context(errors::ApiErrorResponse::PaymentNotFound)?; payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -200,7 +205,7 @@ impl UpdateTracker, api::PaymentsCaptureRequest> for _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, - _merchant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( @@ -224,6 +229,7 @@ impl UpdateTracker, api::PaymentsCaptureRequest> for .update_payment_intent( payment_data.payment_intent, intent_status_update, + key_store, storage_scheme, ) .await diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index ce79f9392c80..94591d44e4bb 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -50,7 +50,12 @@ impl GetTracker, api::PaymentsCancelRequest> .change_context(errors::ApiErrorResponse::PaymentNotFound)?; let payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -213,7 +218,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, - _mechant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( @@ -242,6 +247,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for .update_payment_intent( payment_data.payment_intent, payment_intent_update, + key_store, storage_scheme, ) .await diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 02c69681a883..f6e6dee14a53 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -53,7 +53,12 @@ impl GetTracker, api::PaymentsCaptu .change_context(errors::ApiErrorResponse::PaymentNotFound)?; payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 1ff95c37f667..b2e86967301d 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -52,7 +52,12 @@ impl GetTracker, api::PaymentsRequest> for Co .change_context(errors::ApiErrorResponse::PaymentNotFound)?; payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; payment_intent.setup_future_usage = request @@ -418,6 +423,7 @@ impl Domain for CompleteAuthorize { &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -435,7 +441,7 @@ impl UpdateTracker, api::PaymentsRequest> for Comple _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, - _merchant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -450,7 +456,12 @@ impl UpdateTracker, api::PaymentsRequest> for Comple let payment_intent = payment_data.payment_intent.clone(); let updated_payment_intent = db - .update_payment_intent(payment_intent, payment_intent_update, storage_scheme) + .update_payment_intent( + payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index cff51ed80918..bfd63111e27e 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -74,6 +74,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .find_payment_intent_by_payment_id_merchant_id( &payment_id, m_merchant_id.as_str(), + key_store, storage_scheme, ) .await @@ -304,6 +305,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_intent, payment_attempt, &*state.store, + key_store, storage_scheme, ) .await?; @@ -860,9 +862,16 @@ impl Domain for PaymentConfirm { &'a self, state: &SessionState, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, ) -> CustomResult { - blocklist_utils::validate_data_for_blocklist(state, merchant_account, payment_data).await + blocklist_utils::validate_data_for_blocklist( + state, + merchant_account, + key_store, + payment_data, + ) + .await } #[instrument(skip_all)] @@ -1218,6 +1227,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let m_db = state.clone().store; let m_storage_scheme = storage_scheme.to_string(); let session_expiry = m_payment_data_payment_intent.session_expiry; + let m_key_store = key_store.clone(); let payment_intent_fut = tokio::spawn( async move { @@ -1246,6 +1256,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen request_external_three_ds_authentication: None, frm_metadata: m_frm_metadata, }, + &m_key_store, storage_scheme, ) .map(|x| x.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index bb249e9a9cd1..870fd78abe36 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -281,7 +281,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .await?; payment_intent = db - .insert_payment_intent(payment_intent_new, storage_scheme) + .insert_payment_intent(payment_intent_new, merchant_key_store, storage_scheme) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayment { payment_id: payment_id.clone(), @@ -545,6 +545,7 @@ impl Domain for PaymentCreate { &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -562,7 +563,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, - _merchant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -639,6 +640,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen billing_address_id: None, updated_by: storage_scheme.to_string(), }, + key_store, storage_scheme, ) .await @@ -964,8 +966,8 @@ impl PaymentCreate { active_attempt_id: String, profile_id: String, session_expiry: PrimitiveDateTime, - ) -> RouterResult { - let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); + ) -> RouterResult { + let created_at @ modified_at @ last_synced = common_utils::date_time::now(); let status = helpers::payment_intent_status_fsm( request @@ -1021,7 +1023,7 @@ impl PaymentCreate { .change_context(errors::ApiErrorResponse::InternalServerError)? .map(Secret::new); - Ok(storage::PaymentIntentNew { + Ok(storage::PaymentIntent { payment_id: payment_id.to_string(), merchant_id: merchant_account.merchant_id.to_string(), status, @@ -1030,7 +1032,7 @@ impl PaymentCreate { description: request.description.clone(), created_at, modified_at, - last_synced, + last_synced: Some(last_synced), client_secret: Some(client_secret), setup_future_usage: request.setup_future_usage, off_session: request.off_session, diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index cf8217718cbc..a3a8130a9f67 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -47,7 +47,12 @@ impl GetTracker, PaymentsCancelRequest> for P .change_context(errors::ApiErrorResponse::PaymentNotFound)?; let payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -197,7 +202,7 @@ impl UpdateTracker, PaymentsCancelRequest> for Payme _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, - _mechant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _should_decline_transaction: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<(BoxedOperation<'b, F, PaymentsCancelRequest>, PaymentData)> @@ -231,6 +236,7 @@ impl UpdateTracker, PaymentsCancelRequest> for Payme .update_payment_intent( payment_data.payment_intent, intent_status_update, + key_store, storage_scheme, ) .await diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 4843d1145d50..4b529d2b3999 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -59,6 +59,7 @@ impl PostUpdateTracker, types::PaymentsAuthor types::PaymentsAuthorizeData, types::PaymentsResponseData, >, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -73,6 +74,7 @@ impl PostUpdateTracker, types::PaymentsAuthor payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -245,6 +247,7 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu types::PaymentsIncrementalAuthorizationData, types::PaymentsResponseData, >, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -305,6 +308,7 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu .update_payment_intent( payment_data.payment_intent.clone(), payment_intent_update, + key_store, storage_scheme, ) .await @@ -372,6 +376,7 @@ impl PostUpdateTracker, types::PaymentsSyncData> for payment_id: &api::PaymentIdType, payment_data: PaymentData, router_data: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -382,6 +387,7 @@ impl PostUpdateTracker, types::PaymentsSyncData> for payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await @@ -422,6 +428,7 @@ impl PostUpdateTracker, types::PaymentsSessionData> payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -432,6 +439,7 @@ impl PostUpdateTracker, types::PaymentsSessionData> payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -450,6 +458,7 @@ impl PostUpdateTracker, types::PaymentsCaptureData> payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -460,6 +469,7 @@ impl PostUpdateTracker, types::PaymentsCaptureData> payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -476,7 +486,7 @@ impl PostUpdateTracker, types::PaymentsCancelData> f payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, - + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -487,6 +497,7 @@ impl PostUpdateTracker, types::PaymentsCancelData> f payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -505,7 +516,7 @@ impl PostUpdateTracker, types::PaymentsApproveData> payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, - + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -516,6 +527,7 @@ impl PostUpdateTracker, types::PaymentsApproveData> payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -532,7 +544,7 @@ impl PostUpdateTracker, types::PaymentsRejectData> f payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, - + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -543,6 +555,7 @@ impl PostUpdateTracker, types::PaymentsRejectData> f payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -565,7 +578,7 @@ impl PostUpdateTracker, types::SetupMandateRequestDa types::SetupMandateRequestData, types::PaymentsResponseData, >, - + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -581,6 +594,7 @@ impl PostUpdateTracker, types::SetupMandateRequestDa payment_id, payment_data, router_data, + key_store, storage_scheme, )) .await?; @@ -662,6 +676,7 @@ impl PostUpdateTracker, types::CompleteAuthorizeData payment_id: &api::PaymentIdType, payment_data: PaymentData, response: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> where @@ -672,6 +687,7 @@ impl PostUpdateTracker, types::CompleteAuthorizeData payment_id, payment_data, response, + key_store, storage_scheme, )) .await @@ -708,6 +724,7 @@ async fn payment_response_update_tracker( _payment_id: &api::PaymentIdType, mut payment_data: PaymentData, router_data: types::RouterData, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult> { // Update additional payment data with the payment method response that we received from connector @@ -1133,6 +1150,7 @@ async fn payment_response_update_tracker( }; let m_db = state.clone().store; + let m_key_store = key_store.clone(); let m_payment_data_payment_intent = payment_data.payment_intent.clone(); let m_payment_intent_update = payment_intent_update.clone(); let payment_intent_fut = tokio::spawn( @@ -1140,6 +1158,7 @@ async fn payment_response_update_tracker( m_db.update_payment_intent( m_payment_data_payment_intent, m_payment_intent_update, + &m_key_store, storage_scheme, ) .map(|x| x.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)) diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 4d81914aca5f..cd63d3aedb0a 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -52,7 +52,12 @@ impl GetTracker, api::PaymentsSessionRequest> let storage_scheme = merchant_account.storage_scheme; let mut payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -224,7 +229,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for _customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, - _mechant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( @@ -244,6 +249,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for metadata, updated_by: storage_scheme.to_string(), }, + key_store, storage_scheme, ) .await @@ -460,6 +466,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> errors::CustomResult { Ok(false) diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 7c1c117a7288..865da4fb793d 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -50,7 +50,12 @@ impl GetTracker, api::PaymentsStartRequest> f .change_context(errors::ApiErrorResponse::PaymentNotFound)?; payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -337,6 +342,7 @@ where &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 9971ea76aa3d..afe571275b98 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -133,6 +133,7 @@ impl Domain for PaymentStatus { &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -219,7 +220,7 @@ async fn get_tracker_for_sync< >( payment_id: &api::PaymentIdType, merchant_account: &domain::MerchantAccount, - mechant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, db: &dyn StorageInterface, request: &api::PaymentsRetrieveRequest, operation: Op, @@ -231,6 +232,7 @@ async fn get_tracker_for_sync< db, payment_id, &merchant_account.merchant_id, + key_store, storage_scheme, ) .await?; @@ -245,7 +247,7 @@ async fn get_tracker_for_sync< let shipping_address = helpers::get_address_by_id( db, payment_intent.shipping_address_id.clone(), - mechant_key_store, + key_store, &payment_intent.payment_id.clone(), &merchant_account.merchant_id, merchant_account.storage_scheme, @@ -254,7 +256,7 @@ async fn get_tracker_for_sync< let billing_address = helpers::get_address_by_id( db, payment_intent.billing_address_id.clone(), - mechant_key_store, + key_store, &payment_intent.payment_id.clone(), &merchant_account.merchant_id, merchant_account.storage_scheme, @@ -264,7 +266,7 @@ async fn get_tracker_for_sync< let payment_method_billing = helpers::get_address_by_id( db, payment_attempt.payment_method_billing_address_id.clone(), - mechant_key_store, + key_store, &payment_intent.payment_id.clone(), &merchant_account.merchant_id, merchant_account.storage_scheme, @@ -514,11 +516,11 @@ impl ValidateRequest for Payme } } -#[inline] pub async fn get_payment_intent_payment_attempt( db: &dyn StorageInterface, payment_id: &api::PaymentIdType, merchant_id: &str, + key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> RouterResult<(storage::PaymentIntent, storage::PaymentAttempt)> { let get_pi_pa = || async { @@ -526,7 +528,12 @@ pub async fn get_payment_intent_payment_attempt( match payment_id { api_models::payments::PaymentIdType::PaymentIntentId(ref id) => { pi = db - .find_payment_intent_by_payment_id_merchant_id(id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + id, + merchant_id, + key_store, + storage_scheme, + ) .await?; pa = db .find_payment_attempt_by_payment_id_merchant_id_attempt_id( @@ -549,6 +556,7 @@ pub async fn get_payment_intent_payment_attempt( .find_payment_intent_by_payment_id_merchant_id( pa.payment_id.as_str(), merchant_id, + key_store, storage_scheme, ) .await?; @@ -561,6 +569,7 @@ pub async fn get_payment_intent_payment_attempt( .find_payment_intent_by_payment_id_merchant_id( pa.payment_id.as_str(), merchant_id, + key_store, storage_scheme, ) .await?; @@ -578,6 +587,7 @@ pub async fn get_payment_intent_payment_attempt( .find_payment_intent_by_payment_id_merchant_id( pa.payment_id.as_str(), merchant_id, + key_store, storage_scheme, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index ffa122e54af3..a299b70e89b3 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -56,7 +56,12 @@ impl GetTracker, api::PaymentsRequest> for Pa let db = &*state.store; payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -548,6 +553,7 @@ impl Domain for PaymentUpdate { &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut PaymentData, ) -> CustomResult { Ok(false) @@ -565,7 +571,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen customer: Option, storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, - _key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> @@ -721,6 +727,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .request_external_three_ds_authentication, frm_metadata, }, + key_store, storage_scheme, ) .await diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 26ffd65035d6..249306693a22 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -45,7 +45,7 @@ impl payment_id: &api::PaymentIdType, request: &PaymentsIncrementalAuthorizationRequest, merchant_account: &domain::MerchantAccount, - _key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _payment_confirm_source: Option, ) -> RouterResult> @@ -58,7 +58,12 @@ impl .change_context(errors::ApiErrorResponse::PaymentNotFound)?; let payment_intent = db - .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -180,7 +185,7 @@ impl UpdateTracker, PaymentsIncrementalAut _customer: Option, storage_scheme: enums::MerchantStorageScheme, _updated_customer: Option, - _mechant_key_store: &domain::MerchantKeyStore, + key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( @@ -238,6 +243,7 @@ impl UpdateTracker, PaymentsIncrementalAut storage::PaymentIntentUpdate::AuthorizationCountUpdate { authorization_count: new_authorization_count, }, + key_store, storage_scheme, ) .await @@ -336,6 +342,7 @@ impl Domain &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, _payment_data: &mut payments::PaymentData, ) -> CustomResult { Ok(false) diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 82a08064a3ee..a04af7b0fefe 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -299,6 +299,7 @@ where state, connector.connector_name.to_string(), payment_data, + key_store, merchant_account.storage_scheme, router_data, is_step_up, @@ -329,6 +330,7 @@ pub async fn modify_trackers( state: &routes::SessionState, connector: String, payment_data: &mut payments::PaymentData, + key_store: &domain::MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, router_data: types::RouterData, is_step_up: bool, @@ -462,6 +464,7 @@ where attempt_count: new_attempt_count, updated_by: storage_scheme.to_string(), }, + key_store, storage_scheme, ) .await diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index b014f42a5ce0..64fe526f0c03 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -8,7 +8,7 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use masking::ExposeInterface; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use super::helpers; use crate::{ @@ -469,9 +469,10 @@ where }; let updated_card = Some(CardDetailFromLocker { - scheme: None, + scheme: existing_pm.scheme.clone(), last4_digits: Some(card.card_number.get_last4()), - issuer_country: None, + issuer_country: card.card_issuing_country, + card_isin: Some(card.card_number.get_card_isin()), card_number: Some(card.card_number), expiry_month: Some(card.card_exp_month), expiry_year: Some(card.card_exp_year), @@ -479,10 +480,9 @@ where card_fingerprint: None, card_holder_name: card.card_holder_name, nick_name: card.nick_name, - card_network: None, - card_isin: None, - card_issuer: None, - card_type: None, + card_network: card.card_network, + card_issuer: card.card_issuer, + card_type: card.card_type, saved_to_locker: true, }); @@ -515,35 +515,78 @@ where } }, None => { - let pm_metadata = create_payment_method_metadata(None, connector_token)?; - - locker_id = resp.payment_method.and_then(|pm| { - if pm == PaymentMethod::Card { - Some(resp.payment_method_id) - } else { - None + let customer_saved_pm_id_option = if payment_method_type + == Some(api_models::enums::PaymentMethodType::ApplePay) + || payment_method_type + == Some(api_models::enums::PaymentMethodType::GooglePay) + { + match state + .store + .find_payment_method_by_customer_id_merchant_id_list( + &customer_id, + merchant_id, + None, + ) + .await + { + Ok(customer_payment_methods) => Ok(customer_payment_methods + .iter() + .find(|payment_method| { + payment_method.payment_method_type == payment_method_type + }) + .map(|pm| pm.payment_method_id.clone())), + Err(error) => { + if error.current_context().is_db_not_found() { + Ok(None) + } else { + Err(error) + .change_context( + errors::ApiErrorResponse::InternalServerError, + ) + .attach_printable( + "failed to find payment methods for a customer", + ) + } + } } - }); - - resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm"); - payment_methods::cards::create_payment_method( - db, - &payment_method_create_request, - &customer_id, - &resp.payment_method_id, - locker_id, - merchant_id, - pm_metadata, - customer_acceptance, - pm_data_encrypted, - key_store, - connector_mandate_details, - None, - network_transaction_id, - merchant_account.storage_scheme, - encrypted_payment_method_billing_address, - ) - .await?; + } else { + Ok(None) + }?; + + if let Some(customer_saved_pm_id) = customer_saved_pm_id_option { + resp.payment_method_id = customer_saved_pm_id; + } else { + let pm_metadata = + create_payment_method_metadata(None, connector_token)?; + + locker_id = resp.payment_method.and_then(|pm| { + if pm == PaymentMethod::Card { + Some(resp.payment_method_id) + } else { + None + } + }); + + resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm"); + payment_methods::cards::create_payment_method( + db, + &payment_method_create_request, + &customer_id, + &resp.payment_method_id, + locker_id, + merchant_id, + pm_metadata, + customer_acceptance, + pm_data_encrypted, + key_store, + connector_mandate_details, + None, + network_transaction_id, + merchant_account.storage_scheme, + encrypted_payment_method_billing_address, + ) + .await?; + }; } } @@ -762,16 +805,10 @@ pub async fn add_payment_method_token( metrics::CONNECTOR_PAYMENT_METHOD_TOKENIZATION.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - ), - metrics::request::add_attributes( - "payment_method", - router_data.payment_method.to_string(), - ), - ], + &add_attributes([ + ("connector", connector.connector_name.to_string()), + ("payment_method", router_data.payment_method.to_string()), + ]), ); let pm_token = match resp.response { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 604bc5cda996..43d6908e1a09 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -11,7 +11,7 @@ use common_utils::{consts::X_HS_LATENCY, fp_utils, types::MinorUnit}; use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; use masking::{Maskable, PeekInterface, Secret}; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use super::{flows::Feature, types::AuthenticationData, PaymentData}; use crate::{ @@ -844,12 +844,12 @@ where metrics::PAYMENT_OPS_COUNT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes("operation", format!("{:?}", operation)), - metrics::request::add_attributes("merchant", merchant_id), - metrics::request::add_attributes("payment_method_type", payment_method_type), - metrics::request::add_attributes("payment_method", payment_method), - ], + &add_attributes([ + ("operation", format!("{:?}", operation)), + ("merchant", merchant_id), + ("payment_method_type", payment_method_type), + ("payment_method", payment_method), + ]), ); Ok(output) @@ -1482,6 +1482,7 @@ impl TryFrom> for types::PaymentsCancelDa let amount = MinorUnit::from(payment_data.amount); Ok(Self { amount: Some(amount.get_amount_as_i64()), // This should be removed once we start moving to connector module + minor_amount: Some(amount), currency: Some(payment_data.currency), connector_transaction_id: connector .connector diff --git a/crates/router/src/core/payouts/access_token.rs b/crates/router/src/core/payouts/access_token.rs index 5e40720f4efb..2f1892af821a 100644 --- a/crates/router/src/core/payouts/access_token.rs +++ b/crates/router/src/core/payouts/access_token.rs @@ -1,5 +1,6 @@ use common_utils::ext_traits::AsyncExt; use error_stack::ResultExt; +use router_env::metrics::add_attributes; use crate::{ consts, @@ -185,10 +186,7 @@ pub async fn refresh_connector_auth( metrics::ACCESS_TOKEN_CREATION.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ); Ok(access_token_router_data) } diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 09d18963a29e..e31a4dd75c74 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -98,6 +98,7 @@ pub async fn create_link_token( let payment_intent = oss_helpers::verify_payment_intent_time_and_client_secret( &*state.store, &merchant_account, + &key_store, payload.client_secret, ) .await?; @@ -280,6 +281,7 @@ async fn store_bank_details_in_payment_methods( .find_payment_intent_by_payment_id_merchant_id( &payload.payment_id, &merchant_account.merchant_id, + &key_store, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 0d441e95382d..9fac4e595a01 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -9,9 +9,10 @@ use common_utils::{ ext_traits::{AsyncExt, ValueExt}, types::MinorUnit, }; +use diesel_models::process_tracker::business_status; use error_stack::{report, ResultExt}; use masking::PeekInterface; -use router_env::{instrument, tracing}; +use router_env::{instrument, metrics::add_attributes, tracing}; use scheduler::{consumer::types::process_data, utils as process_tracker_utils}; #[cfg(feature = "olap")] use strum::IntoEnumIterator; @@ -56,6 +57,7 @@ pub async fn refund_create_core( .find_payment_intent_by_payment_id_merchant_id( &req.payment_id, merchant_id, + &key_store, merchant_account.storage_scheme, ) .await @@ -153,10 +155,7 @@ pub async fn trigger_refund_to_gateway( metrics::REFUND_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - routed_through.clone(), - )], + &add_attributes([("connector", routed_through.clone())]), ); let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name( @@ -183,13 +182,19 @@ pub async fn trigger_refund_to_gateway( payment_intent, payment_attempt, refund, - creds_identifier, + creds_identifier.clone(), charges, ) .await?; - let add_access_token_result = - access_token::add_access_token(state, &connector, merchant_account, &router_data).await?; + let add_access_token_result = access_token::add_access_token( + state, + &connector, + merchant_account, + &router_data, + creds_identifier.as_ref(), + ) + .await?; logger::debug!(refund_router_data=?router_data); @@ -279,10 +284,7 @@ pub async fn trigger_refund_to_gateway( metrics::SUCCESSFUL_REFUND.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - connector.connector_name.to_string(), - )], + &add_attributes([("connector", connector.connector_name.to_string())]), ) } storage::RefundUpdate::Update { @@ -362,6 +364,7 @@ pub async fn refund_retrieve_core( .find_payment_intent_by_payment_id_merchant_id( payment_id, merchant_id, + &key_store, merchant_account.storage_scheme, ) .await @@ -463,13 +466,19 @@ pub async fn sync_refund_with_gateway( payment_intent, payment_attempt, refund, - creds_identifier, + creds_identifier.clone(), None, ) .await?; - let add_access_token_result = - access_token::add_access_token(state, &connector, merchant_account, &router_data).await?; + let add_access_token_result = access_token::add_access_token( + state, + &connector, + merchant_account, + &router_data, + creds_identifier.as_ref(), + ) + .await?; logger::debug!(refund_retrieve_router_data=?router_data); @@ -1028,7 +1037,7 @@ pub async fn sync_refund_with_gateway_workflow( .as_scheduler() .finish_process_with_business_status( refund_tracker.clone(), - "COMPLETED_BY_PT".to_string(), + business_status::COMPLETED_BY_PT, ) .await? } @@ -1120,6 +1129,7 @@ pub async fn trigger_refund_execute_workflow( .find_payment_intent_by_payment_id_merchant_id( &payment_attempt.payment_id, &refund.merchant_id, + &key_store, merchant_account.storage_scheme, ) .await @@ -1193,7 +1203,7 @@ pub async fn trigger_refund_execute_workflow( db.as_scheduler() .finish_process_with_business_status( refund_tracker.clone(), - "COMPLETED_BY_PT".to_string(), + business_status::COMPLETED_BY_PT, ) .await?; } @@ -1245,11 +1255,7 @@ pub async fn add_refund_sync_task( refund.refund_id ) })?; - metrics::TASKS_ADDED_COUNT.add( - &metrics::CONTEXT, - 1, - &[metrics::request::add_attributes("flow", "Refund")], - ); + metrics::TASKS_ADDED_COUNT.add(&metrics::CONTEXT, 1, &add_attributes([("flow", "Refund")])); Ok(response) } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index c78f2c8080f5..7e79e9e2e890 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -505,10 +505,8 @@ pub async fn reset_password_token_only_flow( let user = state .global_store - .update_user_by_email( - &email_token - .get_email() - .change_context(UserErrors::InternalServerError)?, + .update_user_by_user_id( + user_from_db.get_user_id(), storage_user::UserUpdate::PasswordUpdate { password: hash_password, }, @@ -516,6 +514,17 @@ pub async fn reset_password_token_only_flow( .await .change_context(UserErrors::InternalServerError)?; + if !user_from_db.is_verified() { + let _ = state + .global_store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::VerifyUser, + ) + .await + .map_err(|e| logger::error!(?e)); + } + let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token) .await .map_err(|e| logger::error!(?e)); @@ -1021,6 +1030,17 @@ pub async fn accept_invite_from_email_token_only_flow( .await .change_context(UserErrors::InternalServerError)?; + if !user_from_db.is_verified() { + let _ = state + .global_store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::VerifyUser, + ) + .await + .map_err(|e| logger::error!(?e)); + } + let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token) .await .map_err(|e| logger::error!(?e)); @@ -1476,13 +1496,9 @@ pub async fn verify_email_token_only_flow( .change_context(UserErrors::InternalServerError)? .into(); - if matches!(user_token.origin, domain::Origin::VerifyEmail) - || matches!(user_token.origin, domain::Origin::MagicLink) - { - let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token) - .await - .map_err(|e| logger::error!(?e)); - } + let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token) + .await + .map_err(|e| logger::error!(?e)); let current_flow = domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::VerifyEmail.into())?; diff --git a/crates/router/src/core/user/sample_data.rs b/crates/router/src/core/user/sample_data.rs index bab620620fa6..e1ccb3b03355 100644 --- a/crates/router/src/core/user/sample_data.rs +++ b/crates/router/src/core/user/sample_data.rs @@ -1,12 +1,13 @@ use api_models::user::sample_data::SampleDataRequest; use common_utils::errors::ReportSwitchExt; use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; -use hyperswitch_domain_models::payments::payment_intent::PaymentIntentNew; +use error_stack::ResultExt; +use hyperswitch_domain_models::payments::PaymentIntent; pub type SampleDataApiResponse = SampleDataResult>; use crate::{ - core::errors::sample_data::SampleDataResult, + core::errors::sample_data::{SampleDataError, SampleDataResult}, routes::{app::ReqState, SessionState}, services::{authentication::UserFromToken, ApplicationResponse}, utils::user::sample_data::generate_sample_data, @@ -21,8 +22,18 @@ pub async fn generate_sample_data_for_user( let sample_data = generate_sample_data(&state, req, user_from_token.merchant_id.as_str()).await?; + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + &user_from_token.merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .change_context(SampleDataError::InternalServerError) + .attach_printable("Not able to fetch merchant key store")?; // If not able to fetch merchant key store for any reason, this should be an internal server error + let (payment_intents, payment_attempts, refunds): ( - Vec, + Vec, Vec, Vec, ) = sample_data.into_iter().fold( @@ -39,7 +50,7 @@ pub async fn generate_sample_data_for_user( state .store - .insert_payment_intents_batch_for_sample_data(payment_intents) + .insert_payment_intents_batch_for_sample_data(payment_intents, &key_store) .await .switch()?; state @@ -64,9 +75,19 @@ pub async fn delete_sample_data_for_user( ) -> SampleDataApiResponse<()> { let merchant_id_del = user_from_token.merchant_id.as_str(); + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + &user_from_token.merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .change_context(SampleDataError::InternalServerError) + .attach_printable("Not able to fetch merchant key store")?; // If not able to fetch merchant key store for any reason, this should be an internal server error + state .store - .delete_payment_intents_for_sample_data(merchant_id_del) + .delete_payment_intents_for_sample_data(merchant_id_del, &key_store) .await .switch()?; state diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 2f1a37981ec6..503a67855d9d 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -10,7 +10,7 @@ use api_models::{ use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; use error_stack::{report, ResultExt}; use masking::ExposeInterface; -use router_env::{instrument, tracing, tracing_actix_web::RequestId}; +use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId}; use super::{types, utils, MERCHANT_ID}; use crate::{ @@ -25,9 +25,7 @@ use crate::{ logger, routes::{ app::{ReqState, SessionStateInfo}, - lock_utils, - metrics::request::add_attributes, - SessionState, + lock_utils, SessionState, }, services::{self, authentication as auth}, types::{ @@ -567,10 +565,7 @@ async fn payments_incoming_webhook_flow( metrics::WEBHOOK_PAYMENT_NOT_FOUND.add( &metrics::CONTEXT, 1, - &[add_attributes( - "merchant_id", - merchant_account.merchant_id.clone(), - )], + &add_attributes([("merchant_id", merchant_account.merchant_id.clone())]), ); return Ok(WebhookResponseTracker::NoEffect); } diff --git a/crates/router/src/core/webhooks/outgoing.rs b/crates/router/src/core/webhooks/outgoing.rs index 0be4e9272f06..410d6c3d851c 100644 --- a/crates/router/src/core/webhooks/outgoing.rs +++ b/crates/router/src/core/webhooks/outgoing.rs @@ -3,10 +3,12 @@ use api_models::{ webhooks, }; use common_utils::{ext_traits::Encode, request::RequestContent}; +use diesel_models::process_tracker::business_status; use error_stack::{report, ResultExt}; use masking::{ExposeInterface, Mask, PeekInterface, Secret}; use router_env::{ instrument, + metrics::add_attributes, tracing::{self, Instrument}, }; @@ -21,7 +23,7 @@ use crate::{ db::StorageInterface, events::outgoing_webhook_logs::{OutgoingWebhookEvent, OutgoingWebhookEventMetric}, logger, - routes::{app::SessionStateInfo, metrics::request::add_attributes, SessionState}, + routes::{app::SessionStateInfo, SessionState}, services, types::{ api, @@ -231,7 +233,7 @@ async fn trigger_webhook_to_merchant( state .store .as_scheduler() - .finish_process_with_business_status(process_tracker, "FAILURE".into()) + .finish_process_with_business_status(process_tracker, business_status::FAILURE) .await .change_context( errors::WebhooksFlowError::OutgoingWebhookProcessTrackerTaskUpdateFailed, @@ -304,7 +306,7 @@ async fn trigger_webhook_to_merchant( state.clone(), &business_profile.merchant_id, process_tracker, - "INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL", + business_status::INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL, ) .await?; } else { @@ -500,7 +502,7 @@ pub(crate) async fn add_outgoing_webhook_retry_task_to_process_tracker( crate::routes::metrics::TASKS_ADDED_COUNT.add( &metrics::CONTEXT, 1, - &[add_attributes("flow", "OutgoingWebhookRetry")], + &add_attributes([("flow", "OutgoingWebhookRetry")]), ); Ok(process_tracker) } @@ -508,7 +510,7 @@ pub(crate) async fn add_outgoing_webhook_retry_task_to_process_tracker( crate::routes::metrics::TASK_ADDITION_FAILURES_COUNT.add( &metrics::CONTEXT, 1, - &[add_attributes("flow", "OutgoingWebhookRetry")], + &add_attributes([("flow", "OutgoingWebhookRetry")]), ); Err(error) } @@ -769,7 +771,7 @@ async fn success_response_handler( Some(process_tracker) => state .store .as_scheduler() - .finish_process_with_business_status(process_tracker, business_status.into()) + .finish_process_with_business_status(process_tracker, business_status) .await .change_context( errors::WebhooksFlowError::OutgoingWebhookProcessTrackerTaskUpdateFailed, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 0545191318d0..ca36a53418cb 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1326,11 +1326,12 @@ impl PaymentIntentInterface for KafkaStore { &self, this: storage::PaymentIntent, payment_intent: storage::PaymentIntentUpdate, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult { let intent = self .diesel_store - .update_payment_intent(this.clone(), payment_intent, storage_scheme) + .update_payment_intent(this.clone(), payment_intent, key_store, storage_scheme) .await?; if let Err(er) = self @@ -1346,13 +1347,14 @@ impl PaymentIntentInterface for KafkaStore { async fn insert_payment_intent( &self, - new: storage::PaymentIntentNew, + new: storage::PaymentIntent, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult { logger::debug!("Inserting PaymentIntent Via KafkaStore"); let intent = self .diesel_store - .insert_payment_intent(new, storage_scheme) + .insert_payment_intent(new, key_store, storage_scheme) .await?; if let Err(er) = self @@ -1370,10 +1372,16 @@ impl PaymentIntentInterface for KafkaStore { &self, payment_id: &str, merchant_id: &str, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.diesel_store - .find_payment_intent_by_payment_id_merchant_id(payment_id, merchant_id, storage_scheme) + .find_payment_intent_by_payment_id_merchant_id( + payment_id, + merchant_id, + key_store, + storage_scheme, + ) .await } @@ -1382,10 +1390,11 @@ impl PaymentIntentInterface for KafkaStore { &self, merchant_id: &str, filters: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { self.diesel_store - .filter_payment_intent_by_constraints(merchant_id, filters, storage_scheme) + .filter_payment_intent_by_constraints(merchant_id, filters, key_store, storage_scheme) .await } @@ -1394,12 +1403,14 @@ impl PaymentIntentInterface for KafkaStore { &self, merchant_id: &str, time_range: &api_models::payments::TimeRange, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { self.diesel_store .filter_payment_intents_by_time_range_constraints( merchant_id, time_range, + key_store, storage_scheme, ) .await @@ -1410,6 +1421,7 @@ impl PaymentIntentInterface for KafkaStore { &self, merchant_id: &str, constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + key_store: &domain::MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> CustomResult< Vec<( @@ -1419,7 +1431,12 @@ impl PaymentIntentInterface for KafkaStore { errors::DataStorageError, > { self.diesel_store - .get_filtered_payment_intents_attempt(merchant_id, constraints, storage_scheme) + .get_filtered_payment_intents_attempt( + merchant_id, + constraints, + key_store, + storage_scheme, + ) .await } @@ -1821,7 +1838,7 @@ impl ProcessTrackerInterface for KafkaStore { async fn finish_process_with_business_status( &self, this: storage::ProcessTracker, - business_status: String, + business_status: &'static str, ) -> CustomResult<(), errors::StorageError> { self.diesel_store .finish_process_with_business_status(this, business_status) @@ -2567,14 +2584,15 @@ impl DashboardMetadataInterface for KafkaStore { impl BatchSampleDataInterface for KafkaStore { async fn insert_payment_intents_batch_for_sample_data( &self, - batch: Vec, + batch: Vec, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, ) -> CustomResult< Vec, hyperswitch_domain_models::errors::StorageError, > { let payment_intents_list = self .diesel_store - .insert_payment_intents_batch_for_sample_data(batch) + .insert_payment_intents_batch_for_sample_data(batch, key_store) .await?; for payment_intent in payment_intents_list.iter() { @@ -2629,13 +2647,14 @@ impl BatchSampleDataInterface for KafkaStore { async fn delete_payment_intents_for_sample_data( &self, merchant_id: &str, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, ) -> CustomResult< Vec, hyperswitch_domain_models::errors::StorageError, > { let payment_intents_list = self .diesel_store - .delete_payment_intents_for_sample_data(merchant_id) + .delete_payment_intents_for_sample_data(merchant_id, key_store) .await?; for payment_intent in payment_intents_list.iter() { diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index a176db265495..ab66b52c32d0 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -538,9 +538,6 @@ impl MerchantConnectorAccountInterface for Store { cache::CacheKind::CGraph( format!("cgraph_{}_{_profile_id}", _merchant_id).into(), ), - cache::CacheKind::PmFiltersCGraph( - format!("pm_filters_cgraph_{}_{_profile_id}", _merchant_id).into(), - ), ], update_call, ) @@ -598,9 +595,6 @@ impl MerchantConnectorAccountInterface for Store { cache::CacheKind::CGraph( format!("cgraph_{}_{_profile_id}", mca.merchant_id).into(), ), - cache::CacheKind::PmFiltersCGraph( - format!("pm_filters_cgraph_{}_{_profile_id}", mca.merchant_id).into(), - ), ], delete_call, ) diff --git a/crates/router/src/db/user/sample_data.rs b/crates/router/src/db/user/sample_data.rs index f7926021afd0..5a23666c7b10 100644 --- a/crates/router/src/db/user/sample_data.rs +++ b/crates/router/src/db/user/sample_data.rs @@ -5,9 +5,12 @@ use diesel_models::{ user::sample_data::PaymentAttemptBatchNew, }; use error_stack::{Report, ResultExt}; +use futures::{future::try_join_all, FutureExt}; use hyperswitch_domain_models::{ + behaviour::Conversion, errors::StorageError, - payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntentNew, PaymentIntent}, + merchant_key_store::MerchantKeyStore, + payments::{payment_attempt::PaymentAttempt, PaymentIntent}, }; use storage_impl::DataModelExt; @@ -17,7 +20,8 @@ use crate::{connection::pg_connection_write, core::errors::CustomResult, service pub trait BatchSampleDataInterface { async fn insert_payment_intents_batch_for_sample_data( &self, - batch: Vec, + batch: Vec, + key_store: &MerchantKeyStore, ) -> CustomResult, StorageError>; async fn insert_payment_attempts_batch_for_sample_data( @@ -33,6 +37,7 @@ pub trait BatchSampleDataInterface { async fn delete_payment_intents_for_sample_data( &self, merchant_id: &str, + key_store: &MerchantKeyStore, ) -> CustomResult, StorageError>; async fn delete_payment_attempts_for_sample_data( @@ -50,20 +55,30 @@ pub trait BatchSampleDataInterface { impl BatchSampleDataInterface for Store { async fn insert_payment_intents_batch_for_sample_data( &self, - batch: Vec, + batch: Vec, + key_store: &MerchantKeyStore, ) -> CustomResult, StorageError> { let conn = pg_connection_write(self) .await .change_context(StorageError::DatabaseConnectionError)?; - let new_intents = batch.into_iter().map(|i| i.to_storage_model()).collect(); + let new_intents = try_join_all(batch.into_iter().map(|payment_intent| async { + payment_intent + .construct_new() + .await + .change_context(StorageError::EncryptionError) + })) + .await?; + sample_data_queries::insert_payment_intents(&conn, new_intents) .await .map_err(diesel_error_to_data_error) .map(|v| { - v.into_iter() - .map(PaymentIntent::from_storage_model) - .collect() - }) + try_join_all(v.into_iter().map(|payment_intent| { + PaymentIntent::convert_back(payment_intent, key_store.key.get_inner()) + })) + .map(|join_result| join_result.change_context(StorageError::DecryptionError)) + })? + .await } async fn insert_payment_attempts_batch_for_sample_data( @@ -97,6 +112,7 @@ impl BatchSampleDataInterface for Store { async fn delete_payment_intents_for_sample_data( &self, merchant_id: &str, + key_store: &MerchantKeyStore, ) -> CustomResult, StorageError> { let conn = pg_connection_write(self) .await @@ -105,10 +121,12 @@ impl BatchSampleDataInterface for Store { .await .map_err(diesel_error_to_data_error) .map(|v| { - v.into_iter() - .map(PaymentIntent::from_storage_model) - .collect() - }) + try_join_all(v.into_iter().map(|payment_intent| { + PaymentIntent::convert_back(payment_intent, key_store.key.get_inner()) + })) + .map(|join_result| join_result.change_context(StorageError::DecryptionError)) + })? + .await } async fn delete_payment_attempts_for_sample_data( @@ -144,7 +162,8 @@ impl BatchSampleDataInterface for Store { impl BatchSampleDataInterface for storage_impl::MockDb { async fn insert_payment_intents_batch_for_sample_data( &self, - _batch: Vec, + _batch: Vec, + _key_store: &MerchantKeyStore, ) -> CustomResult, StorageError> { Err(StorageError::MockDbError)? } @@ -166,6 +185,7 @@ impl BatchSampleDataInterface for storage_impl::MockDb { async fn delete_payment_intents_for_sample_data( &self, _merchant_id: &str, + _key_store: &MerchantKeyStore, ) -> CustomResult, StorageError> { Err(StorageError::MockDbError)? } diff --git a/crates/router/src/events/connector_api_logs.rs b/crates/router/src/events/connector_api_logs.rs index af0263568268..7c354620b5a5 100644 --- a/crates/router/src/events/connector_api_logs.rs +++ b/crates/router/src/events/connector_api_logs.rs @@ -1,95 +1,8 @@ -use common_utils::request::Method; -use router_env::tracing_actix_web::RequestId; -use serde::Serialize; -use serde_json::json; -use time::OffsetDateTime; +pub use hyperswitch_interfaces::events::connector_api_logs::ConnectorEvent; use super::EventType; use crate::services::kafka::KafkaMessage; -#[derive(Debug, Serialize)] -pub struct ConnectorEvent { - connector_name: String, - flow: String, - request: String, - masked_response: Option, - error: Option, - url: String, - method: String, - payment_id: String, - merchant_id: String, - created_at: i128, - request_id: String, - latency: u128, - refund_id: Option, - dispute_id: Option, - status_code: u16, -} - -impl ConnectorEvent { - #[allow(clippy::too_many_arguments)] - pub fn new( - connector_name: String, - flow: &str, - request: serde_json::Value, - url: String, - method: Method, - payment_id: String, - merchant_id: String, - request_id: Option<&RequestId>, - latency: u128, - refund_id: Option, - dispute_id: Option, - status_code: u16, - ) -> Self { - Self { - connector_name, - flow: flow - .rsplit_once("::") - .map(|(_, s)| s) - .unwrap_or(flow) - .to_string(), - request: request.to_string(), - masked_response: None, - error: None, - url, - method: method.to_string(), - payment_id, - merchant_id, - created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000, - request_id: request_id - .map(|i| i.as_hyphenated().to_string()) - .unwrap_or("NO_REQUEST_ID".to_string()), - latency, - refund_id, - dispute_id, - status_code, - } - } - - pub fn set_response_body(&mut self, response: &T) { - match masking::masked_serialize(response) { - Ok(masked) => { - self.masked_response = Some(masked.to_string()); - } - Err(er) => self.set_error(json!({"error": er.to_string()})), - } - } - - pub fn set_error_response_body(&mut self, response: &T) { - match masking::masked_serialize(response) { - Ok(masked) => { - self.error = Some(masked.to_string()); - } - Err(er) => self.set_error(json!({"error": er.to_string()})), - } - } - - pub fn set_error(&mut self, error: serde_json::Value) { - self.error = Some(error.to_string()); - } -} - impl KafkaMessage for ConnectorEvent { fn event_type(&self) -> EventType { EventType::ConnectorApiLogs diff --git a/crates/router/src/middleware.rs b/crates/router/src/middleware.rs index e1601f8bbbe1..7b9919d3afaf 100644 --- a/crates/router/src/middleware.rs +++ b/crates/router/src/middleware.rs @@ -1,3 +1,4 @@ +use common_utils::consts::TENANT_HEADER; use futures::StreamExt; use router_env::{ logger, @@ -140,10 +141,17 @@ where // TODO: have a common source of truth for the list of top level fields // /crates/router_env/src/logger/storage.rs also has a list of fields called PERSISTENT_KEYS fn call(&self, req: actix_web::dev::ServiceRequest) -> Self::Future { + let tenant_id = req + .headers() + .get(TENANT_HEADER) + .and_then(|i| i.to_str().ok()) + .map(|s| s.to_owned()); let response_fut = self.service.call(req); - Box::pin( async move { + if let Some(tenant_id) = tenant_id { + router_env::tracing::Span::current().record("tenant_id", &tenant_id); + } let response = response_fut.await; router_env::tracing::Span::current().record("golden_log_line", true); response diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 92ca3c264fe6..2e7a6c4f61e8 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -5,7 +5,6 @@ use actix_web::{web, Scope}; use api_models::routing::RoutingRetrieveQuery; #[cfg(feature = "olap")] use common_enums::TransactionType; -use common_utils::consts::{DEFAULT_TENANT, GLOBAL_TENANT}; #[cfg(feature = "email")] use external_services::email::{ses::AwsSes, EmailService}; use external_services::file_storage::FileStorageInterface; @@ -257,19 +256,11 @@ impl AppState { let cache_store = get_cache_store(&conf.clone(), shut_down_signal, testable) .await .expect("Failed to create store"); - let global_tenant = if conf.multitenancy.enabled { - GLOBAL_TENANT - } else { - DEFAULT_TENANT - }; let global_store: Box = Self::get_store_interface( &storage_impl, &event_handler, &conf, - &settings::GlobalTenant { - schema: global_tenant.to_string(), - redis_key_prefix: String::default(), - }, + &conf.multitenancy.global_tenant, Arc::clone(&cache_store), testable, ) @@ -288,9 +279,7 @@ impl AppState { .get_storage_interface(); stores.insert(tenant_name.clone(), store); #[cfg(feature = "olap")] - let pool = - AnalyticsProvider::from_conf(conf.analytics.get_inner(), tenant_name.as_str()) - .await; + let pool = AnalyticsProvider::from_conf(conf.analytics.get_inner(), tenant).await; #[cfg(feature = "olap")] pools.insert(tenant_name.clone(), pool); } diff --git a/crates/router/src/routes/cards_info.rs b/crates/router/src/routes/cards_info.rs index ca59e072a900..889b6e0ec401 100644 --- a/crates/router/src/routes/cards_info.rs +++ b/crates/router/src/routes/cards_info.rs @@ -46,7 +46,9 @@ pub async fn card_iin_info( state, &req, payload, - |state, auth, req, _| cards_info::retrieve_card_info(state, auth.merchant_account, req), + |state, auth, req, _| { + cards_info::retrieve_card_info(state, auth.merchant_account, auth.key_store, req) + }, &*auth, api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index 87366cf5c6d9..4684edbd18de 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -47,7 +47,7 @@ pub async fn customers_retrieve( let auth = if auth::is_jwt_auth(req.headers()) { Box::new(auth::JWTAuth(Permission::CustomerRead)) } else { - match auth::is_ephemeral_auth(req.headers(), &payload.customer_id) { + match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), } diff --git a/crates/router/src/routes/health.rs b/crates/router/src/routes/health.rs index 721dbd6a78dd..3e2f42bcc25b 100644 --- a/crates/router/src/routes/health.rs +++ b/crates/router/src/routes/health.rs @@ -16,6 +16,7 @@ use crate::{ pub async fn health() -> impl actix_web::Responder { metrics::HEALTH_METRIC.add(&metrics::CONTEXT, 1, &[]); logger::info!("Health was called"); + actix_web::HttpResponse::Ok().body("health is good") } diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 18014c6e1939..0d55a4b45c1f 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -2,7 +2,7 @@ pub mod bg_metrics_collector; pub mod request; pub mod utils; -use router_env::{counter_metric, gauge_metric, global_meter, histogram_metric, metrics_context}; +use router_env::{counter_metric, global_meter, histogram_metric, metrics_context}; metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); @@ -76,7 +76,6 @@ counter_metric!(REDIRECTION_TRIGGERED, GLOBAL_METER); // Connector Level Metric counter_metric!(REQUEST_BUILD_FAILURE, GLOBAL_METER); -counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER); // Connector http status code metrics counter_metric!(CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT, GLOBAL_METER); counter_metric!(CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT, GLOBAL_METER); @@ -92,10 +91,6 @@ histogram_metric!(CARD_ADD_TIME, GLOBAL_METER); histogram_metric!(CARD_GET_TIME, GLOBAL_METER); histogram_metric!(CARD_DELETE_TIME, GLOBAL_METER); -// Encryption and Decryption metrics -histogram_metric!(ENCRYPTION_TIME, GLOBAL_METER); -histogram_metric!(DECRYPTION_TIME, GLOBAL_METER); - // Apple Pay Flow Metrics counter_metric!(APPLE_PAY_MANUAL_FLOW, GLOBAL_METER); counter_metric!(APPLE_PAY_SIMPLIFIED_FLOW, GLOBAL_METER); @@ -136,6 +131,3 @@ counter_metric!(ACCESS_TOKEN_CACHE_HIT, GLOBAL_METER); // A counter to indicate the access token cache miss counter_metric!(ACCESS_TOKEN_CACHE_MISS, GLOBAL_METER); - -// Metrics for In-memory cache -gauge_metric!(CACHE_ENTRY_COUNT, GLOBAL_METER); diff --git a/crates/router/src/routes/metrics/bg_metrics_collector.rs b/crates/router/src/routes/metrics/bg_metrics_collector.rs index 65cb7a13e5ef..681b523db137 100644 --- a/crates/router/src/routes/metrics/bg_metrics_collector.rs +++ b/crates/router/src/routes/metrics/bg_metrics_collector.rs @@ -2,40 +2,24 @@ use storage_impl::redis::cache; const DEFAULT_BG_METRICS_COLLECTION_INTERVAL_IN_SECS: u16 = 15; -macro_rules! gauge_metrics_for_imc { - ($($cache:ident),*) => { - $( - { - cache::$cache.run_pending_tasks().await; - - super::CACHE_ENTRY_COUNT.observe( - &super::CONTEXT, - cache::$cache.get_entry_count(), - &[super::request::add_attributes( - "cache_type", - stringify!($cache), - )], - ); - } - )* - }; -} - pub fn spawn_metrics_collector(metrics_collection_interval_in_secs: &Option) { let metrics_collection_interval = metrics_collection_interval_in_secs .unwrap_or(DEFAULT_BG_METRICS_COLLECTION_INTERVAL_IN_SECS); + let cache_instances = [ + &cache::CONFIG_CACHE, + &cache::ACCOUNTS_CACHE, + &cache::ROUTING_CACHE, + &cache::CGRAPH_CACHE, + &cache::DECISION_MANAGER_CACHE, + &cache::SURCHARGE_CACHE, + ]; + tokio::spawn(async move { loop { - gauge_metrics_for_imc!( - CONFIG_CACHE, - ACCOUNTS_CACHE, - ROUTING_CACHE, - CGRAPH_CACHE, - PM_FILTERS_CGRAPH_CACHE, - DECISION_MANAGER_CACHE, - SURCHARGE_CACHE - ); + for instance in cache_instances { + instance.record_entry_count_metric().await + } tokio::time::sleep(std::time::Duration::from_secs( metrics_collection_interval.into(), diff --git a/crates/router/src/routes/metrics/request.rs b/crates/router/src/routes/metrics/request.rs index ef53ad83f2cb..41f8c8d8071f 100644 --- a/crates/router/src/routes/metrics/request.rs +++ b/crates/router/src/routes/metrics/request.rs @@ -1,4 +1,4 @@ -use router_env::opentelemetry; +use router_env::metrics::add_attributes; use super::utils as metric_utils; use crate::services::ApplicationResponse; @@ -11,46 +11,29 @@ where F: futures::Future, { let key = "request_type"; - super::REQUESTS_RECEIVED.add(&super::CONTEXT, 1, &[add_attributes(key, flow.to_string())]); + super::REQUESTS_RECEIVED.add( + &super::CONTEXT, + 1, + &add_attributes([(key, flow.to_string())]), + ); let (result, time) = metric_utils::time_future(future).await; super::REQUEST_TIME.record( &super::CONTEXT, time.as_secs_f64(), - &[add_attributes(key, flow.to_string())], + &add_attributes([(key, flow.to_string())]), ); result } -#[inline] -pub async fn record_operation_time( - future: F, - metric: &once_cell::sync::Lazy>, - key_value: &[opentelemetry::KeyValue], -) -> R -where - F: futures::Future, -{ - let (result, time) = metric_utils::time_future(future).await; - metric.record(&super::CONTEXT, time.as_secs_f64(), key_value); - result -} - -pub fn add_attributes>( - key: &'static str, - value: T, -) -> opentelemetry::KeyValue { - opentelemetry::KeyValue::new(key, value) -} - -pub fn status_code_metrics(status_code: i64, flow: String, merchant_id: String) { +pub fn status_code_metrics(status_code: String, flow: String, merchant_id: String) { super::REQUEST_STATUS.add( &super::CONTEXT, 1, - &[ - add_attributes("status_code", status_code), - add_attributes("flow", flow), - add_attributes("merchant_id", merchant_id), - ], + &add_attributes([ + ("status_code", status_code), + ("flow", flow), + ("merchant_id", merchant_id), + ]), ) } diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index f375a18ab614..fed182f03a81 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -71,6 +71,7 @@ pub async fn initiate_payment_link( initiate_payment_link_flow( state, auth.merchant_account, + auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), ) @@ -144,6 +145,7 @@ pub async fn payment_link_status( get_payment_link_status( state, auth.merchant_account, + auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), ) diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index e36a8fc3ea51..09b5c6257117 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -139,7 +139,7 @@ pub async fn list_customer_payment_method_api( let payload = query_payload.into_inner(); let customer_id = customer_id.into_inner().0; - let ephemeral_auth = match auth::is_ephemeral_auth(req.headers(), &customer_id) { + let ephemeral_auth = match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), }; @@ -155,6 +155,7 @@ pub async fn list_customer_payment_method_api( auth.key_store, Some(req), Some(&customer_id), + None, ) }, &*ephemeral_auth, @@ -194,10 +195,12 @@ pub async fn list_customer_payment_method_api_client( ) -> HttpResponse { let flow = Flow::CustomerPaymentMethodsList; let payload = query_payload.into_inner(); - let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { - Ok((auth, _auth_flow)) => (auth, _auth_flow), - Err(e) => return api::log_and_return_error_response(e), - }; + let api_key = auth::get_api_key(req.headers()).ok(); + let (auth, _, is_ephemeral_auth) = + match auth::get_ephemeral_or_other_auth(req.headers(), false, Some(&payload)).await { + Ok((auth, _auth_flow, is_ephemeral_auth)) => (auth, _auth_flow, is_ephemeral_auth), + Err(e) => return api::log_and_return_error_response(e), + }; Box::pin(api::server_wrap( flow, @@ -211,6 +214,7 @@ pub async fn list_customer_payment_method_api_client( auth.key_store, Some(req), None, + is_ephemeral_auth.then_some(api_key).flatten(), ) }, &*auth, @@ -291,6 +295,11 @@ pub async fn payment_method_delete_api( let pm = PaymentMethodId { payment_method_id: payment_method_id.into_inner().0, }; + let ephemeral_auth = match auth::is_ephemeral_auth(req.headers()) { + Ok(auth) => auth, + Err(err) => return api::log_and_return_error_response(err), + }; + Box::pin(api::server_wrap( flow, state, @@ -299,7 +308,7 @@ pub async fn payment_method_delete_api( |state, auth, req, _| { cards::delete_payment_method(state, auth.merchant_account, req, auth.key_store) }, - &auth::ApiKeyAuth, + &*ephemeral_auth, api_locking::LockAction::NotApplicable, )) .await @@ -345,7 +354,7 @@ pub async fn default_payment_method_set_api( let pc = payload.clone(); let customer_id = &pc.customer_id; - let ephemeral_auth = match auth::is_ephemeral_auth(req.headers(), customer_id) { + let ephemeral_auth = match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), }; diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index a89410a2b0f9..e6e4d2386774 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -957,7 +957,9 @@ pub async fn payments_list( state, &req, payload, - |state, auth, req, _| payments::list_payments(state, auth.merchant_account, req), + |state, auth, req, _| { + payments::list_payments(state, auth.merchant_account, auth.key_store, req) + }, auth::auth_type( &auth::ApiKeyAuth, &auth::JWTAuth(Permission::PaymentRead), @@ -982,7 +984,7 @@ pub async fn payments_list_by_filter( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payments::apply_filters_on_payments(state, auth.merchant_account, req) + payments::apply_filters_on_payments(state, auth.merchant_account, auth.key_store, req) }, &auth::JWTAuth(Permission::PaymentRead), api_locking::LockAction::NotApplicable, @@ -1004,7 +1006,7 @@ pub async fn get_filters_for_payments( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payments::get_filters_for_payments(state, auth.merchant_account, req) + payments::get_filters_for_payments(state, auth.merchant_account, auth.key_store, req) }, &auth::JWTAuth(Permission::PaymentRead), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index baa824ced3f8..dcb1b5c13081 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -19,14 +19,17 @@ use api_models::enums::{CaptureMethod, PaymentMethodType}; pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use common_utils::{ - consts::X_HS_LATENCY, + consts::{DEFAULT_TENANT, TENANT_HEADER, X_HS_LATENCY}, errors::{ErrorSwitch, ReportSwitchExt}, request::RequestContent, }; use error_stack::{report, Report, ResultExt}; pub use hyperswitch_domain_models::router_response_types::RedirectForm; +pub use hyperswitch_interfaces::api::{ + BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorIntegrationAny, +}; use masking::{Maskable, PeekInterface}; -use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag}; +use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId, Tag}; use serde::Serialize; use serde_json::json; use tera::{Context, Tera}; @@ -34,7 +37,7 @@ use tera::{Context, Tera}; use self::request::{HeaderExt, RequestBuilderExt}; use super::authentication::AuthenticateAndFetch; use crate::{ - configs::{settings::Connectors, Settings}, + configs::Settings, consts, core::{ api_locking, @@ -48,8 +51,7 @@ use crate::{ logger, routes::{ app::{AppStateInfo, ReqState, SessionStateInfo}, - metrics::{self, request as metrics_request}, - AppState, SessionState, + metrics, AppState, SessionState, }, types::{ self, @@ -58,22 +60,6 @@ use crate::{ }, }; -pub type BoxedConnectorIntegration<'a, T, Req, Resp> = - Box<&'a (dyn ConnectorIntegration + Send + Sync)>; - -pub trait ConnectorIntegrationAny: Send + Sync + 'static { - fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>; -} - -impl ConnectorIntegrationAny for S -where - S: ConnectorIntegration + Send + Sync, -{ - fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> { - Box::new(self) - } -} - pub trait ConnectorValidation: ConnectorCommon { fn validate_capture_method( &self, @@ -129,145 +115,6 @@ pub trait ConnectorValidation: ConnectorCommon { } } -#[async_trait::async_trait] -pub trait ConnectorIntegration: ConnectorIntegrationAny + Sync { - fn get_headers( - &self, - _req: &types::RouterData, - _connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - Ok(vec![]) - } - - fn get_content_type(&self) -> &'static str { - mime::APPLICATION_JSON.essence_str() - } - - /// primarily used when creating signature based on request method of payment flow - fn get_http_method(&self) -> Method { - Method::Post - } - - fn get_url( - &self, - _req: &types::RouterData, - _connectors: &Connectors, - ) -> CustomResult { - Ok(String::new()) - } - - fn get_request_body( - &self, - _req: &types::RouterData, - _connectors: &Connectors, - ) -> CustomResult { - Ok(RequestContent::Json(Box::new(json!(r#"{}"#)))) - } - - fn get_request_form_data( - &self, - _req: &types::RouterData, - ) -> CustomResult, errors::ConnectorError> { - Ok(None) - } - - fn build_request( - &self, - req: &types::RouterData, - _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - metrics::UNIMPLEMENTED_FLOW.add( - &metrics::CONTEXT, - 1, - &[metrics::request::add_attributes( - "connector", - req.connector.clone(), - )], - ); - Ok(None) - } - - fn handle_response( - &self, - data: &types::RouterData, - event_builder: Option<&mut ConnectorEvent>, - _res: types::Response, - ) -> CustomResult, errors::ConnectorError> - where - T: Clone, - Req: Clone, - Resp: Clone, - { - event_builder.map(|e| e.set_error(json!({"error": "Not Implemented"}))); - Ok(data.clone()) - } - - fn get_error_response( - &self, - res: types::Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); - Ok(ErrorResponse::get_not_implemented()) - } - - fn get_5xx_error_response( - &self, - res: types::Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); - let error_message = match res.status_code { - 500 => "internal_server_error", - 501 => "not_implemented", - 502 => "bad_gateway", - 503 => "service_unavailable", - 504 => "gateway_timeout", - 505 => "http_version_not_supported", - 506 => "variant_also_negotiates", - 507 => "insufficient_storage", - 508 => "loop_detected", - 510 => "not_extended", - 511 => "network_authentication_required", - _ => "unknown_error", - }; - Ok(ErrorResponse { - code: res.status_code.to_string(), - message: error_message.to_string(), - reason: String::from_utf8(res.response.to_vec()).ok(), - status_code: res.status_code, - attempt_status: None, - connector_transaction_id: None, - }) - } - - // whenever capture sync is implemented at the connector side, this method should be overridden - fn get_multiple_capture_sync_method( - &self, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("multiple capture sync".into()).into()) - } - - fn get_certificate( - &self, - _req: &types::RouterData, - ) -> CustomResult, errors::ConnectorError> { - Ok(None) - } - - fn get_certificate_key( - &self, - _req: &types::RouterData, - ) -> CustomResult, errors::ConnectorError> { - Ok(None) - } -} - -pub enum CaptureSyncMethod { - Individual, - Bulk, -} - /// Handle the flow by interacting with connector module /// `connector_request` is applicable only in case if the `CallConnectorAction` is `Trigger` /// In other cases, It will be created if required, even if it is not passed @@ -330,9 +177,9 @@ where metrics::CONNECTOR_CALL_COUNT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes("connector", req.connector.to_string()), - metrics::request::add_attributes( + &add_attributes([ + ("connector", req.connector.to_string()), + ( "flow", std::any::type_name::() .split("::") @@ -340,7 +187,7 @@ where .unwrap_or_default() .to_string(), ), - ], + ]), ); let connector_request = match connector_request { @@ -356,10 +203,7 @@ where metrics::REQUEST_BUILD_FAILURE.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - req.connector.to_string(), - )], + &add_attributes([("connector", req.connector.to_string())]), ) } error @@ -424,10 +268,10 @@ where metrics::RESPONSE_DESERIALIZATION_FAILURE.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( + &add_attributes([( "connector", req.connector.to_string(), - )], + )]), ) } error @@ -465,10 +309,7 @@ where metrics::CONNECTOR_ERROR_RESPONSE_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes( - "connector", - req.connector.clone(), - )], + &add_attributes([("connector", req.connector.clone())]), ); let error = match body.status_code { @@ -490,6 +331,7 @@ where if let Some(status) = error_res.attempt_status { router_data.status = status; }; + state.event_handler().log_event(&connector_event); error_res } }; @@ -694,9 +536,10 @@ pub async fn send_request( .attach_printable("Unable to send request to connector") }; - let response = metrics_request::record_operation_time( + let response = common_utils::metrics::utils::record_operation_time( send_request, &metrics::EXTERNAL_REQUEST_TIME, + &metrics::CONTEXT, &[metrics_tag.clone()], ) .await; @@ -721,9 +564,10 @@ pub async fn send_request( logger::info!( "Retrying request due to connection closed before message could complete" ); - metrics_request::record_operation_time( + common_utils::metrics::utils::record_operation_time( cloned_request, &metrics::EXTERNAL_REQUEST_TIME, + &metrics::CONTEXT, &[metrics_tag], ) .await @@ -941,10 +785,10 @@ where .into_iter() .collect(); let tenant_id = if !state.conf.multitenancy.enabled { - common_utils::consts::DEFAULT_TENANT.to_string() + DEFAULT_TENANT.to_string() } else { incoming_request_header - .get("x-tenant-id") + .get(TENANT_HEADER) .and_then(|value| value.to_str().ok()) .ok_or_else(|| errors::ApiErrorResponse::MissingTenantId.switch()) .map(|req_tenant_id| { @@ -1067,7 +911,11 @@ where ); state.event_handler().log_event(&api_event); - metrics::request::status_code_metrics(status_code, flow.to_string(), merchant_id.to_string()); + metrics::request::status_code_metrics( + status_code.to_string(), + flow.to_string(), + merchant_id.to_string(), + ); output } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 72d0860eed9c..e2224da29ea1 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -5,7 +5,7 @@ use api_models::{ }; use async_trait::async_trait; use common_enums::TokenPurpose; -use common_utils::{date_time, id_type}; +use common_utils::date_time; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use masking::PeekInterface; @@ -440,7 +440,7 @@ where } #[derive(Debug)] -pub struct EphemeralKeyAuth(pub id_type::CustomerId); +pub struct EphemeralKeyAuth; #[async_trait] impl AuthenticateAndFetch for EphemeralKeyAuth @@ -460,9 +460,6 @@ where .await .change_context(errors::ApiErrorResponse::Unauthorized)?; - if ephemeral_key.customer_id.ne(&self.0) { - return Err(report!(errors::ApiErrorResponse::InvalidEphemeralKey)); - } MerchantIdAuth(ephemeral_key.merchant_id) .authenticate_and_fetch(request_headers, state) .await @@ -1046,16 +1043,43 @@ where Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant)) } +pub async fn get_ephemeral_or_other_auth( + headers: &HeaderMap, + is_merchant_flow: bool, + payload: Option<&impl ClientSecretFetch>, +) -> RouterResult<( + Box>, + api::AuthFlow, + bool, +)> +where + T: SessionStateInfo, + ApiKeyAuth: AuthenticateAndFetch, + PublishableKeyAuth: AuthenticateAndFetch, + EphemeralKeyAuth: AuthenticateAndFetch, +{ + let api_key = get_api_key(headers)?; + + if api_key.starts_with("epk") { + Ok((Box::new(EphemeralKeyAuth), api::AuthFlow::Client, true)) + } else if is_merchant_flow { + Ok((Box::new(ApiKeyAuth), api::AuthFlow::Merchant, false)) + } else { + let payload = payload.get_required_value("ClientSecretFetch")?; + let (auth, auth_flow) = check_client_secret_and_get_auth(headers, payload)?; + Ok((auth, auth_flow, false)) + } +} + pub fn is_ephemeral_auth( headers: &HeaderMap, - customer_id: &id_type::CustomerId, ) -> RouterResult>> { let api_key = get_api_key(headers)?; if !api_key.starts_with("epk") { Ok(Box::new(ApiKeyAuth)) } else { - Ok(Box::new(EphemeralKeyAuth(customer_id.to_owned()))) + Ok(Box::new(EphemeralKeyAuth)) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index d864eabd0934..38e0bed699f9 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -52,6 +52,7 @@ pub use hyperswitch_domain_models::{ VerifyWebhookSourceResponseData, VerifyWebhookStatus, }, }; +pub use hyperswitch_interfaces::types::Response; pub use crate::core::payments::CustomerDetails; #[cfg(feature = "payouts")] @@ -684,13 +685,6 @@ pub struct ConnectorsList { pub connectors: Vec, } -#[derive(Clone, Debug)] -pub struct Response { - pub headers: Option, - pub response: bytes::Bytes, - pub status_code: u16, -} - impl ForeignTryFrom for AccessTokenRequestData { type Error = errors::ApiErrorResponse; fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result { diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index a40fb22e0b9b..f482e90f0525 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -331,6 +331,7 @@ impl ConnectorData { enums::Connector::Coinbase => Ok(Box::new(&connector::Coinbase)), enums::Connector::Cryptopay => Ok(Box::new(connector::Cryptopay::new())), enums::Connector::Cybersource => Ok(Box::new(&connector::Cybersource)), + // enums::Connector::Datatrans => Ok(Box::new(&connector::Datatrans)), added as template code for future use enums::Connector::Dlocal => Ok(Box::new(&connector::Dlocal)), #[cfg(feature = "dummy_connector")] enums::Connector::DummyConnector1 => Ok(Box::new(&connector::DummyConnector::<1>)), @@ -358,7 +359,7 @@ impl ConnectorData { enums::Connector::Mifinity => Ok(Box::new(&connector::Mifinity)), enums::Connector::Mollie => Ok(Box::new(&connector::Mollie)), enums::Connector::Nmi => Ok(Box::new(connector::Nmi::new())), - enums::Connector::Noon => Ok(Box::new(&connector::Noon)), + enums::Connector::Noon => Ok(Box::new(connector::Noon::new())), enums::Connector::Nuvei => Ok(Box::new(&connector::Nuvei)), enums::Connector::Opennode => Ok(Box::new(&connector::Opennode)), // "payeezy" => Ok(Box::new(&connector::Payeezy)), As psync and rsync are not supported by this connector, it is added as template code for future usage diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs index d18ae0d0190c..be93f30bfbed 100644 --- a/crates/router/src/types/domain.rs +++ b/crates/router/src/types/domain.rs @@ -1,10 +1,15 @@ mod address; -pub mod behaviour; +pub mod behaviour { + pub use hyperswitch_domain_models::behaviour::{Conversion, ReverseConversion}; +} + mod customer; mod event; mod merchant_account; mod merchant_connector_account; -mod merchant_key_store; +mod merchant_key_store { + pub use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore; +} pub mod payments; pub mod types; #[cfg(feature = "olap")] diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs index 0ef4e8579d36..176b88b3f5bc 100644 --- a/crates/router/src/types/domain/types.rs +++ b/crates/router/src/types/domain/types.rs @@ -1,225 +1,3 @@ -use async_trait::async_trait; -use common_utils::{ - crypto, - errors::{self, CustomResult}, - ext_traits::AsyncExt, +pub use hyperswitch_domain_models::type_encryption::{ + decrypt, encrypt, encrypt_optional, AsyncLift, Lift, TypeEncryption, }; -use diesel_models::encryption::Encryption; -use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; -use router_env::{instrument, tracing}; - -use crate::routes::metrics::{request, DECRYPTION_TIME, ENCRYPTION_TIME}; - -#[async_trait] -pub trait TypeEncryption< - T, - V: crypto::EncodeMessage + crypto::DecodeMessage, - S: masking::Strategy, ->: Sized -{ - async fn encrypt( - masked_data: Secret, - key: &[u8], - crypt_algo: V, - ) -> CustomResult; - - async fn decrypt( - encrypted_data: Encryption, - key: &[u8], - crypt_algo: V, - ) -> CustomResult; -} - -#[async_trait] -impl< - V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, - S: masking::Strategy + Send, - > TypeEncryption for crypto::Encryptable> -{ - #[instrument(skip_all)] - async fn encrypt( - masked_data: Secret, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let encrypted_data = crypt_algo.encode_message(key, masked_data.peek().as_bytes())?; - - Ok(Self::new(masked_data, encrypted_data.into())) - } - - #[instrument(skip_all)] - async fn decrypt( - encrypted_data: Encryption, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let encrypted = encrypted_data.into_inner(); - let data = crypt_algo.decode_message(key, encrypted.clone())?; - - let value: String = std::str::from_utf8(&data) - .change_context(errors::CryptoError::DecodingFailed)? - .to_string(); - - Ok(Self::new(value.into(), encrypted)) - } -} - -#[async_trait] -impl< - V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, - S: masking::Strategy + Send, - > TypeEncryption - for crypto::Encryptable> -{ - #[instrument(skip_all)] - async fn encrypt( - masked_data: Secret, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let data = serde_json::to_vec(&masked_data.peek()) - .change_context(errors::CryptoError::DecodingFailed)?; - let encrypted_data = crypt_algo.encode_message(key, &data)?; - - Ok(Self::new(masked_data, encrypted_data.into())) - } - - #[instrument(skip_all)] - async fn decrypt( - encrypted_data: Encryption, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let encrypted = encrypted_data.into_inner(); - let data = crypt_algo.decode_message(key, encrypted.clone())?; - - let value: serde_json::Value = - serde_json::from_slice(&data).change_context(errors::CryptoError::DecodingFailed)?; - - Ok(Self::new(value.into(), encrypted)) - } -} - -#[async_trait] -impl< - V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, - S: masking::Strategy> + Send, - > TypeEncryption, V, S> for crypto::Encryptable, S>> -{ - #[instrument(skip_all)] - async fn encrypt( - masked_data: Secret, S>, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let encrypted_data = crypt_algo.encode_message(key, masked_data.peek())?; - - Ok(Self::new(masked_data, encrypted_data.into())) - } - - #[instrument(skip_all)] - async fn decrypt( - encrypted_data: Encryption, - key: &[u8], - crypt_algo: V, - ) -> CustomResult { - let encrypted = encrypted_data.into_inner(); - let data = crypt_algo.decode_message(key, encrypted.clone())?; - - Ok(Self::new(data.into(), encrypted)) - } -} - -pub trait Lift { - type SelfWrapper; - type OtherWrapper; - - fn lift(self, func: Func) -> Self::OtherWrapper - where - Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper; -} - -impl Lift for Option { - type SelfWrapper = Option; - type OtherWrapper = CustomResult, E>; - - fn lift(self, func: Func) -> Self::OtherWrapper - where - Func: Fn(Self::SelfWrapper) -> Self::OtherWrapper, - { - func(self) - } -} - -#[async_trait] -pub trait AsyncLift { - type SelfWrapper; - type OtherWrapper; - - async fn async_lift(self, func: Func) -> Self::OtherWrapper - where - Func: Fn(Self::SelfWrapper) -> F + Send + Sync, - F: futures::Future> + Send; -} - -#[async_trait] -impl + Lift = V> + Send> AsyncLift for V { - type SelfWrapper = >::SelfWrapper; - type OtherWrapper = >::OtherWrapper; - - async fn async_lift(self, func: Func) -> Self::OtherWrapper - where - Func: Fn(Self::SelfWrapper) -> F + Send + Sync, - F: futures::Future> + Send, - { - func(self).await - } -} - -#[inline] -pub async fn encrypt( - inner: Secret, - key: &[u8], -) -> CustomResult>, errors::CryptoError> -where - S: masking::Strategy, - crypto::Encryptable>: TypeEncryption, -{ - request::record_operation_time( - crypto::Encryptable::encrypt(inner, key, crypto::GcmAes256), - &ENCRYPTION_TIME, - &[], - ) - .await -} - -#[inline] -pub async fn encrypt_optional( - inner: Option>, - key: &[u8], -) -> CustomResult>>, errors::CryptoError> -where - Secret: Send, - S: masking::Strategy, - crypto::Encryptable>: TypeEncryption, -{ - inner.async_map(|f| encrypt(f, key)).await.transpose() -} - -#[inline] -pub async fn decrypt>( - inner: Option, - key: &[u8], -) -> CustomResult>>, errors::CryptoError> -where - crypto::Encryptable>: TypeEncryption, -{ - request::record_operation_time( - inner.async_map(|item| crypto::Encryptable::decrypt(item, key, crypto::GcmAes256)), - &DECRYPTION_TIME, - &[], - ) - .await - .transpose() -} diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 47f26ce04e53..5ff79b7feeee 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -837,6 +837,10 @@ impl UserFromStorage { Ok(Some(days_left_for_verification.whole_days())) } + pub fn is_verified(&self) -> bool { + self.0.is_verified + } + pub fn is_password_rotate_required(&self, state: &SessionState) -> UserResult { let last_password_modified_at = if let Some(last_password_modified_at) = self.0.last_password_modified_at { diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index 41ae12350fb3..2ee9b389788d 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -42,7 +42,7 @@ impl SPTFlow { Self::TOTP => Ok(true), // Main email APIs Self::AcceptInvitationFromEmail | Self::ResetPassword => Ok(true), - Self::VerifyEmail => Ok(!user.0.is_verified), + Self::VerifyEmail => Ok(true), // Final Checks Self::ForceSetPassword => user.is_password_rotate_required(state), Self::MerchantSelect => user @@ -154,17 +154,15 @@ const VERIFY_EMAIL_FLOW: [UserFlow; 5] = [ UserFlow::JWTFlow(JWTFlow::UserInfo), ]; -const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 5] = [ +const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 4] = [ UserFlow::SPTFlow(SPTFlow::TOTP), - UserFlow::SPTFlow(SPTFlow::VerifyEmail), UserFlow::SPTFlow(SPTFlow::AcceptInvitationFromEmail), UserFlow::SPTFlow(SPTFlow::ForceSetPassword), UserFlow::JWTFlow(JWTFlow::UserInfo), ]; -const RESET_PASSWORD_FLOW: [UserFlow; 3] = [ +const RESET_PASSWORD_FLOW: [UserFlow; 2] = [ UserFlow::SPTFlow(SPTFlow::TOTP), - UserFlow::SPTFlow(SPTFlow::VerifyEmail), UserFlow::SPTFlow(SPTFlow::ResetPassword), ]; diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index 79c8b2ed2f02..f5626c267d2a 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -40,7 +40,8 @@ pub mod user_role; use std::collections::HashMap; pub use diesel_models::{ - ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner, ProcessTrackerUpdate, + process_tracker::business_status, ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner, + ProcessTrackerUpdate, }; pub use hyperswitch_domain_models::payments::{ payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 94d84d8ccae0..404297e4aad6 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -224,6 +224,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Coinbase => Self::Coinbase, api_enums::Connector::Cryptopay => Self::Cryptopay, api_enums::Connector::Cybersource => Self::Cybersource, + // api_enums::Connector::Datatrans => Self::Datatrans, added as template code for future use api_enums::Connector::Dlocal => Self::Dlocal, api_enums::Connector::Ebanx => Self::Ebanx, api_enums::Connector::Fiserv => Self::Fiserv, diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 701eda5d1c05..9662220d43f7 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -29,6 +29,7 @@ use image::Luma; use masking::ExposeInterface; use nanoid::nanoid; use qrcode; +use router_env::metrics::add_attributes; use serde::de::DeserializeOwned; use serde_json::Value; use tracing_futures::Instrument; @@ -196,12 +197,14 @@ pub async fn find_payment_intent_from_payment_id_type( db: &dyn StorageInterface, payment_id_type: payments::PaymentIdType, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, ) -> CustomResult { match payment_id_type { payments::PaymentIdType::PaymentIntentId(payment_id) => db .find_payment_intent_by_payment_id_merchant_id( &payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -218,6 +221,7 @@ pub async fn find_payment_intent_from_payment_id_type( db.find_payment_intent_by_payment_id_merchant_id( &attempt.payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -235,6 +239,7 @@ pub async fn find_payment_intent_from_payment_id_type( db.find_payment_intent_by_payment_id_merchant_id( &attempt.payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -250,6 +255,7 @@ pub async fn find_payment_intent_from_refund_id_type( db: &dyn StorageInterface, refund_id_type: webhooks::RefundIdType, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, connector_name: &str, ) -> CustomResult { let refund = match refund_id_type { @@ -282,6 +288,7 @@ pub async fn find_payment_intent_from_refund_id_type( db.find_payment_intent_by_payment_id_merchant_id( &attempt.payment_id, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -292,6 +299,7 @@ pub async fn find_payment_intent_from_mandate_id_type( db: &dyn StorageInterface, mandate_id_type: webhooks::MandateIdType, merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, ) -> CustomResult { let mandate = match mandate_id_type { webhooks::MandateIdType::MandateId(mandate_id) => db @@ -317,6 +325,7 @@ pub async fn find_payment_intent_from_mandate_id_type( .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("original_payment_id not present in mandate record")?, &merchant_account.merchant_id, + key_store, merchant_account.storage_scheme, ) .await @@ -492,8 +501,13 @@ pub async fn get_mca_from_object_reference_id( get_mca_from_payment_intent( db, merchant_account, - find_payment_intent_from_payment_id_type(db, payment_id_type, merchant_account) - .await?, + find_payment_intent_from_payment_id_type( + db, + payment_id_type, + merchant_account, + key_store, + ) + .await?, key_store, connector_name, ) @@ -507,6 +521,7 @@ pub async fn get_mca_from_object_reference_id( db, refund_id_type, merchant_account, + key_store, connector_name, ) .await?, @@ -519,8 +534,13 @@ pub async fn get_mca_from_object_reference_id( get_mca_from_payment_intent( db, merchant_account, - find_payment_intent_from_mandate_id_type(db, mandate_id_type, merchant_account) - .await?, + find_payment_intent_from_mandate_id_type( + db, + mandate_id_type, + merchant_account, + key_store, + ) + .await?, key_store, connector_name, ) @@ -553,12 +573,12 @@ pub async fn get_mca_from_object_reference_id( // validate json format for the error pub fn handle_json_response_deserialization_failure( res: types::Response, - connector: String, + connector: &'static str, ) -> CustomResult { metrics::RESPONSE_DESERIALIZATION_FAILURE.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("connector", connector)], + &add_attributes([("connector", connector)]), ); let response_data = String::from_utf8(res.response.to_vec()) @@ -781,24 +801,24 @@ pub fn add_apple_pay_flow_metrics( domain::ApplePayFlow::Simplified(_) => metrics::APPLE_PAY_SIMPLIFIED_FLOW.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ), domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ), } } @@ -817,26 +837,26 @@ pub fn add_apple_pay_payment_status_metrics( metrics::APPLE_PAY_SIMPLIFIED_FLOW_SUCCESSFUL_PAYMENT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ) } domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_SUCCESSFUL_PAYMENT .add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ), } } @@ -847,25 +867,25 @@ pub fn add_apple_pay_payment_status_metrics( metrics::APPLE_PAY_SIMPLIFIED_FLOW_FAILED_PAYMENT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ) } domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_FAILED_PAYMENT.add( &metrics::CONTEXT, 1, - &[ - metrics::request::add_attributes( + &add_attributes([ + ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - metrics::request::add_attributes("merchant_id", merchant_id.to_owned()), - ], + ("merchant_id", merchant_id.to_owned()), + ]), ), } } diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 9be2ddb57a4e..a2909e5f3c76 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -5,7 +5,7 @@ use api_models::{ use common_utils::{id_type, types::MinorUnit}; use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; use error_stack::ResultExt; -use hyperswitch_domain_models::payments::payment_intent::PaymentIntentNew; +use hyperswitch_domain_models::payments::PaymentIntent; use rand::{prelude::SliceRandom, thread_rng, Rng}; use time::OffsetDateTime; @@ -20,7 +20,7 @@ pub async fn generate_sample_data( state: &SessionState, req: SampleDataRequest, merchant_id: &str, -) -> SampleDataResult)>> { +) -> SampleDataResult)>> { let merchant_id = merchant_id.to_string(); let sample_data_size: usize = req.record.unwrap_or(100); @@ -101,7 +101,7 @@ pub async fn generate_sample_data( let mut rng = thread_rng(); random_array.shuffle(&mut rng); - let mut res: Vec<(PaymentIntentNew, PaymentAttemptBatchNew, Option)> = Vec::new(); + let mut res: Vec<(PaymentIntent, PaymentAttemptBatchNew, Option)> = Vec::new(); let start_time = req .start_time .unwrap_or(common_utils::date_time::now() - time::Duration::days(7)) @@ -172,7 +172,7 @@ pub async fn generate_sample_data( let is_failed_payment = (random_array.get(num - 1).unwrap_or(&0) % failure_after_attempts) == 0; - let payment_intent = PaymentIntentNew { + let payment_intent = PaymentIntent { payment_id: payment_id.clone(), merchant_id: merchant_id.clone(), status: match is_failed_payment { @@ -186,8 +186,8 @@ pub async fn generate_sample_data( .unwrap_or(&common_enums::Currency::USD), ), description: Some("This is a sample payment".to_string()), - created_at: Some(created_at), - modified_at: Some(modified_at), + created_at, + modified_at, last_synced: Some(last_synced), client_secret: Some(client_secret), business_country: business_country_default, diff --git a/crates/router/src/workflows/api_key_expiry.rs b/crates/router/src/workflows/api_key_expiry.rs index dff7905a2624..8d8f1aa6c857 100644 --- a/crates/router/src/workflows/api_key_expiry.rs +++ b/crates/router/src/workflows/api_key_expiry.rs @@ -1,6 +1,8 @@ use common_utils::{errors::ValidationError, ext_traits::ValueExt}; -use diesel_models::{enums as storage_enums, ApiKeyExpiryTrackingData}; -use router_env::logger; +use diesel_models::{ + enums as storage_enums, process_tracker::business_status, ApiKeyExpiryTrackingData, +}; +use router_env::{logger, metrics::add_attributes}; use scheduler::{workflows::ProcessTrackerWorkflow, SchedulerSessionState}; use crate::{ @@ -96,7 +98,7 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { state .get_db() .as_scheduler() - .finish_process_with_business_status(process, "COMPLETED_BY_PT".to_string()) + .finish_process_with_business_status(process, business_status::COMPLETED_BY_PT) .await? } // If tasks are remaining that has to be scheduled @@ -128,7 +130,7 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { metrics::TASKS_RESET_COUNT.add( &metrics::CONTEXT, 1, - &[metrics::request::add_attributes("flow", "ApiKeyExpiry")], + &add_attributes([("flow", "ApiKeyExpiry")]), ); } diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs index 9184a78b6b28..7560da210d1d 100644 --- a/crates/router/src/workflows/outgoing_webhook_retry.rs +++ b/crates/router/src/workflows/outgoing_webhook_retry.rs @@ -6,6 +6,7 @@ use api_models::{ webhooks::{OutgoingWebhook, OutgoingWebhookContent}, }; use common_utils::ext_traits::{StringExt, ValueExt}; +use diesel_models::process_tracker::business_status; use error_stack::ResultExt; use masking::PeekInterface; use router_env::tracing::{self, instrument}; @@ -197,7 +198,7 @@ impl ProcessTrackerWorkflow for OutgoingWebhookRetryWorkflow { db.as_scheduler() .finish_process_with_business_status( process.clone(), - "RESOURCE_STATUS_MISMATCH".to_string(), + business_status::RESOURCE_STATUS_MISMATCH, ) .await?; } @@ -309,7 +310,7 @@ pub(crate) async fn retry_webhook_delivery_task( } None => { db.as_scheduler() - .finish_process_with_business_status(process, "RETRIES_EXCEEDED".to_string()) + .finish_process_with_business_status(process, business_status::RETRIES_EXCEEDED) .await } } diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index c34b8ebe73ba..5e3223ec6777 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -1,4 +1,5 @@ use common_utils::ext_traits::{OptionExt, StringExt, ValueExt}; +use diesel_models::process_tracker::business_status; use error_stack::ResultExt; use router_env::logger; use scheduler::{ @@ -89,7 +90,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { state .store .as_scheduler() - .finish_process_with_business_status(process, "COMPLETED_BY_PT".to_string()) + .finish_process_with_business_status(process, business_status::COMPLETED_BY_PT) .await? } _ => { @@ -148,6 +149,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { .update_payment_intent( payment_data.payment_intent, payment_intent_update, + &key_store, merchant_account.storage_scheme, ) .await @@ -269,7 +271,7 @@ pub async fn retry_sync_task( } None => { db.as_scheduler() - .finish_process_with_business_status(pt, "RETRIES_EXCEEDED".to_string()) + .finish_process_with_business_status(pt, business_status::RETRIES_EXCEEDED) .await?; Ok(true) } diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index ff703dd84ef7..9ec7e1bd38c8 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -83,7 +83,6 @@ impl AdyenTest { use common_utils::pii::Email; Some(PaymentInfo { - country: Some(api_models::enums::CountryAlpha2::NL), currency: Some(enums::Currency::EUR), address: Some(PaymentAddress::new( None, diff --git a/crates/router/tests/connectors/datatrans.rs b/crates/router/tests/connectors/datatrans.rs new file mode 100644 index 000000000000..cc87b29d5bd1 --- /dev/null +++ b/crates/router/tests/connectors/datatrans.rs @@ -0,0 +1,420 @@ +use masking::Secret; +use router::types::{self, api, domain, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct DatatransTest; +impl ConnectorActions for DatatransTest {} +impl utils::Connector for DatatransTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Adyen; + api::ConnectorData { + connector: Box::new(&Adyen), + connector_name: types::Connector::Adyen, + get_token: api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .datatrans + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "datatrans".to_string() + } +} + +static CONNECTOR: DatatransTest = DatatransTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 892e1370cccd..ad19a54bbdad 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -24,6 +24,7 @@ mod checkout; mod coinbase; mod cryptopay; mod cybersource; +mod datatrans; mod dlocal; #[cfg(feature = "dummy_connector")] mod dummyconnector; diff --git a/crates/router/tests/connectors/noon.rs b/crates/router/tests/connectors/noon.rs index 47e76d5ec762..b9c2ace307ae 100644 --- a/crates/router/tests/connectors/noon.rs +++ b/crates/router/tests/connectors/noon.rs @@ -15,7 +15,7 @@ impl utils::Connector for NoonTest { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Noon; types::api::ConnectorData { - connector: Box::new(&Noon), + connector: Box::new(Noon::new()), connector_name: types::Connector::Noon, get_token: types::api::GetToken::Connector, merchant_connector_id: None, diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index 6554ddbefbfb..045d356066d1 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -68,7 +68,6 @@ fn get_default_payment_info() -> Option { return_url: None, connector_customer: None, payment_method_token: None, - country: None, currency: None, #[cfg(feature = "payouts")] payout_method_data: None, diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 2d7b1974996e..655e7c665ae0 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -233,3 +233,6 @@ api_key="API Key" [adyenplatform] api_key="API Key" + +[datatrans] +api_key="API Key" diff --git a/crates/router/tests/connectors/square.rs b/crates/router/tests/connectors/square.rs index 2dcd93cc1055..db7dd933e8c3 100644 --- a/crates/router/tests/connectors/square.rs +++ b/crates/router/tests/connectors/square.rs @@ -48,7 +48,6 @@ fn get_default_payment_info(payment_method_token: Option) -> Option, pub currency: Option, - pub country: Option, } impl PaymentInfo { diff --git a/crates/router/tests/connectors/wise.rs b/crates/router/tests/connectors/wise.rs index 161fd27f03da..47b799324ace 100644 --- a/crates/router/tests/connectors/wise.rs +++ b/crates/router/tests/connectors/wise.rs @@ -52,7 +52,6 @@ impl utils::Connector for WiseTest { impl WiseTest { fn get_payout_info() -> Option { Some(PaymentInfo { - country: Some(api_models::enums::CountryAlpha2::NL), currency: Some(enums::Currency::GBP), address: Some(PaymentAddress::new( None, diff --git a/crates/router_derive/src/macros/misc.rs b/crates/router_derive/src/macros/misc.rs index 4717fe730d96..04c37e759507 100644 --- a/crates/router_derive/src/macros/misc.rs +++ b/crates/router_derive/src/macros/misc.rs @@ -52,6 +52,7 @@ pub fn validate_config(input: syn::DeriveInput) -> Result Result<(), ApplicationError> { #(#function_expansions)* Ok(()) diff --git a/crates/router_env/src/logger/formatter.rs b/crates/router_env/src/logger/formatter.rs index 472b917e746b..30a80aa15df7 100644 --- a/crates/router_env/src/logger/formatter.rs +++ b/crates/router_env/src/logger/formatter.rs @@ -58,7 +58,6 @@ const SESSION_ID: &str = "session_id"; pub static IMPLICIT_KEYS: Lazy> = Lazy::new(|| { let mut set = rustc_hash::FxHashSet::default(); - set.insert(MESSAGE); set.insert(HOSTNAME); set.insert(PID); set.insert(ENV); @@ -81,6 +80,7 @@ pub static IMPLICIT_KEYS: Lazy> = Lazy::new(|| { pub static EXTRA_IMPLICIT_KEYS: Lazy> = Lazy::new(|| { let mut set = rustc_hash::FxHashSet::default(); + set.insert(MESSAGE); set.insert(FLOW); set.insert(MERCHANT_AUTH); set.insert(MERCHANT_ID); @@ -163,7 +163,7 @@ where pub fn new_with_implicit_entries( service: &str, dst_writer: W, - default_fields: HashMap, + mut default_fields: HashMap, formatter: F, ) -> Self { let pid = std::process::id(); @@ -174,6 +174,18 @@ where #[cfg(feature = "vergen")] let build = crate::build!().to_string(); let env = crate::env::which().to_string(); + default_fields.retain(|key, value| { + if !IMPLICIT_KEYS.contains(key.as_str()) { + true + } else { + tracing::warn!( + ?key, + ?value, + "Attempting to log a reserved entry. It won't be added to the logs" + ); + false + } + }); Self { dst_writer, @@ -196,9 +208,8 @@ where map_serializer: &mut impl SerializeMap, metadata: &Metadata<'_>, span: Option<&SpanRef<'_, S>>, - storage: Option<&Storage<'_>>, + storage: &Storage<'_>, name: &str, - message: &str, ) -> Result<(), std::io::Error> where S: Subscriber + for<'a> LookupSpan<'a>, @@ -206,7 +217,6 @@ where let is_extra = |s: &str| !IMPLICIT_KEYS.contains(s); let is_extra_implicit = |s: &str| is_extra(s) && EXTRA_IMPLICIT_KEYS.contains(s); - map_serializer.serialize_entry(MESSAGE, &message)?; map_serializer.serialize_entry(HOSTNAME, &self.hostname)?; map_serializer.serialize_entry(PID, &self.pid)?; map_serializer.serialize_entry(ENV, &self.env)?; @@ -228,30 +238,30 @@ where // Write down implicit default entries. for (key, value) in self.default_fields.iter() { - if !IMPLICIT_KEYS.contains(key.as_str()) { - map_serializer.serialize_entry(key, value)?; - } else { - tracing::warn!("{} is a reserved field. Skipping it.", key); - } + map_serializer.serialize_entry(key, value)?; } #[cfg(feature = "log_custom_entries_to_extra")] let mut extra = serde_json::Map::default(); let mut explicit_entries_set: HashSet<&str> = HashSet::default(); // Write down explicit event's entries. - if let Some(storage) = storage { - for (key, value) in storage.values.iter() { - if is_extra_implicit(key) { - #[cfg(feature = "log_extra_implicit_fields")] - map_serializer.serialize_entry(key, value)?; - explicit_entries_set.insert(key); - } else if is_extra(key) { - #[cfg(feature = "log_custom_entries_to_extra")] - extra.insert(key.to_string(), value.clone()); - #[cfg(not(feature = "log_custom_entries_to_extra"))] - map_serializer.serialize_entry(key, value)?; - explicit_entries_set.insert(key); - } + for (key, value) in storage.values.iter() { + if is_extra_implicit(key) { + #[cfg(feature = "log_extra_implicit_fields")] + map_serializer.serialize_entry(key, value)?; + explicit_entries_set.insert(key); + } else if is_extra(key) { + #[cfg(feature = "log_custom_entries_to_extra")] + extra.insert(key.to_string(), value.clone()); + #[cfg(not(feature = "log_custom_entries_to_extra"))] + map_serializer.serialize_entry(key, value)?; + explicit_entries_set.insert(key); + } else { + tracing::warn!( + ?key, + ?value, + "Attempting to log a reserved entry. It won't be added to the logs" + ); } } @@ -269,7 +279,11 @@ where #[cfg(not(feature = "log_custom_entries_to_extra"))] map_serializer.serialize_entry(key, value)?; } else { - tracing::debug!("{} is a reserved entry. Skipping it.", key); + tracing::warn!( + ?key, + ?value, + "Attempting to log a reserved entry. It won't be added to the logs" + ); } } } @@ -305,14 +319,15 @@ where serde_json::Serializer::with_formatter(&mut buffer, self.formatter.clone()); let mut map_serializer = serializer.serialize_map(None)?; let message = Self::span_message(span, ty); + let mut storage = Storage::default(); + storage.record_value("message", message.into()); self.common_serialize( &mut map_serializer, span.metadata(), Some(span), - None, + &storage, span.name(), - &message, )?; map_serializer.end()?; @@ -337,16 +352,9 @@ where event.record(&mut storage); let name = span.map_or("?", SpanRef::name); - let message = Self::event_message(span, event, &storage); + Self::event_message(span, event, &mut storage); - self.common_serialize( - &mut map_serializer, - event.metadata(), - *span, - Some(&storage), - name, - &message, - )?; + self.common_serialize(&mut map_serializer, event.metadata(), *span, &storage, name)?; map_serializer.end()?; Ok(buffer) @@ -374,32 +382,20 @@ where fn event_message( span: &Option<&SpanRef<'_, S>>, event: &Event<'_>, - storage: &Storage<'_>, - ) -> String - where + storage: &mut Storage<'_>, + ) where S: Subscriber + for<'a> LookupSpan<'a>, { // Get value of kept "message" or "target" if does not exist. - let mut message = storage + let message = storage .values - .get("message") - .and_then(|v| match v { - Value::String(s) => Some(s.as_str()), - _ => None, - }) - .unwrap_or_else(|| event.metadata().target()) - .to_owned(); + .entry("message") + .or_insert_with(|| event.metadata().target().into()); // Prepend the span name to the message if span exists. - if let Some(span) = span { - message = format!( - "{} {}", - Self::span_message(span, RecordType::Event), - message, - ); + if let (Some(span), Value::String(a)) = (span, message) { + *a = format!("{} {}", Self::span_message(span, RecordType::Event), a,); } - - message } } diff --git a/crates/router_env/src/logger/storage.rs b/crates/router_env/src/logger/storage.rs index a134bbed73ed..ce220680bb15 100644 --- a/crates/router_env/src/logger/storage.rs +++ b/crates/router_env/src/logger/storage.rs @@ -28,6 +28,14 @@ impl<'a> Storage<'a> { pub fn new() -> Self { Self::default() } + + pub fn record_value(&mut self, key: &'a str, value: serde_json::Value) { + if super::formatter::IMPLICIT_KEYS.contains(key) { + tracing::warn!(value =? value, "{} is a reserved entry. Skipping it.", key); + } else { + self.values.insert(key, value); + } + } } /// Default constructor. @@ -43,32 +51,27 @@ impl Default for Storage<'_> { impl Visit for Storage<'_> { /// A i64. fn record_i64(&mut self, field: &Field, value: i64) { - self.values - .insert(field.name(), serde_json::Value::from(value)); + self.record_value(field.name(), serde_json::Value::from(value)); } /// A u64. fn record_u64(&mut self, field: &Field, value: u64) { - self.values - .insert(field.name(), serde_json::Value::from(value)); + self.record_value(field.name(), serde_json::Value::from(value)); } /// A 64-bit floating point. fn record_f64(&mut self, field: &Field, value: f64) { - self.values - .insert(field.name(), serde_json::Value::from(value)); + self.record_value(field.name(), serde_json::Value::from(value)); } /// A boolean. fn record_bool(&mut self, field: &Field, value: bool) { - self.values - .insert(field.name(), serde_json::Value::from(value)); + self.record_value(field.name(), serde_json::Value::from(value)); } /// A string. fn record_str(&mut self, field: &Field, value: &str) { - self.values - .insert(field.name(), serde_json::Value::from(value)); + self.record_value(field.name(), serde_json::Value::from(value)); } /// Otherwise. @@ -77,7 +80,7 @@ impl Visit for Storage<'_> { // Skip fields which are already handled name if name.starts_with("log.") => (), name if name.starts_with("r#") => { - self.values.insert( + self.record_value( #[allow(clippy::expect_used)] name.get(2..) .expect("field name must have a minimum of two characters"), @@ -85,8 +88,7 @@ impl Visit for Storage<'_> { ); } name => { - self.values - .insert(name, serde_json::Value::from(format!("{value:?}"))); + self.record_value(name, serde_json::Value::from(format!("{value:?}"))); } }; } @@ -165,7 +167,7 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer span.parent().and_then(|p| { p.extensions_mut() .get_mut::>() - .map(|s| s.values.insert(k, v.to_owned())) + .map(|s| s.record_value(k, v.to_owned())) }); } }) @@ -178,7 +180,7 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer .expect("No visitor in extensions"); if let Ok(elapsed) = serde_json::to_value(elapsed_milliseconds) { - visitor.values.insert("elapsed_milliseconds", elapsed); + visitor.record_value("elapsed_milliseconds", elapsed); } } } diff --git a/crates/router_env/src/metrics.rs b/crates/router_env/src/metrics.rs index e75cacaa3c95..780c010579f7 100644 --- a/crates/router_env/src/metrics.rs +++ b/crates/router_env/src/metrics.rs @@ -120,3 +120,18 @@ macro_rules! gauge_metric { > = once_cell::sync::Lazy::new(|| $meter.u64_observable_gauge($description).init()); }; } + +pub use helpers::add_attributes; + +mod helpers { + pub fn add_attributes(attributes: U) -> Vec + where + T: Into, + U: IntoIterator, + { + attributes + .into_iter() + .map(|(key, value)| opentelemetry::KeyValue::new(key, value)) + .collect::>() + } +} diff --git a/crates/scheduler/src/consumer.rs b/crates/scheduler/src/consumer.rs index 3e15a7cde0fb..b58e8ce60d74 100644 --- a/crates/scheduler/src/consumer.rs +++ b/crates/scheduler/src/consumer.rs @@ -30,7 +30,7 @@ use crate::{ // Valid consumer business statuses pub fn valid_business_statuses() -> Vec<&'static str> { - vec!["Pending"] + vec![storage::business_status::PENDING] } #[instrument(skip_all)] @@ -262,7 +262,7 @@ pub async fn consumer_error_handler( vec![process.id], storage::ProcessTrackerUpdate::StatusUpdate { status: enums::ProcessTrackerStatus::Finish, - business_status: Some("GLOBAL_ERROR".to_string()), + business_status: Some(String::from(storage::business_status::GLOBAL_ERROR)), }, ) .await diff --git a/crates/scheduler/src/consumer/workflows.rs b/crates/scheduler/src/consumer/workflows.rs index bbece87f3094..974f1ec41539 100644 --- a/crates/scheduler/src/consumer/workflows.rs +++ b/crates/scheduler/src/consumer/workflows.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use common_utils::errors::CustomResult; pub use diesel_models::process_tracker as storage; +use diesel_models::process_tracker::business_status; use router_env::logger; use crate::{errors, SchedulerSessionState}; @@ -45,7 +46,10 @@ pub trait ProcessTrackerWorkflows: Send + Sync { let status = app_state .get_db() .as_scheduler() - .finish_process_with_business_status(process, "GLOBAL_FAILURE".to_string()) + .finish_process_with_business_status( + process, + business_status::GLOBAL_FAILURE, + ) .await; if let Err(error) = status { logger::error!(?error, "Failed to update process business status"); diff --git a/crates/scheduler/src/db/process_tracker.rs b/crates/scheduler/src/db/process_tracker.rs index feb201f8b756..c73b53b608c2 100644 --- a/crates/scheduler/src/db/process_tracker.rs +++ b/crates/scheduler/src/db/process_tracker.rs @@ -52,7 +52,7 @@ pub trait ProcessTrackerInterface: Send + Sync + 'static { async fn finish_process_with_business_status( &self, this: storage::ProcessTracker, - business_status: String, + business_status: &'static str, ) -> CustomResult<(), errors::StorageError>; async fn find_processes_by_time_status( @@ -166,13 +166,13 @@ impl ProcessTrackerInterface for Store { async fn finish_process_with_business_status( &self, this: storage::ProcessTracker, - business_status: String, + business_status: &'static str, ) -> CustomResult<(), errors::StorageError> { self.update_process( this, storage::ProcessTrackerUpdate::StatusUpdate { status: storage_enums::ProcessTrackerStatus::Finish, - business_status: Some(business_status), + business_status: Some(String::from(business_status)), }, ) .await @@ -284,7 +284,7 @@ impl ProcessTrackerInterface for MockDb { async fn finish_process_with_business_status( &self, _this: storage::ProcessTracker, - _business_status: String, + _business_status: &'static str, ) -> CustomResult<(), errors::StorageError> { // [#172]: Implement function for `MockDb` Err(errors::StorageError::MockDbError)? diff --git a/crates/storage_impl/src/config.rs b/crates/storage_impl/src/config.rs index e371936a04dd..bb006b6a9e55 100644 --- a/crates/storage_impl/src/config.rs +++ b/crates/storage_impl/src/config.rs @@ -38,6 +38,10 @@ pub trait TenantConfig: Send + Sync { fn get_redis_key_prefix(&self) -> &str; } +pub trait ClickHouseConfig: TenantConfig + Send + Sync { + fn get_clickhouse_database(&self) -> &str; +} + #[derive(Debug, serde::Deserialize, Clone, Copy, Default)] #[serde(rename_all = "PascalCase")] pub enum QueueStrategy { diff --git a/crates/storage_impl/src/metrics.rs b/crates/storage_impl/src/metrics.rs index 2f22d578133e..cb7a6b216e47 100644 --- a/crates/storage_impl/src/metrics.rs +++ b/crates/storage_impl/src/metrics.rs @@ -1,4 +1,4 @@ -use router_env::{counter_metric, global_meter, metrics_context}; +use router_env::{counter_metric, gauge_metric, global_meter, metrics_context}; metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); @@ -11,3 +11,9 @@ counter_metric!(KV_OPERATION_FAILED, GLOBAL_METER); counter_metric!(KV_PUSHED_TO_DRAINER, GLOBAL_METER); counter_metric!(KV_FAILED_TO_PUSH_TO_DRAINER, GLOBAL_METER); counter_metric!(KV_SOFT_KILL_ACTIVE_UPDATE, GLOBAL_METER); + +// Metrics for In-memory cache +gauge_metric!(IN_MEMORY_CACHE_ENTRY_COUNT, GLOBAL_METER); +counter_metric!(IN_MEMORY_CACHE_HIT, GLOBAL_METER); +counter_metric!(IN_MEMORY_CACHE_MISS, GLOBAL_METER); +counter_metric!(IN_MEMORY_CACHE_EVICTION_COUNT, GLOBAL_METER); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 0e8df898f126..547fc9f7d719 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -97,12 +97,9 @@ impl PaymentAttemptInterface for MockDb { storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { let mut payment_attempts = self.payment_attempts.lock().await; - #[allow(clippy::as_conversions)] - let id = payment_attempts.len() as i32; let time = common_utils::date_time::now(); let payment_attempt = payment_attempt.populate_derived_fields(); let payment_attempt = PaymentAttempt { - id, payment_id: payment_attempt.payment_id, merchant_id: payment_attempt.merchant_id, attempt_id: payment_attempt.attempt_id, diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index a12b06b29144..bf3ce0baaab0 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -2,16 +2,17 @@ use common_utils::errors::CustomResult; use diesel_models::enums as storage_enums; use error_stack::ResultExt; use hyperswitch_domain_models::{ + behaviour::Conversion, errors::StorageError, + merchant_key_store::MerchantKeyStore, payments::{ payment_attempt::PaymentAttempt, - payment_intent::{PaymentIntentInterface, PaymentIntentNew, PaymentIntentUpdate}, + payment_intent::{PaymentIntentInterface, PaymentIntentUpdate}, PaymentIntent, }, }; use super::MockDb; -use crate::DataModelExt; #[async_trait::async_trait] impl PaymentIntentInterface for MockDb { @@ -20,6 +21,7 @@ impl PaymentIntentInterface for MockDb { &self, _merchant_id: &str, _filters: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + _key_store: &MerchantKeyStore, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` @@ -30,6 +32,7 @@ impl PaymentIntentInterface for MockDb { &self, _merchant_id: &str, _time_range: &api_models::payments::TimeRange, + _key_store: &MerchantKeyStore, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` @@ -50,6 +53,7 @@ impl PaymentIntentInterface for MockDb { &self, _merchant_id: &str, _constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + _key_store: &MerchantKeyStore, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, StorageError> { // [#172]: Implement function for `MockDb` @@ -59,60 +63,13 @@ impl PaymentIntentInterface for MockDb { #[allow(clippy::panic)] async fn insert_payment_intent( &self, - new: PaymentIntentNew, - storage_scheme: storage_enums::MerchantStorageScheme, + new: PaymentIntent, + _key_store: &MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { let mut payment_intents = self.payment_intents.lock().await; - let time = common_utils::date_time::now(); - let payment_intent = PaymentIntent { - #[allow(clippy::as_conversions)] - id: i32::try_from(payment_intents.len()).change_context(StorageError::MockDbError)?, - payment_id: new.payment_id, - merchant_id: new.merchant_id, - status: new.status, - amount: new.amount, - currency: new.currency, - amount_captured: new.amount_captured, - customer_id: new.customer_id, - description: new.description, - return_url: new.return_url, - metadata: new.metadata, - connector_id: new.connector_id, - shipping_address_id: new.shipping_address_id, - billing_address_id: new.billing_address_id, - statement_descriptor_name: new.statement_descriptor_name, - statement_descriptor_suffix: new.statement_descriptor_suffix, - created_at: new.created_at.unwrap_or(time), - modified_at: new.modified_at.unwrap_or(time), - last_synced: new.last_synced, - setup_future_usage: new.setup_future_usage, - off_session: new.off_session, - client_secret: new.client_secret, - business_country: new.business_country, - business_label: new.business_label, - active_attempt: new.active_attempt, - order_details: new.order_details, - allowed_payment_method_types: new.allowed_payment_method_types, - connector_metadata: new.connector_metadata, - feature_metadata: new.feature_metadata, - attempt_count: new.attempt_count, - profile_id: new.profile_id, - merchant_decision: new.merchant_decision, - payment_link_id: new.payment_link_id, - payment_confirm_source: new.payment_confirm_source, - updated_by: storage_scheme.to_string(), - surcharge_applicable: new.surcharge_applicable, - request_incremental_authorization: new.request_incremental_authorization, - incremental_authorization_allowed: new.incremental_authorization_allowed, - authorization_count: new.authorization_count, - fingerprint_id: new.fingerprint_id, - session_expiry: new.session_expiry, - request_external_three_ds_authentication: new.request_external_three_ds_authentication, - charges: new.charges, - frm_metadata: new.frm_metadata, - }; - payment_intents.push(payment_intent.clone()); - Ok(payment_intent) + payment_intents.push(new.clone()); + Ok(new) } // safety: only used for testing @@ -121,18 +78,29 @@ impl PaymentIntentInterface for MockDb { &self, this: PaymentIntent, update: PaymentIntentUpdate, + key_store: &MerchantKeyStore, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { let mut payment_intents = self.payment_intents.lock().await; let payment_intent = payment_intents .iter_mut() - .find(|item| item.id == this.id) + .find(|item| item.payment_id == this.payment_id && item.merchant_id == this.merchant_id) .unwrap(); - *payment_intent = PaymentIntent::from_storage_model( - update - .to_storage_model() - .apply_changeset(this.to_storage_model()), - ); + + let diesel_payment_intent_update = diesel_models::PaymentIntentUpdate::from(update); + let diesel_payment_intent = payment_intent + .clone() + .convert() + .await + .change_context(StorageError::EncryptionError)?; + + *payment_intent = PaymentIntent::convert_back( + diesel_payment_intent_update.apply_changeset(diesel_payment_intent), + key_store.key.get_inner(), + ) + .await + .change_context(StorageError::DecryptionError)?; + Ok(payment_intent.clone()) } @@ -142,6 +110,7 @@ impl PaymentIntentInterface for MockDb { &self, payment_id: &str, merchant_id: &str, + _key_store: &MerchantKeyStore, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { let payment_intents = self.payment_intents.lock().await; diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 1e60c3e02f22..70cb60f521cc 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -14,6 +14,7 @@ use diesel_models::{ }; use error_stack::ResultExt; use hyperswitch_domain_models::{ + behaviour::Conversion, errors, mandates::{MandateAmountData, MandateDataType, MandateDetails}, payments::{ @@ -192,11 +193,13 @@ impl PaymentAttemptInterface for RouterStore { _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = pg_connection_read(self).await?; - let intents = pi - .iter() - .cloned() - .map(|pi| pi.to_storage_model()) - .collect::>(); + let intents = futures::future::try_join_all(pi.iter().cloned().map(|pi| async { + pi.convert() + .await + .change_context(errors::StorageError::EncryptionError) + })) + .await?; + DieselPaymentAttempt::get_filters_for_payments(&conn, intents.as_slice(), merchant_id) .await .map_err(|er| { @@ -352,7 +355,6 @@ impl PaymentAttemptInterface for KVRouterStore { }; let key_str = key.to_string(); let created_attempt = PaymentAttempt { - id: Default::default(), payment_id: payment_attempt.payment_id.clone(), merchant_id: payment_attempt.merchant_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), @@ -1137,7 +1139,6 @@ impl DataModelExt for PaymentAttempt { fn to_storage_model(self) -> Self::StorageModel { DieselPaymentAttempt { - id: self.id, payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, @@ -1207,7 +1208,6 @@ impl DataModelExt for PaymentAttempt { fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { net_amount: MinorUnit::new(storage_model.get_or_calculate_net_amount()), - id: storage_model.id, payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, attempt_id: storage_model.attempt_id, diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index f40a9c3e2d4d..62854cb261a2 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -4,7 +4,7 @@ use api_models::payments::AmountFilter; use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; #[cfg(feature = "olap")] use common_utils::errors::ReportSwitchExt; -use common_utils::{date_time, ext_traits::Encode}; +use common_utils::ext_traits::{AsyncExt, Encode}; #[cfg(feature = "olap")] use diesel::{associations::HasTable, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_models::{ @@ -12,8 +12,7 @@ use diesel_models::{ kv, payment_attempt::PaymentAttempt as DieselPaymentAttempt, payment_intent::{ - PaymentIntent as DieselPaymentIntent, PaymentIntentNew as DieselPaymentIntentNew, - PaymentIntentUpdate as DieselPaymentIntentUpdate, + PaymentIntent as DieselPaymentIntent, PaymentIntentUpdate as DieselPaymentIntentUpdate, }, }; #[cfg(feature = "olap")] @@ -25,10 +24,12 @@ use error_stack::ResultExt; #[cfg(feature = "olap")] use hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints; use hyperswitch_domain_models::{ + behaviour::Conversion, errors::StorageError, + merchant_key_store::MerchantKeyStore, payments::{ payment_attempt::PaymentAttempt, - payment_intent::{PaymentIntentInterface, PaymentIntentNew, PaymentIntentUpdate}, + payment_intent::{PaymentIntentInterface, PaymentIntentUpdate}, PaymentIntent, }, RemoteStorageObject, @@ -52,12 +53,13 @@ use crate::{ impl PaymentIntentInterface for KVRouterStore { async fn insert_payment_intent( &self, - new: PaymentIntentNew, + payment_intent: PaymentIntent, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let merchant_id = new.merchant_id.clone(); - let payment_id = new.payment_id.clone(); - let field = format!("pi_{}", new.payment_id); + let merchant_id = payment_intent.merchant_id.clone(); + let payment_id = payment_intent.payment_id.clone(); + let field = format!("pi_{}", payment_intent.payment_id); let key = PartitionKey::MerchantIdPaymentId { merchant_id: &merchant_id, payment_id: &payment_id, @@ -67,70 +69,35 @@ impl PaymentIntentInterface for KVRouterStore { match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store - .insert_payment_intent(new, storage_scheme) + .insert_payment_intent(payment_intent, merchant_key_store, storage_scheme) .await } MerchantStorageScheme::RedisKv => { let key_str = key.to_string(); - let created_intent = PaymentIntent { - id: 0i32, - payment_id: new.payment_id.clone(), - merchant_id: new.merchant_id.clone(), - status: new.status, - amount: new.amount, - currency: new.currency, - amount_captured: new.amount_captured, - customer_id: new.customer_id.clone(), - description: new.description.clone(), - return_url: new.return_url.clone(), - metadata: new.metadata.clone(), - frm_metadata: new.frm_metadata.clone(), - connector_id: new.connector_id.clone(), - shipping_address_id: new.shipping_address_id.clone(), - billing_address_id: new.billing_address_id.clone(), - statement_descriptor_name: new.statement_descriptor_name.clone(), - statement_descriptor_suffix: new.statement_descriptor_suffix.clone(), - created_at: new.created_at.unwrap_or_else(date_time::now), - modified_at: new.created_at.unwrap_or_else(date_time::now), - last_synced: new.last_synced, - setup_future_usage: new.setup_future_usage, - off_session: new.off_session, - client_secret: new.client_secret.clone(), - business_country: new.business_country, - business_label: new.business_label.clone(), - active_attempt: new.active_attempt.clone(), - order_details: new.order_details.clone(), - allowed_payment_method_types: new.allowed_payment_method_types.clone(), - connector_metadata: new.connector_metadata.clone(), - feature_metadata: new.feature_metadata.clone(), - attempt_count: new.attempt_count, - profile_id: new.profile_id.clone(), - merchant_decision: new.merchant_decision.clone(), - payment_link_id: new.payment_link_id.clone(), - payment_confirm_source: new.payment_confirm_source, - updated_by: storage_scheme.to_string(), - surcharge_applicable: new.surcharge_applicable, - request_incremental_authorization: new.request_incremental_authorization, - incremental_authorization_allowed: new.incremental_authorization_allowed, - authorization_count: new.authorization_count, - fingerprint_id: new.fingerprint_id.clone(), - session_expiry: new.session_expiry, - request_external_three_ds_authentication: new - .request_external_three_ds_authentication, - charges: new.charges.clone(), - }; + let new_payment_intent = payment_intent + .clone() + .construct_new() + .await + .change_context(StorageError::EncryptionError)?; + let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::PaymentIntent(new.to_storage_model()), + insertable: kv::Insertable::PaymentIntent(new_payment_intent), }, }; + let diesel_payment_intent = payment_intent + .clone() + .convert() + .await + .change_context(StorageError::EncryptionError)?; + match kv_wrapper::( self, KvOperation::::HSetNx( &field, - &created_intent.clone().to_storage_model(), + &diesel_payment_intent, redis_entry, ), key, @@ -144,7 +111,7 @@ impl PaymentIntentInterface for KVRouterStore { key: Some(key_str), } .into()), - Ok(HsetnxReply::KeySet) => Ok(created_intent), + Ok(HsetnxReply::KeySet) => Ok(payment_intent), Err(error) => Err(error.change_context(StorageError::KVError)), } } @@ -156,6 +123,7 @@ impl PaymentIntentInterface for KVRouterStore { &self, this: PaymentIntent, payment_intent_update: PaymentIntentUpdate, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let merchant_id = this.merchant_id.clone(); @@ -174,14 +142,22 @@ impl PaymentIntentInterface for KVRouterStore { match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store - .update_payment_intent(this, payment_intent_update, storage_scheme) + .update_payment_intent( + this, + payment_intent_update, + merchant_key_store, + storage_scheme, + ) .await } MerchantStorageScheme::RedisKv => { let key_str = key.to_string(); - let diesel_intent_update = payment_intent_update.to_storage_model(); - let origin_diesel_intent = this.to_storage_model(); + let diesel_intent_update = DieselPaymentIntentUpdate::from(payment_intent_update); + let origin_diesel_intent = this + .convert() + .await + .change_context(StorageError::EncryptionError)?; let diesel_intent = diesel_intent_update .clone() @@ -213,7 +189,12 @@ impl PaymentIntentInterface for KVRouterStore { .try_into_hset() .change_context(StorageError::KVError)?; - Ok(PaymentIntent::from_storage_model(diesel_intent)) + let payment_intent = + PaymentIntent::convert_back(diesel_intent, merchant_key_store.key.get_inner()) + .await + .change_context(StorageError::DecryptionError)?; + + Ok(payment_intent) } } } @@ -223,6 +204,7 @@ impl PaymentIntentInterface for KVRouterStore { &self, payment_id: &str, merchant_id: &str, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let database_call = || async { @@ -236,7 +218,7 @@ impl PaymentIntentInterface for KVRouterStore { }; let storage_scheme = decide_storage_scheme::<_, DieselPaymentIntent>(self, storage_scheme, Op::Find).await; - match storage_scheme { + let diesel_payment_intent = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -259,8 +241,11 @@ impl PaymentIntentInterface for KVRouterStore { )) .await } - } - .map(PaymentIntent::from_storage_model) + }?; + + PaymentIntent::convert_back(diesel_payment_intent, merchant_key_store.key.get_inner()) + .await + .change_context(StorageError::DecryptionError) } async fn get_active_payment_attempt( @@ -295,10 +280,16 @@ impl PaymentIntentInterface for KVRouterStore { &self, merchant_id: &str, filters: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { self.router_store - .filter_payment_intent_by_constraints(merchant_id, filters, storage_scheme) + .filter_payment_intent_by_constraints( + merchant_id, + filters, + merchant_key_store, + storage_scheme, + ) .await } @@ -307,12 +298,14 @@ impl PaymentIntentInterface for KVRouterStore { &self, merchant_id: &str, time_range: &api_models::payments::TimeRange, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { self.router_store .filter_payment_intents_by_time_range_constraints( merchant_id, time_range, + merchant_key_store, storage_scheme, ) .await @@ -323,10 +316,16 @@ impl PaymentIntentInterface for KVRouterStore { &self, merchant_id: &str, filters: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { self.router_store - .get_filtered_payment_intents_attempt(merchant_id, filters, storage_scheme) + .get_filtered_payment_intents_attempt( + merchant_id, + filters, + merchant_key_store, + storage_scheme, + ) .await } @@ -352,18 +351,25 @@ impl PaymentIntentInterface for crate::RouterStore { #[instrument(skip_all)] async fn insert_payment_intent( &self, - new: PaymentIntentNew, + payment_intent: PaymentIntent, + merchant_key_store: &MerchantKeyStore, _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let conn = pg_connection_write(self).await?; - new.to_storage_model() + let diesel_payment_intent = payment_intent + .construct_new() + .await + .change_context(StorageError::EncryptionError)? .insert(&conn) .await .map_err(|er| { let new_err = diesel_error_to_data_error(er.current_context()); er.change_context(new_err) - }) - .map(PaymentIntent::from_storage_model) + })?; + + PaymentIntent::convert_back(diesel_payment_intent, merchant_key_store.key.get_inner()) + .await + .change_context(StorageError::DecryptionError) } #[instrument(skip_all)] @@ -371,17 +377,26 @@ impl PaymentIntentInterface for crate::RouterStore { &self, this: PaymentIntent, payment_intent: PaymentIntentUpdate, + merchant_key_store: &MerchantKeyStore, _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let conn = pg_connection_write(self).await?; - this.to_storage_model() - .update(&conn, payment_intent.to_storage_model()) + let diesel_payment_intent_update = DieselPaymentIntentUpdate::from(payment_intent); + + let diesel_payment_intent = this + .convert() + .await + .change_context(StorageError::EncryptionError)? + .update(&conn, diesel_payment_intent_update) .await .map_err(|er| { let new_err = diesel_error_to_data_error(er.current_context()); er.change_context(new_err) - }) - .map(PaymentIntent::from_storage_model) + })?; + + PaymentIntent::convert_back(diesel_payment_intent, merchant_key_store.key.get_inner()) + .await + .change_context(StorageError::DecryptionError) } #[instrument(skip_all)] @@ -389,16 +404,26 @@ impl PaymentIntentInterface for crate::RouterStore { &self, payment_id: &str, merchant_id: &str, + merchant_key_store: &MerchantKeyStore, _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let conn = pg_connection_read(self).await?; + DieselPaymentIntent::find_by_payment_id_merchant_id(&conn, payment_id, merchant_id) .await - .map(PaymentIntent::from_storage_model) .map_err(|er| { let new_err = diesel_error_to_data_error(er.current_context()); er.change_context(new_err) }) + .async_and_then(|diesel_payment_intent| async { + PaymentIntent::convert_back( + diesel_payment_intent, + merchant_key_store.key.get_inner(), + ) + .await + .change_context(StorageError::DecryptionError) + }) + .await } #[instrument(skip_all)] @@ -435,9 +460,11 @@ impl PaymentIntentInterface for crate::RouterStore { &self, merchant_id: &str, filters: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { use common_utils::errors::ReportSwitchExt; + use futures::{future::try_join_all, FutureExt}; let conn = connection::pg_connection_read(self).await.switch()?; let conn = async_bb8_diesel::Connection::as_async_conn(&conn); @@ -473,6 +500,7 @@ impl PaymentIntentInterface for crate::RouterStore { .find_payment_intent_by_payment_id_merchant_id( starting_after_id, merchant_id, + merchant_key_store, storage_scheme, ) .await? @@ -490,6 +518,7 @@ impl PaymentIntentInterface for crate::RouterStore { .find_payment_intent_by_payment_id_merchant_id( ending_before_id, merchant_id, + merchant_key_store, storage_scheme, ) .await? @@ -529,18 +558,21 @@ impl PaymentIntentInterface for crate::RouterStore { ) .await .map(|payment_intents| { - payment_intents - .into_iter() - .map(PaymentIntent::from_storage_model) - .collect::>() + try_join_all(payment_intents.into_iter().map(|diesel_payment_intent| { + PaymentIntent::convert_back( + diesel_payment_intent, + merchant_key_store.key.get_inner(), + ) + })) + .map(|join_result| join_result.change_context(StorageError::DecryptionError)) }) .map_err(|er| { StorageError::DatabaseError( error_stack::report!(diesel_models::errors::DatabaseError::from(er)) .attach_printable("Error filtering payment records"), ) - .into() - }) + })? + .await } #[cfg(feature = "olap")] @@ -549,12 +581,18 @@ impl PaymentIntentInterface for crate::RouterStore { &self, merchant_id: &str, time_range: &api_models::payments::TimeRange, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { // TODO: Remove this redundant function let payment_filters = (*time_range).into(); - self.filter_payment_intent_by_constraints(merchant_id, &payment_filters, storage_scheme) - .await + self.filter_payment_intent_by_constraints( + merchant_id, + &payment_filters, + merchant_key_store, + storage_scheme, + ) + .await } #[cfg(feature = "olap")] @@ -563,8 +601,11 @@ impl PaymentIntentInterface for crate::RouterStore { &self, merchant_id: &str, constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, StorageError> { + use futures::{future::try_join_all, FutureExt}; + let conn = connection::pg_connection_read(self).await.switch()?; let conn = async_bb8_diesel::Connection::as_async_conn(&conn); let mut query = DieselPaymentIntent::table() @@ -601,6 +642,7 @@ impl PaymentIntentInterface for crate::RouterStore { .find_payment_intent_by_payment_id_merchant_id( starting_after_id, merchant_id, + merchant_key_store, storage_scheme, ) .await? @@ -618,6 +660,7 @@ impl PaymentIntentInterface for crate::RouterStore { .find_payment_intent_by_payment_id_merchant_id( ending_before_id, merchant_id, + merchant_key_store, storage_scheme, ) .await? @@ -701,23 +744,24 @@ impl PaymentIntentInterface for crate::RouterStore { .get_results_async::<(DieselPaymentIntent, DieselPaymentAttempt)>(conn) .await .map(|results| { - results - .into_iter() - .map(|(pi, pa)| { - ( - PaymentIntent::from_storage_model(pi), - PaymentAttempt::from_storage_model(pa), - ) - }) - .collect() + try_join_all(results.into_iter().map(|(pi, pa)| { + PaymentIntent::convert_back(pi, merchant_key_store.key.get_inner()).map( + |payment_intent| { + payment_intent.map(|payment_intent| { + (payment_intent, PaymentAttempt::from_storage_model(pa)) + }) + }, + ) + })) + .map(|join_result| join_result.change_context(StorageError::DecryptionError)) }) .map_err(|er| { StorageError::DatabaseError( error_stack::report!(diesel_models::errors::DatabaseError::from(er)) .attach_printable("Error filtering payment records"), ) - .into() - }) + })? + .await } #[cfg(feature = "olap")] @@ -802,381 +846,3 @@ impl PaymentIntentInterface for crate::RouterStore { }) } } - -impl DataModelExt for PaymentIntentNew { - type StorageModel = DieselPaymentIntentNew; - - fn to_storage_model(self) -> Self::StorageModel { - DieselPaymentIntentNew { - payment_id: self.payment_id, - merchant_id: self.merchant_id, - status: self.status, - amount: self.amount, - currency: self.currency, - amount_captured: self.amount_captured, - customer_id: self.customer_id, - description: self.description, - return_url: self.return_url, - metadata: self.metadata, - frm_metadata: self.frm_metadata, - connector_id: self.connector_id, - shipping_address_id: self.shipping_address_id, - billing_address_id: self.billing_address_id, - statement_descriptor_name: self.statement_descriptor_name, - statement_descriptor_suffix: self.statement_descriptor_suffix, - created_at: self.created_at, - modified_at: self.modified_at, - last_synced: self.last_synced, - setup_future_usage: self.setup_future_usage, - off_session: self.off_session, - client_secret: self.client_secret, - active_attempt_id: self.active_attempt.get_id(), - business_country: self.business_country, - business_label: self.business_label, - order_details: self.order_details, - allowed_payment_method_types: self.allowed_payment_method_types, - connector_metadata: self.connector_metadata, - feature_metadata: self.feature_metadata, - attempt_count: self.attempt_count, - profile_id: self.profile_id, - merchant_decision: self.merchant_decision, - payment_link_id: self.payment_link_id, - payment_confirm_source: self.payment_confirm_source, - updated_by: self.updated_by, - surcharge_applicable: self.surcharge_applicable, - request_incremental_authorization: self.request_incremental_authorization, - incremental_authorization_allowed: self.incremental_authorization_allowed, - authorization_count: self.authorization_count, - fingerprint_id: self.fingerprint_id, - session_expiry: self.session_expiry, - request_external_three_ds_authentication: self.request_external_three_ds_authentication, - charges: self.charges, - } - } - - fn from_storage_model(storage_model: Self::StorageModel) -> Self { - Self { - payment_id: storage_model.payment_id, - merchant_id: storage_model.merchant_id, - status: storage_model.status, - amount: storage_model.amount, - currency: storage_model.currency, - amount_captured: storage_model.amount_captured, - customer_id: storage_model.customer_id, - description: storage_model.description, - return_url: storage_model.return_url, - metadata: storage_model.metadata, - frm_metadata: storage_model.frm_metadata, - connector_id: storage_model.connector_id, - shipping_address_id: storage_model.shipping_address_id, - billing_address_id: storage_model.billing_address_id, - statement_descriptor_name: storage_model.statement_descriptor_name, - statement_descriptor_suffix: storage_model.statement_descriptor_suffix, - created_at: storage_model.created_at, - modified_at: storage_model.modified_at, - last_synced: storage_model.last_synced, - setup_future_usage: storage_model.setup_future_usage, - off_session: storage_model.off_session, - client_secret: storage_model.client_secret, - active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), - business_country: storage_model.business_country, - business_label: storage_model.business_label, - order_details: storage_model.order_details, - allowed_payment_method_types: storage_model.allowed_payment_method_types, - connector_metadata: storage_model.connector_metadata, - feature_metadata: storage_model.feature_metadata, - attempt_count: storage_model.attempt_count, - profile_id: storage_model.profile_id, - merchant_decision: storage_model.merchant_decision, - payment_link_id: storage_model.payment_link_id, - payment_confirm_source: storage_model.payment_confirm_source, - updated_by: storage_model.updated_by, - surcharge_applicable: storage_model.surcharge_applicable, - request_incremental_authorization: storage_model.request_incremental_authorization, - incremental_authorization_allowed: storage_model.incremental_authorization_allowed, - authorization_count: storage_model.authorization_count, - fingerprint_id: storage_model.fingerprint_id, - session_expiry: storage_model.session_expiry, - request_external_three_ds_authentication: storage_model - .request_external_three_ds_authentication, - charges: storage_model.charges, - } - } -} - -impl DataModelExt for PaymentIntent { - type StorageModel = DieselPaymentIntent; - - fn to_storage_model(self) -> Self::StorageModel { - DieselPaymentIntent { - id: self.id, - payment_id: self.payment_id, - merchant_id: self.merchant_id, - status: self.status, - amount: self.amount, - currency: self.currency, - amount_captured: self.amount_captured, - customer_id: self.customer_id, - description: self.description, - return_url: self.return_url, - metadata: self.metadata, - connector_id: self.connector_id, - shipping_address_id: self.shipping_address_id, - billing_address_id: self.billing_address_id, - statement_descriptor_name: self.statement_descriptor_name, - statement_descriptor_suffix: self.statement_descriptor_suffix, - created_at: self.created_at, - modified_at: self.modified_at, - last_synced: self.last_synced, - setup_future_usage: self.setup_future_usage, - off_session: self.off_session, - client_secret: self.client_secret, - active_attempt_id: self.active_attempt.get_id(), - business_country: self.business_country, - business_label: self.business_label, - order_details: self.order_details, - allowed_payment_method_types: self.allowed_payment_method_types, - connector_metadata: self.connector_metadata, - feature_metadata: self.feature_metadata, - attempt_count: self.attempt_count, - profile_id: self.profile_id, - merchant_decision: self.merchant_decision, - payment_link_id: self.payment_link_id, - payment_confirm_source: self.payment_confirm_source, - updated_by: self.updated_by, - surcharge_applicable: self.surcharge_applicable, - request_incremental_authorization: self.request_incremental_authorization, - incremental_authorization_allowed: self.incremental_authorization_allowed, - authorization_count: self.authorization_count, - fingerprint_id: self.fingerprint_id, - session_expiry: self.session_expiry, - request_external_three_ds_authentication: self.request_external_three_ds_authentication, - charges: self.charges, - frm_metadata: self.frm_metadata, - } - } - - fn from_storage_model(storage_model: Self::StorageModel) -> Self { - Self { - id: storage_model.id, - payment_id: storage_model.payment_id, - merchant_id: storage_model.merchant_id, - status: storage_model.status, - amount: storage_model.amount, - currency: storage_model.currency, - amount_captured: storage_model.amount_captured, - customer_id: storage_model.customer_id, - description: storage_model.description, - return_url: storage_model.return_url, - metadata: storage_model.metadata, - connector_id: storage_model.connector_id, - shipping_address_id: storage_model.shipping_address_id, - billing_address_id: storage_model.billing_address_id, - statement_descriptor_name: storage_model.statement_descriptor_name, - statement_descriptor_suffix: storage_model.statement_descriptor_suffix, - created_at: storage_model.created_at, - modified_at: storage_model.modified_at, - last_synced: storage_model.last_synced, - setup_future_usage: storage_model.setup_future_usage, - off_session: storage_model.off_session, - client_secret: storage_model.client_secret, - active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), - business_country: storage_model.business_country, - business_label: storage_model.business_label, - order_details: storage_model.order_details, - allowed_payment_method_types: storage_model.allowed_payment_method_types, - connector_metadata: storage_model.connector_metadata, - feature_metadata: storage_model.feature_metadata, - attempt_count: storage_model.attempt_count, - profile_id: storage_model.profile_id, - merchant_decision: storage_model.merchant_decision, - payment_link_id: storage_model.payment_link_id, - payment_confirm_source: storage_model.payment_confirm_source, - updated_by: storage_model.updated_by, - surcharge_applicable: storage_model.surcharge_applicable, - request_incremental_authorization: storage_model.request_incremental_authorization, - incremental_authorization_allowed: storage_model.incremental_authorization_allowed, - authorization_count: storage_model.authorization_count, - fingerprint_id: storage_model.fingerprint_id, - session_expiry: storage_model.session_expiry, - request_external_three_ds_authentication: storage_model - .request_external_three_ds_authentication, - charges: storage_model.charges, - frm_metadata: storage_model.frm_metadata, - } - } -} - -impl DataModelExt for PaymentIntentUpdate { - type StorageModel = DieselPaymentIntentUpdate; - - fn to_storage_model(self) -> Self::StorageModel { - match self { - Self::ResponseUpdate { - status, - amount_captured, - fingerprint_id, - return_url, - updated_by, - incremental_authorization_allowed, - } => DieselPaymentIntentUpdate::ResponseUpdate { - status, - amount_captured, - fingerprint_id, - return_url, - updated_by, - incremental_authorization_allowed, - }, - Self::MetadataUpdate { - metadata, - updated_by, - } => DieselPaymentIntentUpdate::MetadataUpdate { - metadata, - updated_by, - }, - Self::ReturnUrlUpdate { - return_url, - status, - customer_id, - shipping_address_id, - billing_address_id, - updated_by, - } => DieselPaymentIntentUpdate::ReturnUrlUpdate { - return_url, - status, - customer_id, - shipping_address_id, - billing_address_id, - updated_by, - }, - Self::MerchantStatusUpdate { - status, - shipping_address_id, - billing_address_id, - updated_by, - } => DieselPaymentIntentUpdate::MerchantStatusUpdate { - status, - shipping_address_id, - billing_address_id, - updated_by, - }, - Self::PGStatusUpdate { - status, - updated_by, - incremental_authorization_allowed, - } => DieselPaymentIntentUpdate::PGStatusUpdate { - status, - updated_by, - incremental_authorization_allowed, - }, - Self::Update { - amount, - currency, - setup_future_usage, - status, - customer_id, - shipping_address_id, - billing_address_id, - return_url, - business_country, - business_label, - description, - statement_descriptor_name, - statement_descriptor_suffix, - order_details, - metadata, - payment_confirm_source, - updated_by, - fingerprint_id, - session_expiry, - request_external_three_ds_authentication, - frm_metadata, - } => DieselPaymentIntentUpdate::Update { - amount, - currency, - setup_future_usage, - status, - customer_id, - shipping_address_id, - billing_address_id, - return_url, - business_country, - business_label, - description, - statement_descriptor_name, - statement_descriptor_suffix, - order_details, - metadata, - payment_confirm_source, - updated_by, - fingerprint_id, - session_expiry, - request_external_three_ds_authentication, - frm_metadata, - }, - Self::PaymentAttemptAndAttemptCountUpdate { - active_attempt_id, - attempt_count, - updated_by, - } => DieselPaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - active_attempt_id, - attempt_count, - updated_by, - }, - Self::StatusAndAttemptUpdate { - status, - active_attempt_id, - attempt_count, - updated_by, - } => DieselPaymentIntentUpdate::StatusAndAttemptUpdate { - status, - active_attempt_id, - attempt_count, - updated_by, - }, - Self::ApproveUpdate { - status, - merchant_decision, - updated_by, - } => DieselPaymentIntentUpdate::ApproveUpdate { - status, - merchant_decision, - updated_by, - }, - Self::RejectUpdate { - status, - merchant_decision, - updated_by, - } => DieselPaymentIntentUpdate::RejectUpdate { - status, - merchant_decision, - updated_by, - }, - Self::SurchargeApplicableUpdate { - surcharge_applicable, - updated_by, - } => DieselPaymentIntentUpdate::SurchargeApplicableUpdate { - surcharge_applicable: Some(surcharge_applicable), - updated_by, - }, - Self::IncrementalAuthorizationAmountUpdate { amount } => { - DieselPaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } - } - Self::AuthorizationCountUpdate { - authorization_count, - } => DieselPaymentIntentUpdate::AuthorizationCountUpdate { - authorization_count, - }, - Self::CompleteAuthorizeUpdate { - shipping_address_id, - } => DieselPaymentIntentUpdate::CompleteAuthorizeUpdate { - shipping_address_id, - }, - } - } - - #[allow(clippy::todo)] - fn from_storage_model(_storage_model: Self::StorageModel) -> Self { - todo!("Reverse map should no longer be needed") - } -} diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 6ad96be27950..46aa3f8aa962 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -9,10 +9,14 @@ use error_stack::{Report, ResultExt}; use moka::future::Cache as MokaCache; use once_cell::sync::Lazy; use redis_interface::{errors::RedisError, RedisConnectionPool, RedisValue}; -use router_env::tracing::{self, instrument}; +use router_env::{ + metrics::add_attributes, + tracing::{self, instrument}, +}; use crate::{ errors::StorageError, + metrics, redis::{PubSubInterface, RedisConnInterface}, }; @@ -37,9 +41,6 @@ const SURCHARGE_CACHE_PREFIX: &str = "surcharge"; /// Prefix for cgraph cache key const CGRAPH_CACHE_PREFIX: &str = "cgraph"; -/// Prefix for PM Filter cgraph cache key -const PM_FILTERS_CGRAPH_CACHE_PREFIX: &str = "pm_filters_cgraph"; - /// Prefix for all kinds of cache key const ALL_CACHE_PREFIX: &str = "all_cache_kind"; @@ -53,31 +54,34 @@ const CACHE_TTI: u64 = 10 * 60; const MAX_CAPACITY: u64 = 30; /// Config Cache with time_to_live as 30 mins and time_to_idle as 10 mins. -pub static CONFIG_CACHE: Lazy = Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, None)); +pub static CONFIG_CACHE: Lazy = + Lazy::new(|| Cache::new("CONFIG_CACHE", CACHE_TTL, CACHE_TTI, None)); /// Accounts cache with time_to_live as 30 mins and size limit pub static ACCOUNTS_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + Lazy::new(|| Cache::new("ACCOUNTS_CACHE", CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); /// Routing Cache pub static ROUTING_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + Lazy::new(|| Cache::new("ROUTING_CACHE", CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); /// 3DS Decision Manager Cache -pub static DECISION_MANAGER_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); +pub static DECISION_MANAGER_CACHE: Lazy = Lazy::new(|| { + Cache::new( + "DECISION_MANAGER_CACHE", + CACHE_TTL, + CACHE_TTI, + Some(MAX_CAPACITY), + ) +}); /// Surcharge Cache pub static SURCHARGE_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + Lazy::new(|| Cache::new("SURCHARGE_CACHE", CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); /// CGraph Cache pub static CGRAPH_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); - -/// PM Filter CGraph Cache -pub static PM_FILTERS_CGRAPH_CACHE: Lazy = - Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + Lazy::new(|| Cache::new("CGRAPH_CACHE", CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); /// Trait which defines the behaviour of types that's gonna be stored in Cache pub trait Cacheable: Any + Send + Sync + DynClone { @@ -91,7 +95,6 @@ pub enum CacheKind<'a> { DecisionManager(Cow<'a, str>), Surcharge(Cow<'a, str>), CGraph(Cow<'a, str>), - PmFiltersCGraph(Cow<'a, str>), All(Cow<'a, str>), } @@ -104,7 +107,6 @@ impl<'a> From> for RedisValue { CacheKind::DecisionManager(s) => format!("{DECISION_MANAGER_CACHE_PREFIX},{s}"), CacheKind::Surcharge(s) => format!("{SURCHARGE_CACHE_PREFIX},{s}"), CacheKind::CGraph(s) => format!("{CGRAPH_CACHE_PREFIX},{s}"), - CacheKind::PmFiltersCGraph(s) => format!("{PM_FILTERS_CGRAPH_CACHE_PREFIX},{s}"), CacheKind::All(s) => format!("{ALL_CACHE_PREFIX},{s}"), }; Self::from_string(value) @@ -128,10 +130,6 @@ impl<'a> TryFrom for CacheKind<'a> { } SURCHARGE_CACHE_PREFIX => Ok(Self::Surcharge(Cow::Owned(split.1.to_string()))), CGRAPH_CACHE_PREFIX => Ok(Self::CGraph(Cow::Owned(split.1.to_string()))), - PM_FILTERS_CGRAPH_CACHE_PREFIX => { - Ok(Self::PmFiltersCGraph(Cow::Owned(split.1.to_string()))) - } - ALL_CACHE_PREFIX => Ok(Self::All(Cow::Owned(split.1.to_string()))), _ => Err(validation_err.into()), } @@ -150,6 +148,7 @@ where dyn_clone::clone_trait_object!(Cacheable); pub struct Cache { + name: &'static str, inner: MokaCache>, } @@ -173,19 +172,38 @@ impl From for String { impl Cache { /// With given `time_to_live` and `time_to_idle` creates a moka cache. /// + /// `name` : Cache type name to be used as an attribute in metrics /// `time_to_live`: Time in seconds before an object is stored in a caching system before it’s deleted /// `time_to_idle`: Time in seconds before a `get` or `insert` operation an object is stored in a caching system before it's deleted /// `max_capacity`: Max size in MB's that the cache can hold - pub fn new(time_to_live: u64, time_to_idle: u64, max_capacity: Option) -> Self { + pub fn new( + name: &'static str, + time_to_live: u64, + time_to_idle: u64, + max_capacity: Option, + ) -> Self { + // Record the metrics of manual invalidation of cache entry by the application + let eviction_listener = move |_, _, cause| { + metrics::IN_MEMORY_CACHE_EVICTION_COUNT.add( + &metrics::CONTEXT, + 1, + &add_attributes([ + ("cache_type", name.to_owned()), + ("removal_cause", format!("{:?}", cause)), + ]), + ); + }; let mut cache_builder = MokaCache::builder() .time_to_live(std::time::Duration::from_secs(time_to_live)) - .time_to_idle(std::time::Duration::from_secs(time_to_idle)); + .time_to_idle(std::time::Duration::from_secs(time_to_idle)) + .eviction_listener(eviction_listener); if let Some(capacity) = max_capacity { cache_builder = cache_builder.max_capacity(capacity * 1024 * 1024); } Self { + name, inner: cache_builder.build(), } } @@ -195,8 +213,26 @@ impl Cache { } pub async fn get_val(&self, key: CacheKey) -> Option { - let val = self.inner.get::(&key.into()).await?; - (*val).as_any().downcast_ref::().cloned() + let val = self.inner.get::(&key.into()).await; + + // Add cache hit and cache miss metrics + if val.is_some() { + metrics::IN_MEMORY_CACHE_HIT.add( + &metrics::CONTEXT, + 1, + &add_attributes([("cache_type", self.name)]), + ); + } else { + metrics::IN_MEMORY_CACHE_MISS.add( + &metrics::CONTEXT, + 1, + &add_attributes([("cache_type", self.name)]), + ); + } + + let val = (*val?).as_any().downcast_ref::().cloned(); + + val } /// Check if a key exists in cache @@ -209,14 +245,28 @@ impl Cache { } /// Performs any pending maintenance operations needed by the cache. - pub async fn run_pending_tasks(&self) { + async fn run_pending_tasks(&self) { self.inner.run_pending_tasks().await; } /// Returns an approximate number of entries in this cache. - pub fn get_entry_count(&self) -> u64 { + fn get_entry_count(&self) -> u64 { self.inner.entry_count() } + + pub fn name(&self) -> &'static str { + self.name + } + + pub async fn record_entry_count_metric(&self) { + self.run_pending_tasks().await; + + metrics::IN_MEMORY_CACHE_ENTRY_COUNT.observe( + &metrics::CONTEXT, + self.get_entry_count(), + &add_attributes([("cache_type", self.name)]), + ); + } } #[instrument(skip_all)] @@ -390,7 +440,7 @@ mod cache_tests { #[tokio::test] async fn construct_and_get_cache() { - let cache = Cache::new(1800, 1800, None); + let cache = Cache::new("test", 1800, 1800, None); cache .push( CacheKey { @@ -413,7 +463,7 @@ mod cache_tests { #[tokio::test] async fn eviction_on_size_test() { - let cache = Cache::new(2, 2, Some(0)); + let cache = Cache::new("test", 2, 2, Some(0)); cache .push( CacheKey { @@ -436,7 +486,7 @@ mod cache_tests { #[tokio::test] async fn invalidate_cache_for_key() { - let cache = Cache::new(1800, 1800, None); + let cache = Cache::new("test", 1800, 1800, None); cache .push( CacheKey { @@ -467,7 +517,7 @@ mod cache_tests { #[tokio::test] async fn eviction_on_time_test() { - let cache = Cache::new(2, 2, None); + let cache = Cache::new("test", 2, 2, None); cache .push( CacheKey { diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 7c4bd93681f4..a50de0a4edd7 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -4,7 +4,7 @@ use router_env::{logger, tracing::Instrument}; use crate::redis::cache::{ CacheKey, CacheKind, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, DECISION_MANAGER_CACHE, - PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, SURCHARGE_CACHE, + ROUTING_CACHE, SURCHARGE_CACHE, }; #[async_trait::async_trait] @@ -100,16 +100,6 @@ impl PubSubInterface for std::sync::Arc { .await; key } - CacheKind::PmFiltersCGraph(key) => { - PM_FILTERS_CGRAPH_CACHE - .remove(CacheKey { - key: key.to_string(), - prefix: self.key_prefix.clone(), - }) - .await; - - key - } CacheKind::Routing(key) => { ROUTING_CACHE .remove(CacheKey { @@ -156,12 +146,6 @@ impl PubSubInterface for std::sync::Arc { prefix: self.key_prefix.clone(), }) .await; - PM_FILTERS_CGRAPH_CACHE - .remove(CacheKey { - key: key.to_string(), - prefix: self.key_prefix.clone(), - }) - .await; ROUTING_CACHE .remove(CacheKey { key: key.to_string(), diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 4b41506fe2d3..aafccbff4380 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -29,6 +29,7 @@ pub struct ConnectorAuthentication { pub coinbase: Option, pub cryptopay: Option, pub cybersource: Option, + pub datatrans: Option, pub dlocal: Option, #[cfg(feature = "dummy_connector")] pub dummyconnector: Option, diff --git a/docker-compose-development.yml b/docker-compose-development.yml index 09750ba84487..868eeba24376 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -25,6 +25,12 @@ services: - POSTGRES_USER=db_user - POSTGRES_PASSWORD=db_pass - POSTGRES_DB=hyperswitch_db + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + retries: 3 + start_period: 5s + timeout: 5s redis-standalone: image: redis:7 @@ -32,6 +38,12 @@ services: - router_net ports: - "6379:6379" + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep '^PONG$'"] + interval: 5s + retries: 3 + start_period: 5s + timeout: 5s migration_runner: image: rust:latest @@ -64,6 +76,11 @@ services: environment: - CARGO_HOME=/cargo_cache - CARGO_TARGET_DIR=/cargo_build_cache + depends_on: + pg: + condition: service_healthy + redis-standalone: + condition: service_healthy labels: logs: "promtail" healthcheck: diff --git a/docker-compose.yml b/docker-compose.yml index c993262e744f..57f7ccde1cd4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,12 @@ services: - POSTGRES_USER=db_user - POSTGRES_PASSWORD=db_pass - POSTGRES_DB=hyperswitch_db + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + retries: 3 + start_period: 5s + timeout: 5s redis-standalone: image: redis:7 @@ -27,6 +33,12 @@ services: - router_net ports: - "6379:6379" + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep '^PONG$'"] + interval: 5s + retries: 3 + start_period: 5s + timeout: 5s migration_runner: image: rust:latest @@ -55,14 +67,19 @@ services: volumes: - ./config:/local/config - ./files:/local/bin/files + depends_on: + pg: + condition: service_healthy + redis-standalone: + condition: service_healthy labels: logs: "promtail" healthcheck: test: curl --fail http://localhost:8080/health || exit 1 - interval: 10s + interval: 5s retries: 3 start_period: 5s - timeout: 10s + timeout: 5s hyperswitch-producer: image: juspaydotin/hyperswitch-producer:standalone @@ -96,7 +113,7 @@ services: - SCHEDULER_FLOW=consumer depends_on: hyperswitch-server: - condition: service_started + condition: service_healthy labels: logs: "promtail" healthcheck: @@ -121,7 +138,7 @@ services: restart: unless-stopped depends_on: hyperswitch-server: - condition: service_started + condition: service_healthy labels: logs: "promtail" @@ -131,21 +148,25 @@ services: - "9050:9050" - "9060:9060" - "5252:5252" + networks: + - router_net build: context: ./docker dockerfile: web.Dockerfile + depends_on: + hyperswitch-server: + condition: service_healthy environment: - - HYPERSWITCH_PUBLISHABLE_KEY=${HYPERSWITCH_PUBLISHABLE_KEY:-PUBLISHABLE_KEY} - - HYPERSWITCH_SECRET_KEY=${HYPERSWITCH_SECRET_KEY:-SECRET_KEY} - - HYPERSWITCH_SERVER_URL=${HYPERSWITCH_SERVER_URL:-http://hyperswitch-server:8080} - - HYPERSWITCH_CLIENT_URL=${HYPERSWITCH_CLIENT_URL:-http://localhost:9050} - - SELF_SERVER_URL=${SELF_SERVER_URL:-http://localhost:5252} - - SDK_ENV=${SDK_ENV:-local} - - ENV_SDK_URL=${ENV_SDK_URL:-http://localhost:9050} - - ENV_BACKEND_URL=${ENV_BACKEND_URL:-http://localhost:8080} - - ENV_LOGGING_URL=${ENV_LOGGING_URL:-http://localhost:3100} + - HYPERSWITCH_PUBLISHABLE_KEY=placeholder_publishable_key + - HYPERSWITCH_SECRET_KEY=placeholder_api_key + - HYPERSWITCH_SERVER_URL=http://localhost:8080 + - HYPERSWITCH_SERVER_URL_FOR_DEMO_APP=http://hyperswitch-server:8080 + - HYPERSWITCH_CLIENT_URL=http://localhost:9050 + - SELF_SERVER_URL=http://localhost:5252 + - SDK_ENV=local + - ENV_LOGGING_URL=http://localhost:3100 labels: - logs: "promtail" + logs: "promtail" ### Control Center hyperswitch-control-center: @@ -158,7 +179,10 @@ services: volumes: - ./config/dashboard.toml:/tmp/dashboard-config.toml depends_on: - - hyperswitch-web + hyperswitch-server: + condition: service_healthy + hyperswitch-web: + condition: service_started labels: logs: "promtail" @@ -354,6 +378,8 @@ services: - "8123:8123" volumes: - ./crates/analytics/docs/clickhouse/scripts:/docker-entrypoint-initdb.d + environment: + - TZ=Asia/Kolkata profiles: - olap ulimits: diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index d008ff3231cb..cfe4a99e1566 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -91,6 +91,7 @@ checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" +datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -168,6 +169,7 @@ cards = [ "coinbase", "cryptopay", "cybersource", + "datatrans", "dlocal", "dummyconnector", "ebanx", @@ -328,6 +330,7 @@ keys = "user-agent" [multitenancy] enabled = false +global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = ""} \ No newline at end of file +public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} \ No newline at end of file diff --git a/migrations/2024-06-03-090859_make_id_as_optional_for_payments/down.sql b/migrations/2024-06-03-090859_make_id_as_optional_for_payments/down.sql new file mode 100644 index 000000000000..9766ed42d019 --- /dev/null +++ b/migrations/2024-06-03-090859_make_id_as_optional_for_payments/down.sql @@ -0,0 +1,18 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt +ALTER COLUMN id +SET NOT NULL; + +ALTER TABLE payment_attempt DROP CONSTRAINT payment_attempt_pkey; + +ALTER TABLE payment_attempt +ADD PRIMARY KEY (id); + +ALTER TABLE payment_intent +ALTER COLUMN id +SET NOT NULL; + +ALTER TABLE payment_intent DROP CONSTRAINT payment_intent_pkey; + +ALTER TABLE payment_intent +ADD PRIMARY KEY (id); diff --git a/migrations/2024-06-03-090859_make_id_as_optional_for_payments/up.sql b/migrations/2024-06-03-090859_make_id_as_optional_for_payments/up.sql new file mode 100644 index 000000000000..3c050d6e9671 --- /dev/null +++ b/migrations/2024-06-03-090859_make_id_as_optional_for_payments/up.sql @@ -0,0 +1,19 @@ +-- First drop the primary key of payment_intent +ALTER TABLE payment_intent DROP CONSTRAINT payment_intent_pkey; + +-- Create new primary key +ALTER TABLE payment_intent +ADD PRIMARY KEY (payment_id, merchant_id); + +-- Make the previous primary key as optional +ALTER TABLE payment_intent +ALTER COLUMN id DROP NOT NULL; + +-- Follow the same steps for payment attempt as well +ALTER TABLE payment_attempt DROP CONSTRAINT payment_attempt_pkey; + +ALTER TABLE payment_attempt +ADD PRIMARY KEY (attempt_id, merchant_id); + +ALTER TABLE payment_attempt +ALTER COLUMN id DROP NOT NULL; diff --git a/migrations/2024-06-03-133421_remove_id_from_payment_table/down.sql b/migrations/2024-06-03-133421_remove_id_from_payment_table/down.sql new file mode 100644 index 000000000000..5b704469b9cc --- /dev/null +++ b/migrations/2024-06-03-133421_remove_id_from_payment_table/down.sql @@ -0,0 +1,6 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent +ADD id SERIAL; + +ALTER TABLE payment_attempt +ADD id SERIAL; diff --git a/migrations/2024-06-03-133421_remove_id_from_payment_table/up.sql b/migrations/2024-06-03-133421_remove_id_from_payment_table/up.sql new file mode 100644 index 000000000000..221ab4e9ae24 --- /dev/null +++ b/migrations/2024-06-03-133421_remove_id_from_payment_table/up.sql @@ -0,0 +1,6 @@ +-- The following queries must be run after the newer version of the application is deployed. +-- Running these queries can even be deferred for some time (a couple of weeks or even a month) until the +-- new version being deployed is considered stable +ALTER TABLE payment_intent DROP COLUMN id; + +ALTER TABLE payment_attempt DROP COLUMN id; diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index f35d080bfe62..f4b4fc875784 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless gpayments helcim iatapay klarna mifinity mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme payone paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1") + connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless gpayments helcim iatapay klarna mifinity mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme payone paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp