diff --git a/CHANGELOG.md b/CHANGELOG.md index 58d5bf792049..6bf415dd8db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.05.23.0 + +### Features + +- **connector:** + - Accept connector_transaction_id in 4xx error_response of connector ([#4720](https://github.com/juspay/hyperswitch/pull/4720)) ([`2ad7fc0`](https://github.com/juspay/hyperswitch/commit/2ad7fc0cd6c102bea4d671c98f7fe50fd709d4ec)) + - [AUTHORIZEDOTNET] Implement zero mandates ([#4704](https://github.com/juspay/hyperswitch/pull/4704)) ([`8afeda5`](https://github.com/juspay/hyperswitch/commit/8afeda54fc5e3f3d510c48c81c222387e9cacc0e)) +- **payment_methods:** Enable auto-retries for apple pay ([#4721](https://github.com/juspay/hyperswitch/pull/4721)) ([`d942a31`](https://github.com/juspay/hyperswitch/commit/d942a31d60595d366977746be7215620da0ababd)) +- **routing:** Use Moka cache for routing with cache invalidation ([#3216](https://github.com/juspay/hyperswitch/pull/3216)) ([`431560b`](https://github.com/juspay/hyperswitch/commit/431560b7fb4401d000c11dbb9c7eb70663591307)) +- **users:** Create generate recovery codes API ([#4708](https://github.com/juspay/hyperswitch/pull/4708)) ([`8fa2cd5`](https://github.com/juspay/hyperswitch/commit/8fa2cd556bf898621a1a8722a0af99d174447485)) +- **webhook:** Add frm webhook support ([#4662](https://github.com/juspay/hyperswitch/pull/4662)) ([`ae601e8`](https://github.com/juspay/hyperswitch/commit/ae601e8e1be9215488daaae7cb39ad5a030e98d9)) + +### Bug Fixes + +- **core:** Fix failing token based MIT payments ([#4735](https://github.com/juspay/hyperswitch/pull/4735)) ([`1bd4061`](https://github.com/juspay/hyperswitch/commit/1bd406197b5baf1c041f0dffa5bc02dce10f1529)) +- Added hget lookup for all updated_by existing cases ([#4716](https://github.com/juspay/hyperswitch/pull/4716)) ([`fabf80c`](https://github.com/juspay/hyperswitch/commit/fabf80c2b18ca690b7fb709c8c12d1ef7f24e5b6)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`ec50843`](https://github.com/juspay/hyperswitch/commit/ec508435a19c2942a5d66757a74dd06bed5b1a76)) + +**Full Changelog:** [`2024.05.22.0...2024.05.23.0`](https://github.com/juspay/hyperswitch/compare/2024.05.22.0...2024.05.23.0) + +- - - + ## 2024.05.22.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index 89a0d9d50792..9411c009aef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3667,18 +3667,25 @@ dependencies = [ name = "hyperswitch_domain_models" version = "0.1.0" dependencies = [ + "actix-web", "api_models", "async-trait", + "cards", "common_enums", "common_utils", "diesel_models", "error-stack", "http 0.2.12", "masking", + "mime", + "router_derive", "serde", "serde_json", + "serde_with", "thiserror", "time", + "url", + "utoipa", ] [[package]] @@ -6051,6 +6058,7 @@ dependencies = [ "error-stack", "external_services", "futures 0.3.30", + "hyperswitch_domain_models", "masking", "num_cpus", "once_cell", diff --git a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql index e5c52ccace82..223d29c98368 100644 --- a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql +++ b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql @@ -38,6 +38,8 @@ CREATE TABLE payment_attempt_queue ( `unified_code` Nullable(String), `unified_message` Nullable(String), `mandate_data` Nullable(String), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8 ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', kafka_topic_list = 'hyperswitch-payment-attempt-events', @@ -86,6 +88,8 @@ CREATE TABLE payment_attempts ( `unified_message` Nullable(String), `mandate_data` Nullable(String), `inserted_at` DateTime DEFAULT now() CODEC(T64, LZ4), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8, INDEX connectorIndex connector TYPE bloom_filter GRANULARITY 1, INDEX paymentMethodIndex payment_method TYPE bloom_filter GRANULARITY 1, @@ -137,6 +141,8 @@ CREATE MATERIALIZED VIEW payment_attempt_mv TO payment_attempts ( `unified_message` Nullable(String), `mandate_data` Nullable(String), `inserted_at` DateTime64(3), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8 ) AS SELECT @@ -180,6 +186,8 @@ SELECT unified_message, mandate_data, now() AS inserted_at, + client_source, + client_version, sign_flag FROM payment_attempt_queue diff --git a/crates/analytics/src/auth_events/accumulator.rs b/crates/analytics/src/auth_events/accumulator.rs index 409e805af8f9..2958030c8da6 100644 --- a/crates/analytics/src/auth_events/accumulator.rs +++ b/crates/analytics/src/auth_events/accumulator.rs @@ -11,6 +11,7 @@ pub struct AuthEventMetricsAccumulator { pub challenge_attempt_count: CountAccumulator, pub challenge_success_count: CountAccumulator, pub frictionless_flow_count: CountAccumulator, + pub frictionless_success_count: CountAccumulator, } #[derive(Debug, Default)] @@ -53,6 +54,7 @@ impl AuthEventMetricsAccumulator { challenge_attempt_count: self.challenge_attempt_count.collect(), challenge_success_count: self.challenge_success_count.collect(), frictionless_flow_count: self.frictionless_flow_count.collect(), + frictionless_success_count: self.frictionless_success_count.collect(), } } } diff --git a/crates/analytics/src/auth_events/core.rs b/crates/analytics/src/auth_events/core.rs index 761a95bb9b3b..5ee34d7a36fd 100644 --- a/crates/analytics/src/auth_events/core.rs +++ b/crates/analytics/src/auth_events/core.rs @@ -78,6 +78,9 @@ pub async fn get_metrics( AuthEventMetrics::FrictionlessFlowCount => metrics_builder .frictionless_flow_count .add_metrics_bucket(&value), + AuthEventMetrics::FrictionlessSuccessCount => metrics_builder + .frictionless_success_count + .add_metrics_bucket(&value), } } } diff --git a/crates/analytics/src/auth_events/metrics.rs b/crates/analytics/src/auth_events/metrics.rs index 683074749273..ae61aefb858b 100644 --- a/crates/analytics/src/auth_events/metrics.rs +++ b/crates/analytics/src/auth_events/metrics.rs @@ -15,6 +15,7 @@ mod challenge_attempt_count; mod challenge_flow_count; mod challenge_success_count; mod frictionless_flow_count; +mod frictionless_success_count; mod three_ds_sdk_count; use authentication_attempt_count::AuthenticationAttemptCount; @@ -23,6 +24,7 @@ use challenge_attempt_count::ChallengeAttemptCount; use challenge_flow_count::ChallengeFlowCount; use challenge_success_count::ChallengeSuccessCount; use frictionless_flow_count::FrictionlessFlowCount; +use frictionless_success_count::FrictionlessSuccessCount; use three_ds_sdk_count::ThreeDsSdkCount; #[derive(Debug, PartialEq, Eq, serde::Deserialize)] @@ -102,6 +104,11 @@ where .load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .await } + Self::FrictionlessSuccessCount => { + FrictionlessSuccessCount + .load_metrics(merchant_id, publishable_key, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs index dbea5a1dc17a..f2d2ab153f06 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs @@ -8,7 +8,7 @@ use time::PrimitiveDateTime; use super::AuthEventMetricRow; use crate::{ - query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, }; @@ -34,7 +34,7 @@ where pool: &T, ) -> MetricsResult> { let mut query_builder: QueryBuilder = - QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); query_builder .add_select_column(Aggregate::Count { @@ -54,7 +54,11 @@ where .switch()?; query_builder - .add_filter_clause("flow", AuthEventFlows::PostAuthentication) + .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) + .switch()?; + + query_builder + .add_custom_filter_clause("request", "threeDSServerTransID", FilterTypes::Like) .switch()?; time_range diff --git a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs index cb0932c33c22..b7d55714b604 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs @@ -34,7 +34,7 @@ where pool: &T, ) -> MetricsResult> { let mut query_builder: QueryBuilder = - QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); query_builder .add_select_column(Aggregate::Count { @@ -54,11 +54,11 @@ where .switch()?; query_builder - .add_filter_clause("flow", AuthEventFlows::PostAuthentication) + .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) .switch()?; query_builder - .add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") + .add_filter_clause("visitParamExtractRaw(request, 'transStatus')", "\"Y\"") .switch()?; time_range diff --git a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs new file mode 100644 index 000000000000..2f05a050ce7b --- /dev/null +++ b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs @@ -0,0 +1,94 @@ +use api_models::analytics::{ + auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::AuthEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct FrictionlessSuccessCount; + +#[async_trait::async_trait] +impl super::AuthEventMetric for FrictionlessSuccessCount +where + T: AnalyticsDataSource + super::AuthEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + merchant_id: &str, + _publishable_key: &str, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; + + query_builder + .add_filter_clause("api_flow", AuthEventFlows::PaymentsExternalAuthentication) + .switch()?; + + query_builder + .add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") + .switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + AuthEventMetricsBucketIdentifier::new(i.time_bucket.clone()), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 3009c036b99c..64064c09c880 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -136,7 +136,7 @@ impl AnalyticsDataSource for ClickhouseClient { AnalyticsCollection::SdkEvents | AnalyticsCollection::ApiEvents | AnalyticsCollection::ConnectorEvents - | AnalyticsCollection::ConnectorEventsAnalytics + | AnalyticsCollection::ApiEventsAnalytics | AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree, } } @@ -374,7 +374,7 @@ impl ToSql for AnalyticsCollection { Self::Refund => Ok("refunds".to_string()), Self::SdkEvents => Ok("sdk_events_audit".to_string()), Self::ApiEvents => Ok("api_events_audit".to_string()), - Self::ConnectorEventsAnalytics => Ok("connector_events".to_string()), + Self::ApiEventsAnalytics => Ok("api_events".to_string()), Self::PaymentIntent => Ok("payment_intents".to_string()), Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs index debc03fc9d58..a3f24b65a17e 100644 --- a/crates/analytics/src/payments/core.rs +++ b/crates/analytics/src/payments/core.rs @@ -291,6 +291,8 @@ pub async fn get_filters( PaymentDimensions::AuthType => fil.authentication_type.map(|i| i.as_ref().to_string()), PaymentDimensions::PaymentMethod => fil.payment_method, PaymentDimensions::PaymentMethodType => fil.payment_method_type, + PaymentDimensions::ClientSource => fil.client_source, + PaymentDimensions::ClientVersion => fil.client_version, }) .collect::>(); res.query_data.push(FilterValue { diff --git a/crates/analytics/src/payments/distribution.rs b/crates/analytics/src/payments/distribution.rs index cf18c26310a7..c238eb79b9b8 100644 --- a/crates/analytics/src/payments/distribution.rs +++ b/crates/analytics/src/payments/distribution.rs @@ -24,6 +24,8 @@ pub struct PaymentDistributionRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, pub total: Option, pub count: Option, pub error_message: Option, diff --git a/crates/analytics/src/payments/distribution/payment_error_message.rs b/crates/analytics/src/payments/distribution/payment_error_message.rs index c70fc09aeac4..7dc42994d295 100644 --- a/crates/analytics/src/payments/distribution/payment_error_message.rs +++ b/crates/analytics/src/payments/distribution/payment_error_message.rs @@ -153,6 +153,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/filters.rs b/crates/analytics/src/payments/filters.rs index 6c165f78a8e4..232a3e5b0a39 100644 --- a/crates/analytics/src/payments/filters.rs +++ b/crates/analytics/src/payments/filters.rs @@ -57,4 +57,6 @@ pub struct FilterRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, } diff --git a/crates/analytics/src/payments/metrics.rs b/crates/analytics/src/payments/metrics.rs index 6fe6b6260d48..59a9ca88a847 100644 --- a/crates/analytics/src/payments/metrics.rs +++ b/crates/analytics/src/payments/metrics.rs @@ -35,6 +35,8 @@ pub struct PaymentMetricRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] diff --git a/crates/analytics/src/payments/metrics/avg_ticket_size.rs b/crates/analytics/src/payments/metrics/avg_ticket_size.rs index 9475d5288a64..38ab0a9540d8 100644 --- a/crates/analytics/src/payments/metrics/avg_ticket_size.rs +++ b/crates/analytics/src/payments/metrics/avg_ticket_size.rs @@ -113,6 +113,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/connector_success_rate.rs b/crates/analytics/src/payments/metrics/connector_success_rate.rs index 0c4d19b2e0ba..2742327f4ab9 100644 --- a/crates/analytics/src/payments/metrics/connector_success_rate.rs +++ b/crates/analytics/src/payments/metrics/connector_success_rate.rs @@ -107,6 +107,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_count.rs b/crates/analytics/src/payments/metrics/payment_count.rs index 34e71f3da6fb..50ea1791b398 100644 --- a/crates/analytics/src/payments/metrics/payment_count.rs +++ b/crates/analytics/src/payments/metrics/payment_count.rs @@ -99,6 +99,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_processed_amount.rs b/crates/analytics/src/payments/metrics/payment_processed_amount.rs index f2dbf97e0db9..6ea5b15734f4 100644 --- a/crates/analytics/src/payments/metrics/payment_processed_amount.rs +++ b/crates/analytics/src/payments/metrics/payment_processed_amount.rs @@ -107,6 +107,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_success_count.rs b/crates/analytics/src/payments/metrics/payment_success_count.rs index a6fb8ed2239d..4350700618e9 100644 --- a/crates/analytics/src/payments/metrics/payment_success_count.rs +++ b/crates/analytics/src/payments/metrics/payment_success_count.rs @@ -106,6 +106,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/retries_count.rs b/crates/analytics/src/payments/metrics/retries_count.rs index 91952adb569a..87d80c87fb4d 100644 --- a/crates/analytics/src/payments/metrics/retries_count.rs +++ b/crates/analytics/src/payments/metrics/retries_count.rs @@ -99,6 +99,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/success_rate.rs b/crates/analytics/src/payments/metrics/success_rate.rs index 9e688240ddbf..a0c67c1d8070 100644 --- a/crates/analytics/src/payments/metrics/success_rate.rs +++ b/crates/analytics/src/payments/metrics/success_rate.rs @@ -102,6 +102,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/types.rs b/crates/analytics/src/payments/types.rs index d5d8eca13e58..b44446c66a94 100644 --- a/crates/analytics/src/payments/types.rs +++ b/crates/analytics/src/payments/types.rs @@ -50,6 +50,16 @@ where ) .attach_printable("Error adding payment method filter")?; } + if !self.client_source.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::ClientSource, &self.client_source) + .attach_printable("Error adding client source filter")?; + } + if !self.client_version.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::ClientVersion, &self.client_version) + .attach_printable("Error adding client version filter")?; + } Ok(()) } } diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index df146cc799d0..d3cb24a00c26 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -260,6 +260,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; let total: Option = row.try_get("total").or_else(|e| match e { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), @@ -282,6 +291,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { authentication_type, payment_method, payment_method_type, + client_source, + client_version, total, count, start_bucket, @@ -321,6 +332,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; let total: Option = row.try_get("total").or_else(|e| match e { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), @@ -347,6 +367,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi authentication_type, payment_method, payment_method_type, + client_source, + client_version, total, count, error_message, @@ -387,6 +409,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::FilterRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; Ok(Self { currency, status, @@ -394,6 +425,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::FilterRow { authentication_type, payment_method, payment_method_type, + client_source, + client_version, }) } } @@ -515,10 +548,10 @@ impl ToSql for AnalyticsCollection { Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::PaymentIntent => Ok("payment_intent".to_string()), - Self::ConnectorEvents | Self::ConnectorEventsAnalytics => { - Err(error_stack::report!(ParsingError::UnknownError) - .attach_printable("ConnectorEvents table is not implemented for Sqlx"))? - } + Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ConnectorEvents table is not implemented for Sqlx"))?, + Self::ApiEventsAnalytics => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, Self::Dispute => Ok("dispute".to_string()), diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index e8196a0968d7..86a9ec86eff2 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -31,7 +31,7 @@ pub enum AnalyticsCollection { ConnectorEvents, OutgoingWebhookEvent, Dispute, - ConnectorEventsAnalytics, + ApiEventsAnalytics, } #[allow(dead_code)] diff --git a/crates/api_models/src/analytics/auth_events.rs b/crates/api_models/src/analytics/auth_events.rs index bb08f9f59342..7791a2f3cd5f 100644 --- a/crates/api_models/src/analytics/auth_events.rs +++ b/crates/api_models/src/analytics/auth_events.rs @@ -25,6 +25,7 @@ pub enum AuthEventMetrics { AuthenticationSuccessCount, ChallengeFlowCount, FrictionlessFlowCount, + FrictionlessSuccessCount, ChallengeAttemptCount, ChallengeSuccessCount, } @@ -42,7 +43,8 @@ pub enum AuthEventMetrics { strum::AsRefStr, )] pub enum AuthEventFlows { - PostAuthentication, + IncomingWebhookReceive, + PaymentsExternalAuthentication, } pub mod metric_behaviour { @@ -51,6 +53,7 @@ pub mod metric_behaviour { pub struct AuthenticationSuccessCount; pub struct ChallengeFlowCount; pub struct FrictionlessFlowCount; + pub struct FrictionlessSuccessCount; pub struct ChallengeAttemptCount; pub struct ChallengeSuccessCount; } @@ -100,6 +103,7 @@ pub struct AuthEventMetricsBucketValue { pub challenge_attempt_count: Option, pub challenge_success_count: Option, pub frictionless_flow_count: Option, + pub frictionless_success_count: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs index 2d7ae262f489..61b88c8fc545 100644 --- a/crates/api_models/src/analytics/payments.rs +++ b/crates/api_models/src/analytics/payments.rs @@ -22,6 +22,10 @@ pub struct PaymentFilters { pub payment_method: Vec, #[serde(default)] pub payment_method_type: Vec, + #[serde(default)] + pub client_source: Vec, + #[serde(default)] + pub client_version: Vec, } #[derive( @@ -53,6 +57,8 @@ pub enum PaymentDimensions { #[strum(serialize = "status")] #[serde(rename = "status")] PaymentStatus, + ClientSource, + ClientVersion, } #[derive( @@ -141,6 +147,8 @@ pub struct PaymentMetricsBucketIdentifier { pub auth_type: Option, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, // Coz FE sucks @@ -150,6 +158,7 @@ pub struct PaymentMetricsBucketIdentifier { } impl PaymentMetricsBucketIdentifier { + #[allow(clippy::too_many_arguments)] pub fn new( currency: Option, status: Option, @@ -157,6 +166,8 @@ impl PaymentMetricsBucketIdentifier { auth_type: Option, payment_method: Option, payment_method_type: Option, + client_source: Option, + client_version: Option, normalized_time_range: TimeRange, ) -> Self { Self { @@ -166,6 +177,8 @@ impl PaymentMetricsBucketIdentifier { auth_type, payment_method, payment_method_type, + client_source, + client_version, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } @@ -180,6 +193,8 @@ impl Hash for PaymentMetricsBucketIdentifier { self.auth_type.map(|i| i.to_string()).hash(state); self.payment_method.hash(state); self.payment_method_type.hash(state); + self.client_source.hash(state); + self.client_version.hash(state); self.time_bucket.hash(state); } } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 4f648ebd1bb2..f0ce3839fceb 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2980,6 +2980,10 @@ pub struct ThreeDsData { pub three_ds_method_details: ThreeDsMethodData, /// Poll config for a connector pub poll_config: PollConfigResponse, + /// Message Version + pub message_version: Option, + /// Directory Server ID + pub directory_server_id: Option, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index ce2f02d087fe..9b7c4c96ee04 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -42,6 +42,7 @@ pub struct Authentication { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub directory_server_id: Option, } impl Authentication { @@ -86,6 +87,7 @@ pub struct AuthenticationNew { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub directory_server_id: Option, } #[derive(Debug)] @@ -101,6 +103,7 @@ pub enum AuthenticationUpdate { authentication_status: common_enums::AuthenticationStatus, acquirer_bin: Option, acquirer_merchant_id: Option, + directory_server_id: Option, }, AuthenticationUpdate { authentication_value: Option, @@ -159,6 +162,7 @@ pub struct AuthenticationUpdateInternal { pub acs_reference_number: Option, pub acs_trans_id: Option, pub acs_signed_content: Option, + pub directory_server_id: Option, } impl Default for AuthenticationUpdateInternal { @@ -189,6 +193,7 @@ impl Default for AuthenticationUpdateInternal { acs_reference_number: Default::default(), acs_trans_id: Default::default(), acs_signed_content: Default::default(), + directory_server_id: Default::default(), } } } @@ -221,6 +226,7 @@ impl AuthenticationUpdateInternal { acs_reference_number, acs_trans_id, acs_signed_content, + directory_server_id, } = self; Authentication { connector_authentication_id: connector_authentication_id @@ -252,6 +258,7 @@ impl AuthenticationUpdateInternal { acs_reference_number: acs_reference_number.or(source.acs_reference_number), acs_trans_id: acs_trans_id.or(source.acs_trans_id), acs_signed_content: acs_signed_content.or(source.acs_signed_content), + directory_server_id: directory_server_id.or(source.directory_server_id), ..source } } @@ -304,6 +311,7 @@ impl From for AuthenticationUpdateInternal { authentication_status, acquirer_bin, acquirer_merchant_id, + directory_server_id, } => Self { threeds_server_transaction_id: Some(threeds_server_transaction_id), maximum_supported_version: Some(maximum_supported_3ds_version), @@ -315,6 +323,7 @@ impl From for AuthenticationUpdateInternal { authentication_status: Some(authentication_status), acquirer_bin, acquirer_merchant_id, + directory_server_id, ..Default::default() }, AuthenticationUpdate::AuthenticationUpdate { diff --git a/crates/diesel_models/src/query/merchant_connector_account.rs b/crates/diesel_models/src/query/merchant_connector_account.rs index bd24d12ec22c..cc73bd7a5323 100644 --- a/crates/diesel_models/src/query/merchant_connector_account.rs +++ b/crates/diesel_models/src/query/merchant_connector_account.rs @@ -124,7 +124,9 @@ impl MerchantConnectorAccount { if get_disabled { generics::generic_filter::<::Table, _, _, _>( conn, - dsl::merchant_id.eq(merchant_id.to_owned()), + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::disabled.eq(true)), None, None, Some(dsl::created_at.asc()), diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 4fbe39503b4e..de5b536dcda2 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -115,6 +115,8 @@ diesel::table! { payment_id -> Nullable, #[max_length = 128] merchant_connector_id -> Varchar, + #[max_length = 128] + directory_server_id -> Nullable, } } diff --git a/crates/euclid/src/frontend/dir.rs b/crates/euclid/src/frontend/dir.rs index bc16057fbb59..f2b9ab990499 100644 --- a/crates/euclid/src/frontend/dir.rs +++ b/crates/euclid/src/frontend/dir.rs @@ -809,6 +809,10 @@ mod test { let mut key_names: FxHashMap = FxHashMap::default(); for key in DirKeyKind::iter() { + if matches!(key, DirKeyKind::Connector) { + continue; + } + let json_str = if let DirKeyKind::MetaData = key { r#""metadata""#.to_string() } else { diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index dd3335cb2c07..c1db2a4fb995 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -14,17 +14,25 @@ payouts = ["api_models/payouts"] [dependencies] # First party deps -api_models = { version = "0.1.0", path = "../api_models" } +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" } 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"} # Third party deps +actix-web = "4.5.1" async-trait = "0.1.79" error-stack = "0.4.1" http = "0.2.12" +mime = "0.3.17" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +serde_with = "3.7.0" 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"] } + diff --git a/crates/hyperswitch_domain_models/src/errors.rs b/crates/hyperswitch_domain_models/src/errors.rs index bed1ab9ccbf5..bd05e1a37713 100644 --- a/crates/hyperswitch_domain_models/src/errors.rs +++ b/crates/hyperswitch_domain_models/src/errors.rs @@ -1,3 +1,4 @@ +pub mod api_error_response; use diesel_models::errors::DatabaseError; pub type StorageResult = error_stack::Result; diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs new file mode 100644 index 000000000000..26a6f1618fa5 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -0,0 +1,634 @@ +use api_models::errors::types::Extra; +use common_utils::errors::ErrorSwitch; +use http::StatusCode; + +use crate::router_data; + +#[derive(Clone, Debug, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ErrorType { + InvalidRequestError, + ObjectNotFound, + RouterError, + ProcessingError, + BadGateway, + ServerNotAvailable, + DuplicateRequest, + ValidationError, + ConnectorError, + LockTimeout, +} + +#[derive(Debug, Clone, router_derive::ApiError)] +#[error(error_type_enum = ErrorType)] +pub enum ApiErrorResponse { + #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "{message:?}")] + NotImplemented { message: NotImplementedMessage }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_01", + message = "API key not provided or invalid API key used" + )] + Unauthorized, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL")] + InvalidRequestUrl, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "The HTTP method is not applicable for this API")] + InvalidHttpMethod, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "Missing required param: {field_name}")] + MissingRequiredField { field_name: &'static str }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_05", + message = "{field_name} contains invalid data. Expected format is {expected_format}" + )] + InvalidDataFormat { + field_name: String, + expected_format: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_06", message = "{message}")] + InvalidRequestData { message: String }, + /// Typically used when a field has invalid value, or deserialization of the value contained in a field fails. + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}")] + InvalidDataValue { field_name: &'static str }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret was not provided")] + ClientSecretNotGiven, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret has expired")] + ClientSecretExpired, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "The client_secret provided does not match the client_secret associated with the Payment")] + ClientSecretInvalid, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Customer has active mandate/subsciption")] + MandateActive, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_11", message = "Customer has already been redacted")] + CustomerRedacted, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")] + MaximumRefundCount, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")] + RefundAmountExceedsPaymentAmount, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")] + PaymentUnexpectedState { + current_flow: String, + field_name: String, + current_value: String, + states: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_15", message = "Invalid Ephemeral Key for the customer")] + InvalidEphemeralKey, + /// Typically used when information involving multiple fields or previously provided information doesn't satisfy a condition. + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_16", message = "{message}")] + PreconditionFailed { message: String }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_17", + message = "Access forbidden, invalid JWT token was used" + )] + InvalidJwtToken, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_18", + message = "{message}", + )] + GenericUnauthorized { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] + NotSupported { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_20", message = "{flow} flow not supported by the {connector} connector")] + FlowNotSupported { flow: String, connector: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_21", message = "Missing required params")] + MissingRequiredFields { field_names: Vec<&'static str> }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_22", message = "Access forbidden. Not authorized to access this resource {resource}")] + AccessForbidden { resource: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] + FileProviderNotSupported { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] + UnprocessableEntity { message: String }, + #[error( + error_type = ErrorType::ProcessingError, code = "IR_24", + message = "Invalid {wallet_name} wallet token" + )] + InvalidWalletToken { wallet_name: String }, + #[error(error_type = ErrorType::ConnectorError, code = "CE_00", message = "{code}: {message}", ignore = "status_code")] + ExternalConnectorError { + code: String, + message: String, + connector: String, + status_code: u16, + reason: Option, + }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed during authorization with connector. Retry payment")] + PaymentAuthorizationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed during authentication with connector. Retry payment")] + PaymentAuthenticationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector")] + PaymentCaptureFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_04", message = "The card data is invalid")] + InvalidCardData { data: Option }, + #[error(error_type = ErrorType::InvalidRequestError, code = "CE_04", message = "Payout validation failed")] + PayoutFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_05", message = "The card has expired")] + CardExpired { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_06", message = "Refund failed while processing with connector. Retry refund")] + RefundFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_07", message = "Verification failed while processing with connector. Retry operation")] + VerificationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_08", message = "Dispute operation failed while processing with connector. Retry operation")] + DisputeFailed { data: Option }, + #[error(error_type = ErrorType::ServerNotAvailable, code = "HE_00", message = "Something went wrong")] + InternalServerError, + #[error(error_type = ErrorType::LockTimeout, code = "HE_00", message = "Resource is busy. Please try again later.")] + ResourceBusy, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate refund request. Refund already attempted with the refund ID")] + DuplicateRefundRequest, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate mandate request. Mandate already attempted with the Mandate ID")] + DuplicateMandate, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant account with the specified details already exists in our records")] + DuplicateMerchantAccount, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")] + DuplicateMerchantConnectorAccount { + profile_id: String, + connector_label: String, + }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment method with the specified details already exists in our records")] + DuplicatePaymentMethod, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment with the specified payment_id already exists in our records")] + DuplicatePayment { payment_id: String }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payout with the specified payout_id '{payout_id}' already exists in our records")] + DuplicatePayout { payout_id: String }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The config with the specified key already exists in our records")] + DuplicateConfig, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Refund does not exist in our records")] + RefundNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Customer does not exist in our records")] + CustomerNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "RE_02", message = "Config key does not exist in our records.")] + ConfigNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment does not exist in our records")] + PaymentNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment method does not exist in our records")] + PaymentMethodNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant account does not exist in our records")] + MerchantAccountNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant connector account does not exist in our records")] + MerchantConnectorAccountNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Business profile with the given id '{id}' does not exist in our records")] + BusinessProfileNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Poll with the given id '{id}' does not exist in our records")] + PollNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")] + ResourceIdNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] + MandateNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")] + AuthenticationNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] + MandateUpdateFailed, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] + ApiKeyNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payout does not exist in our records")] + PayoutNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Event does not exist in our records")] + EventNotFound, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Invalid mandate id passed from connector")] + MandateSerializationFailed, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Unable to parse the mandate identifier passed from connector")] + MandateDeserializationFailed, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Return URL is not configured and not passed in payments request")] + ReturnUrlUnavailable, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard")] + RefundNotPossible { connector: String }, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Mandate Validation Failed" )] + MandateValidationFailed { reason: String }, + #[error(error_type= ErrorType::ValidationError, code = "HE_03", message = "The payment has not succeeded yet. Please pass a successful payment to initiate refund")] + PaymentNotSucceeded, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")] + MerchantConnectorAccountDisabled, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "{code}: {message}")] + PaymentBlockedError { + code: u16, + message: String, + status: String, + reason: String, + }, + #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")] + SuccessfulPaymentNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")] + IncorrectConnectorNameGiven, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Address does not exist in our records")] + AddressNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Dispute does not exist in our records")] + DisputeNotFound { dispute_id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File does not exist in our records")] + FileNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File not available")] + FileNotAvailable, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute status validation failed")] + DisputeStatusValidationFailed { reason: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Card with the provided iin does not exist")] + InvalidCardIin, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "The provided card IIN length is invalid, please provide an iin with 6 or 8 digits")] + InvalidCardIinLength, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "File validation failed")] + FileValidationFailed { reason: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File not found / valid in the request")] + MissingFile, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute id not found in the request")] + MissingDisputeId, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File purpose not found in the request or is invalid")] + MissingFilePurpose, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File content type not found / valid")] + MissingFileContentType, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_05", message = "{message}")] + GenericNotFoundError { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_01", message = "{message}")] + GenericDuplicateError { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] + WebhookAuthenticationFailed, + #[error(error_type = ErrorType::ObjectNotFound, code = "WE_04", message = "Webhook resource not found")] + WebhookResourceNotFound, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] + WebhookBadRequest, + #[error(error_type = ErrorType::RouterError, code = "WE_03", message = "There was some issue processing the webhook")] + WebhookProcessingFailure, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "required payment method is not configured or configured incorrectly for all configured connectors")] + IncorrectPaymentMethodConfiguration, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Unable to process the webhook body")] + WebhookUnprocessableEntity, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment Link does not exist in our records")] + PaymentLinkNotFound, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Merchant Secret set my merchant for webhook source verification is invalid")] + WebhookInvalidMerchantSecret, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] + CurrencyNotSupported { message: String }, + #[error(error_type = ErrorType::ServerNotAvailable, code= "HE_00", message = "{component} health check is failing with error: {message}")] + HealthCheckError { + component: &'static str, + message: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] + InvalidConnectorConfiguration { config: String }, + #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] + CurrencyConversionFailed, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] + PaymentMethodDeleteFailed, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_26", + message = "Invalid Cookie" + )] + InvalidCookie, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_27", message = "Extended card info does not exist")] + ExtendedCardInfoNotFound, +} + +#[derive(Clone)] +pub enum NotImplementedMessage { + Reason(String), + Default, +} + +impl std::fmt::Debug for NotImplementedMessage { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Reason(message) => write!(fmt, "{message} is not implemented"), + Self::Default => { + write!( + fmt, + "This API is under development and will be made available soon." + ) + } + } + } +} + +impl ::core::fmt::Display for ApiErrorResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#"{{"error":{}}}"#, + serde_json::to_string(self).unwrap_or_else(|_| "API error response".to_string()) + ) + } +} + +impl ErrorSwitch for ApiErrorResponse { + fn switch(&self) -> api_models::errors::types::ApiErrorResponse { + use api_models::errors::types::{ApiError, ApiErrorResponse as AER}; + + match self { + Self::NotImplemented { message } => { + AER::NotImplemented(ApiError::new("IR", 0, format!("{message:?}"), None)) + } + Self::Unauthorized => AER::Unauthorized(ApiError::new( + "IR", + 1, + "API key not provided or invalid API key used", None + )), + Self::InvalidRequestUrl => { + AER::NotFound(ApiError::new("IR", 2, "Unrecognized request URL", None)) + } + Self::InvalidHttpMethod => AER::MethodNotAllowed(ApiError::new( + "IR", + 3, + "The HTTP method is not applicable for this API", None + )), + Self::MissingRequiredField { field_name } => AER::BadRequest( + ApiError::new("IR", 4, format!("Missing required param: {field_name}"), None), + ), + Self::InvalidDataFormat { + field_name, + expected_format, + } => AER::Unprocessable(ApiError::new( + "IR", + 5, + format!( + "{field_name} contains invalid data. Expected format is {expected_format}" + ), None + )), + Self::InvalidRequestData { message } => { + AER::Unprocessable(ApiError::new("IR", 6, message.to_string(), None)) + } + Self::InvalidDataValue { field_name } => AER::BadRequest(ApiError::new( + "IR", + 7, + format!("Invalid value provided: {field_name}"), None + )), + Self::ClientSecretNotGiven => AER::BadRequest(ApiError::new( + "IR", + 8, + "client_secret was not provided", None + )), + Self::ClientSecretInvalid => { + AER::BadRequest(ApiError::new("IR", 9, "The client_secret provided does not match the client_secret associated with the Payment", None)) + } + Self::CurrencyNotSupported { message } => { + AER::BadRequest(ApiError::new("IR", 9, message, None)) + } + Self::MandateActive => { + AER::BadRequest(ApiError::new("IR", 10, "Customer has active mandate/subsciption", None)) + } + Self::CustomerRedacted => { + AER::BadRequest(ApiError::new("IR", 11, "Customer has already been redacted", None)) + } + Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)), + Self::RefundAmountExceedsPaymentAmount => { + AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None)) + } + Self::PaymentUnexpectedState { + current_flow, + field_name, + current_value, + states, + } => AER::BadRequest(ApiError::new("IR", 14, format!("This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}"), None)), + Self::InvalidEphemeralKey => AER::Unauthorized(ApiError::new("IR", 15, "Invalid Ephemeral Key for the customer", None)), + Self::PreconditionFailed { message } => { + AER::BadRequest(ApiError::new("IR", 16, message.to_string(), None)) + } + Self::InvalidJwtToken => AER::Unauthorized(ApiError::new("IR", 17, "Access forbidden, invalid JWT token was used", None)), + Self::GenericUnauthorized { message } => { + AER::Unauthorized(ApiError::new("IR", 18, message.to_string(), None)) + }, + Self::ClientSecretExpired => AER::BadRequest(ApiError::new( + "IR", + 19, + "The provided client_secret has expired", None + )), + Self::MissingRequiredFields { field_names } => AER::BadRequest( + ApiError::new("IR", 21, "Missing required params".to_string(), Some(Extra {data: Some(serde_json::json!(field_names)), ..Default::default() })), + ), + Self::AccessForbidden {resource} => { + AER::ForbiddenCommonResource(ApiError::new("IR", 22, format!("Access forbidden. Not authorized to access this resource {resource}"), None)) + }, + Self::FileProviderNotSupported { message } => { + AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None)) + }, + Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 23, message.to_string(), None)), + Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new( + "IR", + 24, + format!("Invalid {wallet_name} wallet token"), None + )), + Self::ExternalConnectorError { + code, + message, + connector, + reason, + status_code, + } => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.to_owned().map(Into::into), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)), + Self::PaymentAuthorizationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 1, "Payment failed during authorization with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::PaymentAuthenticationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 2, "Payment failed during authentication with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::PaymentCaptureFailed { data } => { + AER::BadRequest(ApiError::new("CE", 3, "Capture attempt failed while processing with connector", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::DisputeFailed { data } => { + AER::BadRequest(ApiError::new("CE", 1, "Dispute operation failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::InvalidCardData { data } => AER::BadRequest(ApiError::new("CE", 4, "The card data is invalid", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::CardExpired { data } => AER::BadRequest(ApiError::new("CE", 5, "The card has expired", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::RefundFailed { data } => AER::BadRequest(ApiError::new("CE", 6, "Refund failed while processing with connector. Retry refund", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::VerificationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 7, "Verification failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) + }, + Self::MandateUpdateFailed | Self::MandateSerializationFailed | Self::MandateDeserializationFailed | Self::InternalServerError => { + AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None)) + }, + Self::HealthCheckError { message,component } => { + AER::InternalServerError(ApiError::new("HE",0,format!("{} health check failed with error: {}",component,message),None)) + }, + Self::PayoutFailed { data } => { + AER::BadRequest(ApiError::new("CE", 4, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) + }, + Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)), + Self::DuplicateMandate => AER::BadRequest(ApiError::new("HE", 1, "Duplicate mandate request. Mandate already attempted with the Mandate ID", None)), + Self::DuplicateMerchantAccount => AER::BadRequest(ApiError::new("HE", 1, "The merchant account with the specified details already exists in our records", None)), + Self::DuplicateMerchantConnectorAccount { profile_id, connector_label: connector_name } => { + AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_name}' already exists in our records"), None)) + } + Self::DuplicatePaymentMethod => AER::BadRequest(ApiError::new("HE", 1, "The payment method with the specified details already exists in our records", None)), + Self::DuplicatePayment { payment_id } => { + AER::BadRequest(ApiError::new("HE", 1, "The payment with the specified payment_id already exists in our records", Some(Extra {reason: Some(format!("{payment_id} already exists")), ..Default::default()}))) + } + Self::DuplicatePayout { payout_id } => { + AER::BadRequest(ApiError::new("HE", 1, format!("The payout with the specified payout_id '{payout_id}' already exists in our records"), None)) + } + Self::GenericDuplicateError { message } => { + AER::BadRequest(ApiError::new("HE", 1, message, None)) + } + Self::RefundNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Refund does not exist in our records.", None)) + } + Self::CustomerNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Customer does not exist in our records", None)) + } + Self::ConfigNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Config key does not exist in our records.", None)) + }, + Self::DuplicateConfig => { + AER::BadRequest(ApiError::new("HE", 1, "The config with the specified key already exists in our records", None)) + } + Self::PaymentNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment does not exist in our records", None)) + } + Self::PaymentMethodNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment method does not exist in our records", None)) + } + Self::MerchantAccountNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Merchant account does not exist in our records", None)) + } + Self::MerchantConnectorAccountNotFound {id } => { + AER::NotFound(ApiError::new("HE", 2, "Merchant connector account does not exist in our records", Some(Extra {reason: Some(format!("{id} does not exist")), ..Default::default()}))) + } + Self::MerchantConnectorAccountDisabled => { + AER::BadRequest(ApiError::new("HE", 3, "The selected merchant connector account is disabled", None)) + } + Self::ResourceIdNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Resource ID does not exist in our records", None)) + } + Self::MandateNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Mandate does not exist in our records", None)) + } + Self::PayoutNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payout does not exist in our records", None)) + } + Self::EventNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Event does not exist in our records", None)) + } + Self::ReturnUrlUnavailable => AER::NotFound(ApiError::new("HE", 3, "Return URL is not configured and not passed in payments request", None)), + Self::RefundNotPossible { connector } => { + AER::BadRequest(ApiError::new("HE", 3, format!("This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard"), None)) + } + Self::MandateValidationFailed { reason } => { + AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.to_owned()), ..Default::default() }))) + } + Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)), + Self::PaymentBlockedError { + message, + reason, + .. + } => AER::DomainError(ApiError::new("HE", 3, message, Some(Extra { reason: Some(reason.clone()), ..Default::default() }))), + Self::SuccessfulPaymentNotFound => { + AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None)) + } + Self::IncorrectConnectorNameGiven => { + AER::NotFound(ApiError::new("HE", 4, "The connector provided in the request is incorrect or not available", None)) + } + Self::AddressNotFound => { + AER::NotFound(ApiError::new("HE", 4, "Address does not exist in our records", None)) + }, + Self::GenericNotFoundError { message } => { + AER::NotFound(ApiError::new("HE", 5, message, None)) + }, + Self::ApiKeyNotFound => { + AER::NotFound(ApiError::new("HE", 2, "API Key does not exist in our records", None)) + } + Self::NotSupported { message } => { + AER::BadRequest(ApiError::new("HE", 3, "Payment method type not supported", Some(Extra {reason: Some(message.to_owned()), ..Default::default()}))) + }, + Self::InvalidCardIin => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN does not exist", None)), + Self::InvalidCardIinLength => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN length is invalid, please provide an IIN with 6 digits", None)), + Self::FlowNotSupported { flow, connector } => { + AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message + } + Self::DisputeNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) + }, + Self::AuthenticationNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None)) + }, + Self::BusinessProfileNotFound { id } => { + AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) + } + Self::FileNotFound => { + AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) + } + Self::PollNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Poll does not exist in our records", None)) + }, + Self::FileNotAvailable => { + AER::NotFound(ApiError::new("HE", 2, "File not available", None)) + } + Self::DisputeStatusValidationFailed { .. } => { + AER::BadRequest(ApiError::new("HE", 2, "Dispute status validation failed", None)) + } + Self::FileValidationFailed { reason } => { + AER::BadRequest(ApiError::new("HE", 2, format!("File validation failed {reason}"), None)) + } + Self::MissingFile => { + AER::BadRequest(ApiError::new("HE", 2, "File not found in the request", None)) + } + Self::MissingFilePurpose => { + AER::BadRequest(ApiError::new("HE", 2, "File purpose not found in the request or is invalid", None)) + } + Self::MissingFileContentType => { + AER::BadRequest(ApiError::new("HE", 2, "File content type not found", None)) + } + Self::MissingDisputeId => { + AER::BadRequest(ApiError::new("HE", 2, "Dispute id not found in the request", None)) + } + Self::WebhookAuthenticationFailed => { + AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) + } + Self::WebhookResourceNotFound => { + AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None)) + } + Self::WebhookBadRequest => { + AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None)) + } + Self::WebhookProcessingFailure => { + AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None)) + }, + Self::WebhookInvalidMerchantSecret => { + AER::BadRequest(ApiError::new("WE", 2, "Merchant Secret set for webhook source verificartion is invalid", None)) + } + Self::IncorrectPaymentMethodConfiguration => { + AER::BadRequest(ApiError::new("HE", 4, "No eligible connector was found for the current payment method configuration", None)) + } + Self::WebhookUnprocessableEntity => { + AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) + }, + Self::ResourceBusy => { + AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) + } + Self::PaymentLinkNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment Link does not exist in our records", None)) + } + Self::InvalidConnectorConfiguration {config} => { + AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) + } + Self::CurrencyConversionFailed => { + AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) + } + Self::PaymentMethodDeleteFailed => { + AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) + } + Self::InvalidCookie => { + AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None)) + } + Self::ExtendedCardInfoNotFound => { + AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None)) + } + } + } +} + +impl actix_web::ResponseError for ApiErrorResponse { + fn status_code(&self) -> StatusCode { + ErrorSwitch::::switch(self).status_code() + } + + fn error_response(&self) -> actix_web::HttpResponse { + ErrorSwitch::::switch(self).error_response() + } +} + +impl From for router_data::ErrorResponse { + fn from(error: ApiErrorResponse) -> Self { + Self { + code: error.error_code(), + message: error.error_message(), + reason: None, + status_code: match error { + ApiErrorResponse::ExternalConnectorError { status_code, .. } => status_code, + _ => 500, + }, + attempt_status: None, + connector_transaction_id: None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index b470b538d786..ba6ccdf839a4 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -1,10 +1,12 @@ pub mod errors; pub mod mandates; pub mod payment_address; +pub mod payment_method_data; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod router_data; +pub mod router_request_types; #[cfg(not(feature = "payouts"))] pub trait PayoutAttemptInterface {} diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs new file mode 100644 index 000000000000..9dc85c103e97 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -0,0 +1,837 @@ +use common_utils::pii::{self, Email}; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +// We need to derive Serialize and Deserialize because some parts of payment method data are being +// stored in the database as serde_json::Value +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum PaymentMethodData { + Card(Card), + CardRedirect(CardRedirectData), + Wallet(WalletData), + PayLater(PayLaterData), + BankRedirect(BankRedirectData), + BankDebit(BankDebitData), + BankTransfer(Box), + Crypto(CryptoData), + MandatePayment, + Reward, + Upi(UpiData), + Voucher(VoucherData), + GiftCard(Box), + CardToken(CardToken), +} + +impl PaymentMethodData { + pub fn get_payment_method(&self) -> Option { + match self { + Self::Card(_) => Some(common_enums::PaymentMethod::Card), + Self::CardRedirect(_) => Some(common_enums::PaymentMethod::CardRedirect), + Self::Wallet(_) => Some(common_enums::PaymentMethod::Wallet), + Self::PayLater(_) => Some(common_enums::PaymentMethod::PayLater), + Self::BankRedirect(_) => Some(common_enums::PaymentMethod::BankRedirect), + Self::BankDebit(_) => Some(common_enums::PaymentMethod::BankDebit), + Self::BankTransfer(_) => Some(common_enums::PaymentMethod::BankTransfer), + Self::Crypto(_) => Some(common_enums::PaymentMethod::Crypto), + Self::Reward => Some(common_enums::PaymentMethod::Reward), + Self::Upi(_) => Some(common_enums::PaymentMethod::Upi), + Self::Voucher(_) => Some(common_enums::PaymentMethod::Voucher), + Self::GiftCard(_) => Some(common_enums::PaymentMethod::GiftCard), + Self::CardToken(_) | Self::MandatePayment => None, + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] +pub struct Card { + pub card_number: cards::CardNumber, + pub card_exp_month: Secret, + pub card_exp_year: Secret, + pub card_cvc: Secret, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_issuing_country: Option, + pub bank_code: Option, + pub nick_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum CardRedirectData { + Knet {}, + Benefit {}, + MomoAtm {}, + CardRedirect {}, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum PayLaterData { + KlarnaRedirect {}, + KlarnaSdk { token: String }, + AffirmRedirect {}, + AfterpayClearpayRedirect {}, + PayBrightRedirect {}, + WalleyRedirect {}, + AlmaRedirect {}, + AtomeRedirect {}, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub enum WalletData { + AliPayQr(Box), + AliPayRedirect(AliPayRedirection), + AliPayHkRedirect(AliPayHkRedirection), + MomoRedirect(MomoRedirection), + KakaoPayRedirect(KakaoPayRedirection), + GoPayRedirect(GoPayRedirection), + GcashRedirect(GcashRedirection), + ApplePay(ApplePayWalletData), + ApplePayRedirect(Box), + ApplePayThirdPartySdk(Box), + DanaRedirect {}, + GooglePay(GooglePayWalletData), + GooglePayRedirect(Box), + GooglePayThirdPartySdk(Box), + MbWayRedirect(Box), + MobilePayRedirect(Box), + PaypalRedirect(PaypalRedirection), + PaypalSdk(PayPalWalletData), + SamsungPay(Box), + TwintRedirect {}, + VippsRedirect {}, + TouchNGoRedirect(Box), + WeChatPayRedirect(Box), + WeChatPayQr(Box), + CashappQr(Box), + SwishQr(SwishQrData), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct SamsungPayWalletData { + /// The encrypted payment token from Samsung + pub token: Secret, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct GooglePayWalletData { + /// The type of payment method + pub pm_type: String, + /// User-facing message to describe the payment method that funds this transaction. + pub description: String, + /// The information of the payment method + pub info: GooglePayPaymentMethodInfo, + /// The tokenization data of Google pay + pub tokenization_data: GpayTokenizationData, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayRedirectData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GooglePayRedirectData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GooglePayThirdPartySdkData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayThirdPartySdkData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPay {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPayQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct CashappQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct PaypalRedirection { + /// paypal's email address + pub email: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayHkRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MomoRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct KakaoPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GoPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GcashRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MobilePayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MbWayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct GooglePayPaymentMethodInfo { + /// The name of the card network + pub card_network: String, + /// The details of the card + pub card_details: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct PayPalWalletData { + /// Token generated for the Apple pay + pub token: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct TouchNGoRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SwishQrData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GpayTokenizationData { + /// The type of the token + pub token_type: String, + /// Token generated for the wallet + pub token: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayWalletData { + /// The payment data of Apple pay + pub payment_data: String, + /// The payment method of Apple pay + pub payment_method: ApplepayPaymentMethod, + /// The unique identifier for the transaction + pub transaction_identifier: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplepayPaymentMethod { + pub display_name: String, + pub network: String, + pub pm_type: String, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] + +pub enum BankRedirectData { + BancontactCard { + card_number: Option, + card_exp_month: Option>, + card_exp_year: Option>, + }, + Bizum {}, + Blik { + blik_code: Option, + }, + Eps { + bank_name: Option, + }, + Giropay { + bank_account_bic: Option>, + bank_account_iban: Option>, + }, + Ideal { + bank_name: Option, + }, + Interac {}, + OnlineBankingCzechRepublic { + issuer: common_enums::BankNames, + }, + OnlineBankingFinland {}, + OnlineBankingPoland { + issuer: common_enums::BankNames, + }, + OnlineBankingSlovakia { + issuer: common_enums::BankNames, + }, + OpenBankingUk { + issuer: Option, + }, + Przelewy24 { + bank_name: Option, + }, + Sofort { + preferred_language: Option, + }, + Trustly {}, + OnlineBankingFpx { + issuer: common_enums::BankNames, + }, + OnlineBankingThailand { + issuer: common_enums::BankNames, + }, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct CryptoData { + pub pay_currency: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct UpiData { + pub vpa_id: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum VoucherData { + Boleto(Box), + Efecty, + PagoEfectivo, + RedCompra, + RedPagos, + Alfamart(Box), + Indomaret(Box), + Oxxo, + SevenEleven(Box), + Lawson(Box), + MiniStop(Box), + FamilyMart(Box), + Seicomart(Box), + PayEasy(Box), +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct BoletoVoucherData { + /// The shopper's social security number + pub social_security_number: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct AlfamartVoucherData {} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct IndomaretVoucherData {} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct JCSVoucherData {} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum GiftCardData { + Givex(GiftCardDetails), + PaySafeCard {}, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct GiftCardDetails { + /// The gift card number + pub number: Secret, + /// The card verification code. + pub cvc: Secret, +} + +#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, Default)] +#[serde(rename_all = "snake_case")] +pub struct CardToken { + /// The card holder's name + pub card_holder_name: Option>, + + /// The CVC number for the card + pub card_cvc: Option>, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BankDebitData { + AchBankDebit { + account_number: Secret, + routing_number: Secret, + bank_name: Option, + bank_type: Option, + bank_holder_type: Option, + }, + SepaBankDebit { + iban: Secret, + }, + BecsBankDebit { + account_number: Secret, + bsb_number: Secret, + }, + BacsBankDebit { + account_number: Secret, + sort_code: Secret, + }, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum BankTransferData { + AchBankTransfer {}, + SepaBankTransfer {}, + BacsBankTransfer {}, + MultibancoBankTransfer {}, + PermataBankTransfer {}, + BcaBankTransfer {}, + BniVaBankTransfer {}, + BriVaBankTransfer {}, + CimbVaBankTransfer {}, + DanamonVaBankTransfer {}, + MandiriVaBankTransfer {}, + Pix {}, + Pse {}, + LocalBankTransfer { bank_code: Option }, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct SepaAndBacsBillingDetails { + /// The Email ID for SEPA and BACS billing + pub email: Email, + /// The billing name for SEPA and BACS billing + pub name: Secret, +} + +impl From for PaymentMethodData { + fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self { + match api_model_payment_method_data { + api_models::payments::PaymentMethodData::Card(card_data) => { + Self::Card(Card::from(card_data)) + } + api_models::payments::PaymentMethodData::CardRedirect(card_redirect) => { + Self::CardRedirect(From::from(card_redirect)) + } + api_models::payments::PaymentMethodData::Wallet(wallet_data) => { + Self::Wallet(From::from(wallet_data)) + } + api_models::payments::PaymentMethodData::PayLater(pay_later_data) => { + Self::PayLater(From::from(pay_later_data)) + } + api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { + Self::BankRedirect(From::from(bank_redirect_data)) + } + api_models::payments::PaymentMethodData::BankDebit(bank_debit_data) => { + Self::BankDebit(From::from(bank_debit_data)) + } + api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { + Self::BankTransfer(Box::new(From::from(*bank_transfer_data))) + } + api_models::payments::PaymentMethodData::Crypto(crypto_data) => { + Self::Crypto(From::from(crypto_data)) + } + api_models::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, + api_models::payments::PaymentMethodData::Reward => Self::Reward, + api_models::payments::PaymentMethodData::Upi(upi_data) => { + Self::Upi(From::from(upi_data)) + } + api_models::payments::PaymentMethodData::Voucher(voucher_data) => { + Self::Voucher(From::from(voucher_data)) + } + api_models::payments::PaymentMethodData::GiftCard(gift_card) => { + Self::GiftCard(Box::new(From::from(*gift_card))) + } + api_models::payments::PaymentMethodData::CardToken(card_token) => { + Self::CardToken(From::from(card_token)) + } + } + } +} + +impl From for Card { + fn from(value: api_models::payments::Card) -> Self { + let api_models::payments::Card { + card_number, + card_exp_month, + card_exp_year, + card_holder_name: _, + card_cvc, + card_issuer, + card_network, + card_type, + card_issuing_country, + bank_code, + nick_name, + } = value; + + Self { + card_number, + card_exp_month, + card_exp_year, + card_cvc, + card_issuer, + card_network, + card_type, + card_issuing_country, + bank_code, + nick_name, + } + } +} + +impl From for CardRedirectData { + fn from(value: api_models::payments::CardRedirectData) -> Self { + match value { + api_models::payments::CardRedirectData::Knet {} => Self::Knet {}, + api_models::payments::CardRedirectData::Benefit {} => Self::Benefit {}, + api_models::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm {}, + api_models::payments::CardRedirectData::CardRedirect {} => Self::CardRedirect {}, + } + } +} + +impl From for WalletData { + fn from(value: api_models::payments::WalletData) -> Self { + match value { + api_models::payments::WalletData::AliPayQr(_) => Self::AliPayQr(Box::new(AliPayQr {})), + api_models::payments::WalletData::AliPayRedirect(_) => { + Self::AliPayRedirect(AliPayRedirection {}) + } + api_models::payments::WalletData::AliPayHkRedirect(_) => { + Self::AliPayHkRedirect(AliPayHkRedirection {}) + } + api_models::payments::WalletData::MomoRedirect(_) => { + Self::MomoRedirect(MomoRedirection {}) + } + api_models::payments::WalletData::KakaoPayRedirect(_) => { + Self::KakaoPayRedirect(KakaoPayRedirection {}) + } + api_models::payments::WalletData::GoPayRedirect(_) => { + Self::GoPayRedirect(GoPayRedirection {}) + } + api_models::payments::WalletData::GcashRedirect(_) => { + Self::GcashRedirect(GcashRedirection {}) + } + api_models::payments::WalletData::ApplePay(apple_pay_data) => { + Self::ApplePay(ApplePayWalletData::from(apple_pay_data)) + } + api_models::payments::WalletData::ApplePayRedirect(_) => { + Self::ApplePayRedirect(Box::new(ApplePayRedirectData {})) + } + api_models::payments::WalletData::ApplePayThirdPartySdk(_) => { + Self::ApplePayThirdPartySdk(Box::new(ApplePayThirdPartySdkData {})) + } + api_models::payments::WalletData::DanaRedirect {} => Self::DanaRedirect {}, + api_models::payments::WalletData::GooglePay(google_pay_data) => { + Self::GooglePay(GooglePayWalletData::from(google_pay_data)) + } + api_models::payments::WalletData::GooglePayRedirect(_) => { + Self::GooglePayRedirect(Box::new(GooglePayRedirectData {})) + } + api_models::payments::WalletData::GooglePayThirdPartySdk(_) => { + Self::GooglePayThirdPartySdk(Box::new(GooglePayThirdPartySdkData {})) + } + api_models::payments::WalletData::MbWayRedirect(..) => { + Self::MbWayRedirect(Box::new(MbWayRedirection {})) + } + api_models::payments::WalletData::MobilePayRedirect(_) => { + Self::MobilePayRedirect(Box::new(MobilePayRedirection {})) + } + api_models::payments::WalletData::PaypalRedirect(paypal_redirect_data) => { + Self::PaypalRedirect(PaypalRedirection { + email: paypal_redirect_data.email, + }) + } + api_models::payments::WalletData::PaypalSdk(paypal_sdk_data) => { + Self::PaypalSdk(PayPalWalletData { + token: paypal_sdk_data.token, + }) + } + api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { + Self::SamsungPay(Box::new(SamsungPayWalletData { + token: samsung_pay_data.token, + })) + } + api_models::payments::WalletData::TwintRedirect {} => Self::TwintRedirect {}, + api_models::payments::WalletData::VippsRedirect {} => Self::VippsRedirect {}, + api_models::payments::WalletData::TouchNGoRedirect(_) => { + Self::TouchNGoRedirect(Box::new(TouchNGoRedirection {})) + } + api_models::payments::WalletData::WeChatPayRedirect(_) => { + Self::WeChatPayRedirect(Box::new(WeChatPayRedirection {})) + } + api_models::payments::WalletData::WeChatPayQr(_) => { + Self::WeChatPayQr(Box::new(WeChatPayQr {})) + } + api_models::payments::WalletData::CashappQr(_) => { + Self::CashappQr(Box::new(CashappQr {})) + } + api_models::payments::WalletData::SwishQr(_) => Self::SwishQr(SwishQrData {}), + } + } +} + +impl From for GooglePayWalletData { + fn from(value: api_models::payments::GooglePayWalletData) -> Self { + Self { + pm_type: value.pm_type, + description: value.description, + info: GooglePayPaymentMethodInfo { + card_network: value.info.card_network, + card_details: value.info.card_details, + }, + tokenization_data: GpayTokenizationData { + token_type: value.tokenization_data.token_type, + token: value.tokenization_data.token, + }, + } + } +} + +impl From for ApplePayWalletData { + fn from(value: api_models::payments::ApplePayWalletData) -> Self { + Self { + payment_data: value.payment_data, + payment_method: ApplepayPaymentMethod { + display_name: value.payment_method.display_name, + network: value.payment_method.network, + pm_type: value.payment_method.pm_type, + }, + transaction_identifier: value.transaction_identifier, + } + } +} + +impl From for PayLaterData { + fn from(value: api_models::payments::PayLaterData) -> Self { + match value { + api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, + api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, + api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, + api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { + Self::AfterpayClearpayRedirect {} + } + api_models::payments::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect {}, + api_models::payments::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect {}, + api_models::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect {}, + api_models::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect {}, + } + } +} + +impl From for BankRedirectData { + fn from(value: api_models::payments::BankRedirectData) -> Self { + match value { + api_models::payments::BankRedirectData::BancontactCard { + card_number, + card_exp_month, + card_exp_year, + .. + } => Self::BancontactCard { + card_number, + card_exp_month, + card_exp_year, + }, + api_models::payments::BankRedirectData::Bizum {} => Self::Bizum {}, + api_models::payments::BankRedirectData::Blik { blik_code } => Self::Blik { blik_code }, + api_models::payments::BankRedirectData::Eps { bank_name, .. } => { + Self::Eps { bank_name } + } + api_models::payments::BankRedirectData::Giropay { + bank_account_bic, + bank_account_iban, + .. + } => Self::Giropay { + bank_account_bic, + bank_account_iban, + }, + api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { + Self::Ideal { bank_name } + } + api_models::payments::BankRedirectData::Interac { .. } => Self::Interac {}, + api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { issuer } => { + Self::OnlineBankingCzechRepublic { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingFinland { .. } => { + Self::OnlineBankingFinland {} + } + api_models::payments::BankRedirectData::OnlineBankingPoland { issuer } => { + Self::OnlineBankingPoland { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingSlovakia { issuer } => { + Self::OnlineBankingSlovakia { issuer } + } + api_models::payments::BankRedirectData::OpenBankingUk { issuer, .. } => { + Self::OpenBankingUk { issuer } + } + api_models::payments::BankRedirectData::Przelewy24 { bank_name, .. } => { + Self::Przelewy24 { bank_name } + } + api_models::payments::BankRedirectData::Sofort { + preferred_language, .. + } => Self::Sofort { preferred_language }, + api_models::payments::BankRedirectData::Trustly { .. } => Self::Trustly {}, + api_models::payments::BankRedirectData::OnlineBankingFpx { issuer } => { + Self::OnlineBankingFpx { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingThailand { issuer } => { + Self::OnlineBankingThailand { issuer } + } + } + } +} + +impl From for CryptoData { + fn from(value: api_models::payments::CryptoData) -> Self { + let api_models::payments::CryptoData { pay_currency } = value; + Self { pay_currency } + } +} + +impl From for UpiData { + fn from(value: api_models::payments::UpiData) -> Self { + let api_models::payments::UpiData { vpa_id } = value; + Self { vpa_id } + } +} + +impl From for VoucherData { + fn from(value: api_models::payments::VoucherData) -> Self { + match value { + api_models::payments::VoucherData::Boleto(boleto_data) => { + Self::Boleto(Box::new(BoletoVoucherData { + social_security_number: boleto_data.social_security_number, + })) + } + api_models::payments::VoucherData::Alfamart(_) => { + Self::Alfamart(Box::new(AlfamartVoucherData {})) + } + api_models::payments::VoucherData::Indomaret(_) => { + Self::Indomaret(Box::new(IndomaretVoucherData {})) + } + api_models::payments::VoucherData::SevenEleven(_) + | api_models::payments::VoucherData::Lawson(_) + | api_models::payments::VoucherData::MiniStop(_) + | api_models::payments::VoucherData::FamilyMart(_) + | api_models::payments::VoucherData::Seicomart(_) + | api_models::payments::VoucherData::PayEasy(_) => { + Self::SevenEleven(Box::new(JCSVoucherData {})) + } + api_models::payments::VoucherData::Efecty => Self::Efecty, + api_models::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, + api_models::payments::VoucherData::RedCompra => Self::RedCompra, + api_models::payments::VoucherData::RedPagos => Self::RedPagos, + api_models::payments::VoucherData::Oxxo => Self::Oxxo, + } + } +} + +impl From for GiftCardData { + fn from(value: api_models::payments::GiftCardData) -> Self { + match value { + api_models::payments::GiftCardData::Givex(details) => Self::Givex(GiftCardDetails { + number: details.number, + cvc: details.cvc, + }), + api_models::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, + } + } +} + +impl From for CardToken { + fn from(value: api_models::payments::CardToken) -> Self { + let api_models::payments::CardToken { + card_holder_name, + card_cvc, + } = value; + Self { + card_holder_name, + card_cvc, + } + } +} + +impl From for BankDebitData { + fn from(value: api_models::payments::BankDebitData) -> Self { + match value { + api_models::payments::BankDebitData::AchBankDebit { + account_number, + routing_number, + bank_name, + bank_type, + bank_holder_type, + .. + } => Self::AchBankDebit { + account_number, + routing_number, + bank_name, + bank_type, + bank_holder_type, + }, + api_models::payments::BankDebitData::SepaBankDebit { iban, .. } => { + Self::SepaBankDebit { iban } + } + api_models::payments::BankDebitData::BecsBankDebit { + account_number, + bsb_number, + .. + } => Self::BecsBankDebit { + account_number, + bsb_number, + }, + api_models::payments::BankDebitData::BacsBankDebit { + account_number, + sort_code, + .. + } => Self::BacsBankDebit { + account_number, + sort_code, + }, + } + } +} + +impl From for BankTransferData { + fn from(value: api_models::payments::BankTransferData) -> Self { + match value { + api_models::payments::BankTransferData::AchBankTransfer { .. } => { + Self::AchBankTransfer {} + } + api_models::payments::BankTransferData::SepaBankTransfer { .. } => { + Self::SepaBankTransfer {} + } + api_models::payments::BankTransferData::BacsBankTransfer { .. } => { + Self::BacsBankTransfer {} + } + api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { + Self::MultibancoBankTransfer {} + } + api_models::payments::BankTransferData::PermataBankTransfer { .. } => { + Self::PermataBankTransfer {} + } + api_models::payments::BankTransferData::BcaBankTransfer { .. } => { + Self::BcaBankTransfer {} + } + api_models::payments::BankTransferData::BniVaBankTransfer { .. } => { + Self::BniVaBankTransfer {} + } + api_models::payments::BankTransferData::BriVaBankTransfer { .. } => { + Self::BriVaBankTransfer {} + } + api_models::payments::BankTransferData::CimbVaBankTransfer { .. } => { + Self::CimbVaBankTransfer {} + } + api_models::payments::BankTransferData::DanamonVaBankTransfer { .. } => { + Self::DanamonVaBankTransfer {} + } + api_models::payments::BankTransferData::MandiriVaBankTransfer { .. } => { + Self::MandiriVaBankTransfer {} + } + api_models::payments::BankTransferData::Pix {} => Self::Pix {}, + api_models::payments::BankTransferData::Pse {} => Self::Pse {}, + api_models::payments::BankTransferData::LocalBankTransfer { bank_code } => { + Self::LocalBankTransfer { bank_code } + } + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs new file mode 100644 index 000000000000..026a191977ce --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -0,0 +1,480 @@ +pub mod authentication; +pub mod fraud_check; +use api_models::payments::RequestSurchargeDetails; +use common_utils::{consts, errors, pii}; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use masking::Secret; +use serde::Serialize; +use serde_with::serde_as; + +use super::payment_method_data::PaymentMethodData; +use crate::{errors::api_error_response, mandates, payments, router_data}; +#[derive(Debug, Clone)] +pub struct PaymentsAuthorizeData { + pub payment_method_data: PaymentMethodData, + /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount) + /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately + /// ``` + /// get_original_amount() + /// get_surcharge_amount() + /// get_tax_on_surcharge_amount() + /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount + /// ``` + pub amount: i64, + pub email: Option, + pub customer_name: Option>, + pub currency: storage_enums::Currency, + pub confirm: bool, + pub statement_descriptor_suffix: Option, + pub statement_descriptor: Option, + pub capture_method: Option, + pub router_return_url: Option, + pub webhook_url: Option, + pub complete_authorize_url: Option, + // Mandates + pub setup_future_usage: Option, + pub mandate_id: Option, + pub off_session: Option, + pub customer_acceptance: Option, + pub setup_mandate_details: Option, + pub browser_info: Option, + pub order_details: Option>, + pub order_category: Option, + pub session_token: Option, + pub enrolled_for_3ds: bool, + pub related_transaction_id: Option, + pub payment_experience: Option, + pub payment_method_type: Option, + pub surcharge_details: Option, + pub customer_id: Option, + pub request_incremental_authorization: bool, + pub metadata: Option, + pub authentication_data: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct PaymentsCaptureData { + pub amount_to_capture: i64, + pub currency: storage_enums::Currency, + pub connector_transaction_id: String, + pub payment_amount: i64, + pub multiple_capture_data: Option, + pub connector_meta: Option, + pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. +} + +#[derive(Debug, Clone, Default)] +pub struct PaymentsIncrementalAuthorizationData { + pub total_amount: i64, + pub additional_amount: i64, + pub currency: storage_enums::Currency, + pub reason: Option, + pub connector_transaction_id: String, +} + +#[derive(Debug, Clone, Default)] +pub struct MultipleCaptureRequestData { + pub capture_sequence: i16, + pub capture_reference: String, +} + +#[derive(Debug, Clone)] +pub struct AuthorizeSessionTokenData { + pub amount_to_capture: Option, + pub currency: storage_enums::Currency, + pub connector_transaction_id: String, + pub amount: Option, +} + +#[derive(Debug, Clone)] +pub struct ConnectorCustomerData { + pub description: Option, + pub email: Option, + pub phone: Option>, + pub name: Option>, + pub preprocessing_id: Option, + pub payment_method_data: PaymentMethodData, +} + +#[derive(Debug, Clone)] +pub struct PaymentMethodTokenizationData { + pub payment_method_data: PaymentMethodData, + pub browser_info: Option, + pub currency: storage_enums::Currency, + pub amount: Option, +} + +#[derive(Debug, Clone)] +pub struct PaymentsPreProcessingData { + pub payment_method_data: Option, + pub amount: Option, + pub email: Option, + pub currency: Option, + pub payment_method_type: Option, + pub setup_mandate_details: Option, + pub capture_method: Option, + pub order_details: Option>, + pub router_return_url: Option, + pub webhook_url: Option, + pub complete_authorize_url: Option, + pub surcharge_details: Option, + pub browser_info: Option, + pub connector_transaction_id: Option, + pub redirect_response: Option, +} + +#[derive(Debug, Clone)] +pub struct CompleteAuthorizeData { + pub payment_method_data: Option, + pub amount: i64, + pub email: Option, + pub currency: storage_enums::Currency, + pub confirm: bool, + pub statement_descriptor_suffix: Option, + pub capture_method: Option, + // Mandates + pub setup_future_usage: Option, + pub mandate_id: Option, + pub off_session: Option, + pub setup_mandate_details: Option, + pub redirect_response: Option, + pub browser_info: Option, + pub connector_transaction_id: Option, + pub connector_meta: Option, + pub complete_authorize_url: Option, + pub metadata: Option, +} + +#[derive(Debug, Clone)] +pub struct CompleteAuthorizeRedirectResponse { + pub params: Option>, + pub payload: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsSyncData { + //TODO : add fields based on the connector requirements + pub connector_transaction_id: ResponseId, + pub encoded_data: Option, + pub capture_method: Option, + pub connector_meta: Option, + pub sync_type: SyncRequestType, + pub mandate_id: Option, + pub payment_method_type: Option, + pub currency: storage_enums::Currency, +} + +#[derive(Debug, Default, Clone)] +pub enum SyncRequestType { + MultipleCaptureSync(Vec), + #[default] + SinglePaymentSync, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsCancelData { + pub amount: Option, + pub currency: Option, + pub connector_transaction_id: String, + pub cancellation_reason: Option, + pub connector_meta: Option, + pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsRejectData { + pub amount: Option, + pub currency: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsApproveData { + pub amount: Option, + pub currency: Option, +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct BrowserInformation { + pub color_depth: Option, + pub java_enabled: Option, + pub java_script_enabled: Option, + pub language: Option, + pub screen_height: Option, + pub screen_width: Option, + pub time_zone: Option, + pub ip_address: Option, + pub accept_header: Option, + pub user_agent: Option, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub enum ResponseId { + ConnectorTransactionId(String), + EncodedData(String), + #[default] + NoResponseId, +} +impl ResponseId { + pub fn get_connector_transaction_id( + &self, + ) -> errors::CustomResult { + match self { + Self::ConnectorTransactionId(txn_id) => Ok(txn_id.to_string()), + _ => Err(errors::ValidationError::IncorrectValueProvided { + field_name: "connector_transaction_id", + }) + .attach_printable("Expected connector transaction ID not found"), + } + } +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SurchargeDetails { + /// original_amount + pub original_amount: common_utils::types::MinorUnit, + /// surcharge value + pub surcharge: common_utils::types::Surcharge, + /// tax on surcharge value + pub tax_on_surcharge: + Option>, + /// surcharge amount for this payment + pub surcharge_amount: common_utils::types::MinorUnit, + /// tax on surcharge amount for this payment + pub tax_on_surcharge_amount: common_utils::types::MinorUnit, + /// sum of original amount, + pub final_amount: common_utils::types::MinorUnit, +} + +impl SurchargeDetails { + pub fn is_request_surcharge_matching( + &self, + request_surcharge_details: RequestSurchargeDetails, + ) -> bool { + request_surcharge_details.surcharge_amount == self.surcharge_amount + && request_surcharge_details.tax_amount.unwrap_or_default() + == self.tax_on_surcharge_amount + } + pub fn get_total_surcharge_amount(&self) -> common_utils::types::MinorUnit { + self.surcharge_amount + self.tax_on_surcharge_amount + } +} + +impl + From<( + &RequestSurchargeDetails, + &payments::payment_attempt::PaymentAttempt, + )> for SurchargeDetails +{ + fn from( + (request_surcharge_details, payment_attempt): ( + &RequestSurchargeDetails, + &payments::payment_attempt::PaymentAttempt, + ), + ) -> Self { + let surcharge_amount = request_surcharge_details.surcharge_amount; + let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or_default(); + Self { + original_amount: payment_attempt.amount, + surcharge: common_utils::types::Surcharge::Fixed( + request_surcharge_details.surcharge_amount, + ), + tax_on_surcharge: None, + surcharge_amount, + tax_on_surcharge_amount, + final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount, + } + } +} + +#[derive(Debug, Clone)] +pub struct AuthenticationData { + pub eci: Option, + pub cavv: String, + pub threeds_server_transaction_id: String, + pub message_version: String, +} + +#[derive(Debug, Clone)] +pub struct RefundsData { + pub refund_id: String, + pub connector_transaction_id: String, + + pub connector_refund_id: Option, + pub currency: storage_enums::Currency, + /// Amount for the payment against which this refund is issued + pub payment_amount: i64, + pub reason: Option, + pub webhook_url: Option, + /// Amount to be refunded + pub refund_amount: i64, + /// Arbitrary metadata required for refund + pub connector_metadata: Option, + pub browser_info: Option, + /// Charges associated with the payment + pub charges: Option, +} + +#[derive(Debug, serde::Deserialize, Clone)] +pub struct ChargeRefunds { + pub charge_id: String, + pub transfer_account_id: String, + pub charge_type: api_models::enums::PaymentChargeType, + pub options: ChargeRefundsOptions, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub enum ChargeRefundsOptions { + Destination(DestinationChargeRefund), + Direct(DirectChargeRefund), +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DirectChargeRefund { + pub revert_platform_fee: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DestinationChargeRefund { + pub revert_platform_fee: bool, + pub revert_transfer: bool, +} + +#[derive(Debug, Clone)] +pub struct AccessTokenRequestData { + pub app_id: Secret, + pub id: Option>, + // Add more keys if required +} + +impl TryFrom for AccessTokenRequestData { + type Error = api_error_response::ApiErrorResponse; + fn try_from(connector_auth: router_data::ConnectorAuthType) -> Result { + match connector_auth { + router_data::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + app_id: api_key, + id: None, + }), + router_data::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + router_data::ConnectorAuthType::SignatureKey { api_key, key1, .. } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + router_data::ConnectorAuthType::MultiAuthKey { api_key, key1, .. } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + + _ => Err(api_error_response::ApiErrorResponse::InvalidDataValue { + field_name: "connector_account_details", + }), + } + } +} + +#[derive(Default, Debug, Clone)] +pub struct AcceptDisputeRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, +} + +#[derive(Default, Debug, Clone)] +pub struct DefendDisputeRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, +} + +#[derive(Default, Debug, Clone)] +pub struct SubmitEvidenceRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, + pub access_activity_log: Option, + pub billing_address: Option, + pub cancellation_policy: Option>, + pub cancellation_policy_provider_file_id: Option, + pub cancellation_policy_disclosure: Option, + pub cancellation_rebuttal: Option, + pub customer_communication: Option>, + pub customer_communication_provider_file_id: Option, + pub customer_email_address: Option, + pub customer_name: Option, + pub customer_purchase_ip: Option, + pub customer_signature: Option>, + pub customer_signature_provider_file_id: Option, + pub product_description: Option, + pub receipt: Option>, + pub receipt_provider_file_id: Option, + pub refund_policy: Option>, + pub refund_policy_provider_file_id: Option, + pub refund_policy_disclosure: Option, + pub refund_refusal_explanation: Option, + pub service_date: Option, + pub service_documentation: Option>, + pub service_documentation_provider_file_id: Option, + pub shipping_address: Option, + pub shipping_carrier: Option, + pub shipping_date: Option, + pub shipping_documentation: Option>, + pub shipping_documentation_provider_file_id: Option, + pub shipping_tracking_number: Option, + pub invoice_showing_distinct_transactions: Option>, + pub invoice_showing_distinct_transactions_provider_file_id: Option, + pub recurring_transaction_agreement: Option>, + pub recurring_transaction_agreement_provider_file_id: Option, + pub uncategorized_file: Option>, + pub uncategorized_file_provider_file_id: Option, + pub uncategorized_text: Option, +} + +#[derive(Clone, Debug)] +pub struct RetrieveFileRequestData { + pub provider_file_id: String, +} + +#[serde_as] +#[derive(Clone, Debug, serde::Serialize)] +pub struct UploadFileRequestData { + pub file_key: String, + #[serde(skip)] + pub file: Vec, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub file_type: mime::Mime, + pub file_size: i32, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Clone)] +pub struct PayoutsData { + pub payout_id: String, + pub amount: i64, + pub connector_payout_id: Option, + pub destination_currency: storage_enums::Currency, + pub source_currency: storage_enums::Currency, + pub payout_type: storage_enums::PayoutType, + pub entity_type: storage_enums::PayoutEntityType, + pub customer_details: Option, + pub vendor_details: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct CustomerDetails { + pub customer_id: Option, + pub name: Option>, + pub email: Option, + pub phone: Option>, + pub phone_country_code: Option, +} + +#[derive(Debug, Clone)] +pub struct VerifyWebhookSourceRequestData { + pub webhook_headers: actix_web::http::header::HeaderMap, + pub webhook_body: Vec, + pub merchant_secret: api_models::webhooks::ConnectorWebhookSecrets, +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs new file mode 100644 index 000000000000..1a554e8054d4 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs @@ -0,0 +1,182 @@ +use cards::CardNumber; +use common_utils::{ext_traits::OptionExt, pii::Email}; +use error_stack::{Report, ResultExt}; +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::api_error_response::ApiErrorResponse, payment_method_data::PaymentMethodData, + router_request_types::BrowserInformation, +}; + +#[derive(Debug, Clone)] +pub enum AuthenticationResponseData { + PreAuthNResponse { + threeds_server_transaction_id: String, + maximum_supported_3ds_version: common_utils::types::SemanticVersion, + connector_authentication_id: String, + three_ds_method_data: Option, + three_ds_method_url: Option, + message_version: common_utils::types::SemanticVersion, + connector_metadata: Option, + }, + AuthNResponse { + authn_flow_type: AuthNFlowType, + authentication_value: Option, + trans_status: common_enums::TransactionStatus, + }, + PostAuthNResponse { + trans_status: common_enums::TransactionStatus, + authentication_value: Option, + eci: Option, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ChallengeParams { + pub acs_url: Option, + pub challenge_request: Option, + pub acs_reference_number: Option, + pub acs_trans_id: Option, + pub three_dsserver_trans_id: Option, + pub acs_signed_content: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthNFlowType { + Challenge(Box), + Frictionless, +} + +impl AuthNFlowType { + pub fn get_acs_url(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_url.as_ref().map(ToString::to_string) + } else { + None + } + } + pub fn get_challenge_request(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.challenge_request.clone() + } else { + None + } + } + pub fn get_acs_reference_number(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_reference_number.clone() + } else { + None + } + } + pub fn get_acs_trans_id(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_trans_id.clone() + } else { + None + } + } + pub fn get_acs_signed_content(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_signed_content.clone() + } else { + None + } + } + pub fn get_decoupled_authentication_type(&self) -> common_enums::DecoupledAuthenticationType { + match self { + Self::Challenge(_) => common_enums::DecoupledAuthenticationType::Challenge, + Self::Frictionless => common_enums::DecoupledAuthenticationType::Frictionless, + } + } +} + +#[derive(Clone, Default, Debug)] +pub struct PreAuthNRequestData { + // card number + #[allow(dead_code)] + pub(crate) card_holder_account_number: CardNumber, +} + +#[derive(Clone, Debug)] +pub struct ConnectorAuthenticationRequestData { + pub payment_method_data: PaymentMethodData, + pub billing_address: api_models::payments::Address, + pub shipping_address: Option, + pub browser_details: Option, + pub amount: Option, + pub currency: Option, + pub message_category: MessageCategory, + pub device_channel: api_models::payments::DeviceChannel, + pub pre_authentication_data: PreAuthenticationData, + pub return_url: Option, + pub sdk_information: Option, + pub email: Option, + pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, + pub three_ds_requestor_url: String, + pub webhook_url: String, +} + +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] +pub enum MessageCategory { + Payment, + NonPayment, +} + +#[derive(Clone, Debug)] +pub struct ConnectorPostAuthenticationRequestData { + pub threeds_server_transaction_id: String, +} + +#[derive(Clone, Debug)] +pub struct PreAuthenticationData { + pub threeds_server_transaction_id: String, + pub message_version: common_utils::types::SemanticVersion, + pub acquirer_bin: Option, + pub acquirer_merchant_id: Option, + pub connector_metadata: Option, +} + +impl TryFrom<&diesel_models::authentication::Authentication> for PreAuthenticationData { + type Error = Report; + + fn try_from( + authentication: &diesel_models::authentication::Authentication, + ) -> Result { + let error_message = ApiErrorResponse::UnprocessableEntity { message: "Pre Authentication must be completed successfully before Authentication can be performed".to_string() }; + let threeds_server_transaction_id = authentication + .threeds_server_transaction_id + .clone() + .get_required_value("threeds_server_transaction_id") + .change_context(error_message.clone())?; + let message_version = authentication + .message_version + .clone() + .get_required_value("message_version") + .change_context(error_message)?; + Ok(Self { + threeds_server_transaction_id, + message_version, + acquirer_bin: authentication.acquirer_bin.clone(), + acquirer_merchant_id: authentication.acquirer_merchant_id.clone(), + connector_metadata: authentication.connector_metadata.clone(), + }) + } +} + +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct ThreeDsMethodData { + pub three_ds_method_data_submission: bool, + pub three_ds_method_data: String, + pub three_ds_method_url: Option, +} +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct AcquirerDetails { + pub acquirer_bin: String, + pub acquirer_merchant_id: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ExternalThreeDSConnectorMetadata { + pub pull_mechanism_for_external_3ds_enabled: Option, +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs b/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs new file mode 100644 index 000000000000..441d3a70b41f --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs @@ -0,0 +1,164 @@ +use api_models; +use common_enums; +use common_utils::{ + events::{ApiEventMetric, ApiEventsType}, + pii::Email, +}; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::router_request_types; +#[derive(Debug, Clone)] +pub struct FraudCheckSaleData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub email: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckCheckoutData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub browser_info: Option, + pub payment_method_data: Option, + pub email: Option, + pub gateway: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckTransactionData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub payment_method: Option, + pub error_code: Option, + pub error_message: Option, + pub connector_transaction_id: Option, + //The name of the payment gateway or financial institution that processed the transaction. + pub connector: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckRecordReturnData { + pub amount: i64, + pub currency: Option, + pub refund_method: RefundMethod, + pub refund_transaction_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RefundMethod { + StoreCredit, + OriginalPaymentInstrument, + NewPaymentInstrument, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckFulfillmentData { + pub amount: i64, + pub order_details: Option>>, + pub fulfillment_req: FrmFulfillmentRequest, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct FrmFulfillmentRequest { + ///unique payment_id for the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub payment_id: String, + ///unique order_id for the order_details in the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED + #[schema(value_type = Option, example = "COMPLETE")] + pub fulfillment_status: Option, + ///contains details of the fulfillment + #[schema(value_type = Vec)] + pub fulfillments: Vec, + //name of the tracking Company + #[schema(max_length = 255, example = "fedex")] + pub tracking_company: Option, + //tracking ID of the product + #[schema(example = r#"["track_8327446667", "track_8327446668"]"#)] + pub tracking_numbers: Option>, + //tracking_url for tracking the product + pub tracking_urls: Option>, + // The name of the Shipper. + pub carrier: Option, + // Fulfillment method for the shipment. + pub fulfillment_method: Option, + // Statuses to indicate shipment state. + pub shipment_status: Option, + // The date and time items are ready to be shipped. + pub shipped_at: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Fulfillments { + ///shipment_id of the shipped items + #[schema(max_length = 255, example = "ship_101")] + pub shipment_id: String, + ///products sent in the shipment + #[schema(value_type = Option>)] + pub products: Option>, + ///destination address of the shipment + #[schema(value_type = Destination)] + pub destination: Destination, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde(untagged)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub enum FulfillmentStatus { + PARTIAL, + COMPLETE, + REPLACEMENT, + CANCELED, +} + +#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Product { + pub item_name: String, + pub item_quantity: i64, + pub item_id: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Destination { + pub full_name: Secret, + pub organization: Option, + pub email: Option, + pub address: Address, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Address { + pub street_address: Secret, + pub unit: Option>, + pub postal_code: Secret, + pub city: String, + pub province_code: Secret, + pub country_code: common_enums::CountryAlpha2, +} + +impl ApiEventMetric for FrmFulfillmentRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::FraudCheck) + } +} diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index ed96cd9b5450..b32c8c23bd99 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -716,7 +716,6 @@ mod tests { api_enums::CardNetwork::Mastercard, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ - api_enums::Currency::USD, api_enums::Currency::INR, ])), accepted_countries: None, @@ -734,7 +733,6 @@ mod tests { ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::GBP, - api_enums::Currency::PHP, ])), accepted_countries: None, minimum_amount: Some(10), @@ -752,14 +750,6 @@ mod tests { status: api_enums::ConnectorStatus::Inactive, }; - let currency_country_flow_filter = kgraph_types::CurrencyCountryFlowFilter { - currency: Some(HashSet::from([api_enums::Currency::INR])), - country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), - not_available_flows: Some(kgraph_types::NotAvailableFlows { - capture_method: Some(api_enums::CaptureMethod::Manual), - }), - }; - let config_map = kgraph_types::CountryCurrencyFilter { connector_configs: HashMap::from([( api_enums::RoutableConnectors::Stripe, @@ -768,13 +758,31 @@ mod tests { kgraph_types::PaymentMethodFilterKey::PaymentMethodType( api_enums::PaymentMethodType::Credit, ), - currency_country_flow_filter.clone(), + kgraph_types::CurrencyCountryFlowFilter { + currency: Some(HashSet::from([ + api_enums::Currency::INR, + api_enums::Currency::USD, + ])), + country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), + not_available_flows: Some(kgraph_types::NotAvailableFlows { + capture_method: Some(api_enums::CaptureMethod::Manual), + }), + }, ), ( kgraph_types::PaymentMethodFilterKey::PaymentMethodType( api_enums::PaymentMethodType::Debit, ), - currency_country_flow_filter, + kgraph_types::CurrencyCountryFlowFilter { + currency: Some(HashSet::from([ + api_enums::Currency::GBP, + api_enums::Currency::PHP, + ])), + country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), + not_available_flows: Some(kgraph_types::NotAvailableFlows { + capture_method: Some(api_enums::CaptureMethod::Manual), + }), + }, ), ])), )]), @@ -838,8 +846,8 @@ mod tests { dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), - dirval!(CardNetwork = DinersClub), - dirval!(PaymentCurrency = GBP), + dirval!(CardNetwork = Maestro), + dirval!(PaymentCurrency = PHP), dirval!(PaymentAmount = 100), ]), &mut Memoization::new(), diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index bca1cbb64b87..528381061cf5 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -1,7 +1,7 @@ -#![allow(unused_variables)] use common_utils::errors::ErrorSwitch; +use hyperswitch_domain_models::errors::api_error_response as errors; -use crate::core::errors::{self, CustomersErrorResponse}; +use crate::core::errors::CustomersErrorResponse; #[derive(Debug, router_derive::ApiError, Clone)] #[error(error_type_enum = StripeErrorType)] @@ -481,11 +481,11 @@ impl From for StripeErrorCode { Self::PaymentIntentPaymentAttemptFailed { data } } errors::ApiErrorResponse::DisputeFailed { data } => Self::DisputeFailed { data }, - errors::ApiErrorResponse::InvalidCardData { data } => Self::InvalidCardType, // Maybe it is better to de generalize this router error - errors::ApiErrorResponse::CardExpired { data } => Self::ExpiredCard, - errors::ApiErrorResponse::RefundNotPossible { connector } => Self::RefundFailed, - errors::ApiErrorResponse::RefundFailed { data } => Self::RefundFailed, // Nothing at stripe to map - errors::ApiErrorResponse::PayoutFailed { data } => Self::PayoutFailed, + errors::ApiErrorResponse::InvalidCardData { data: _ } => Self::InvalidCardType, // Maybe it is better to de generalize this router error + errors::ApiErrorResponse::CardExpired { data: _ } => Self::ExpiredCard, + errors::ApiErrorResponse::RefundNotPossible { connector: _ } => Self::RefundFailed, + errors::ApiErrorResponse::RefundFailed { data: _ } => Self::RefundFailed, // Nothing at stripe to map + errors::ApiErrorResponse::PayoutFailed { data: _ } => Self::PayoutFailed, errors::ApiErrorResponse::MandateUpdateFailed | errors::ApiErrorResponse::MandateSerializationFailed @@ -605,7 +605,7 @@ impl From for StripeErrorCode { object: "poll".to_owned(), id, }, - errors::ApiErrorResponse::DisputeStatusValidationFailed { reason } => { + errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: _ } => { Self::InternalServerError } errors::ApiErrorResponse::FileValidationFailed { .. } => Self::FileValidationFailed, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 213492ac0d93..1fe86a209e3f 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -5672,23 +5672,32 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::from([ ( - "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, } ), ( - "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, + field_type: enums::FieldType::UserFullName, value: None, } - ) + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ]), } ), @@ -5726,14 +5735,23 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "card_holder_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } - ) + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ]), } ) @@ -5750,9 +5768,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.giropay.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -5856,9 +5874,10 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.giropay.country".to_string(), + ( + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -5869,14 +5888,23 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ) + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) ]), common: HashMap::new(), } @@ -5886,14 +5914,24 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ) + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) ]), common: HashMap::new(), } @@ -5986,9 +6024,9 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6100,9 +6138,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry{ options: vec![ @@ -6121,19 +6159,28 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + ( + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserFullName, + field_type: enums::FieldType::UserBillingName, value: None, } ), ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry{ options: vec![ @@ -6152,18 +6199,27 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } ), ( - "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "billing_email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -6244,9 +6300,9 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + ("billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6384,9 +6440,9 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), + ( "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6401,15 +6457,24 @@ impl Default for super::settings::RequiredFields { value: None, } ), - ( - "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ) + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) ]), common: HashMap::new(), } @@ -6427,18 +6492,27 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, } ), ( - "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserBillingName, value: None, @@ -6446,9 +6520,9 @@ impl Default for super::settings::RequiredFields { ) ]), non_mandate : HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), + ("billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6567,14 +6641,23 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } - ) + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ]), common: HashMap::new(), } @@ -6585,9 +6668,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "bank_account_country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6637,18 +6720,27 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } ), ( - "payment_method_data.bank_redirect.eps.country".to_string(), + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "bank_account_country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index 90cf465e8205..fffacc3ea4dd 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -96,6 +96,9 @@ impl three_ds_method_url, message_version: maximum_supported_3ds_version, connector_metadata: None, + directory_server_id: card_range + .as_ref() + .and_then(|card_range| card_range.directory_server_id.clone()), }, ) } diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 8c487f904f8a..9ffae8a02de6 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -1,6 +1,7 @@ use bigdecimal::ToPrimitive; use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::{self, ResultExt}; +pub use hyperswitch_domain_models::router_request_types::fraud_check::RefundMethod; use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -14,7 +15,7 @@ use crate::{ core::{errors, fraud_check::types as core_types}, types::{ self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, - ResponseId, ResponseRouterData, + transformers::ForeignFrom, ResponseId, ResponseRouterData, }, }; @@ -509,7 +510,7 @@ impl TryFrom<&frm_types::FrmFulfillmentRouterData> for FrmFulfillmentSignifydReq .fulfillment_status .as_ref() .map(|fulfillment_status| FulfillmentStatus::from(&fulfillment_status.clone())), - fulfillments: Vec::::from(&item.request.fulfillment_req), + fulfillments: Vec::::foreign_from(&item.request.fulfillment_req), }) } } @@ -525,8 +526,8 @@ impl From<&core_types::FulfillmentStatus> for FulfillmentStatus { } } -impl From<&core_types::FrmFulfillmentRequest> for Vec { - fn from(fulfillment_req: &core_types::FrmFulfillmentRequest) -> Self { +impl ForeignFrom<&core_types::FrmFulfillmentRequest> for Vec { + fn foreign_from(fulfillment_req: &core_types::FrmFulfillmentRequest) -> Self { fulfillment_req .fulfillments .iter() @@ -643,15 +644,6 @@ pub struct SignifydPaymentsRecordReturnRequest { refund: SignifydRefund, } -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum RefundMethod { - StoreCredit, - OriginalPaymentInstrument, - NewPaymentInstrument, -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde_with::skip_serializing_none] #[serde(rename_all = "camelCase")] diff --git a/crates/router/src/connector/threedsecureio/transformers.rs b/crates/router/src/connector/threedsecureio/transformers.rs index d6a77fef31eb..fe26e509c120 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -108,6 +108,7 @@ impl ) .change_context(errors::ConnectorError::ParsingFailed)?, connector_metadata: Some(connector_metadata), + directory_server_id: None, }, ) } diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 5fa2a68e3895..c44ae7afddfd 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -24,6 +24,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignTryFrom, ErrorResponse, Response, }, utils::BytesExt, @@ -225,7 +226,7 @@ impl ConnectorIntegration> for ResponseIdStr { } } -impl TryFrom> for types::ResponseId { +impl ForeignTryFrom> for types::ResponseId { type Error = error_stack::Report; - fn try_from(links: Option) -> Result { + fn foreign_try_from(links: Option) -> Result { get_resource_id(links, Self::ConnectorTransactionId) } } diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 61c4868bdba3..8a61f0f693f3 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -9,7 +9,9 @@ use crate::{ connector::utils, consts, core::errors, - types::{self, domain, PaymentsAuthorizeData, PaymentsResponseData}, + types::{ + self, domain, transformers::ForeignTryFrom, PaymentsAuthorizeData, PaymentsResponseData, + }, }; #[derive(Debug, Serialize)] @@ -245,7 +247,7 @@ impl TryFrom> }, description: item.response.description, response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::try_from(item.response.links)?, + resource_id: types::ResponseId::foreign_try_from(item.response.links)?, redirection_data: None, mandate_reference: None, connector_metadata: None, diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index e29c8b10232e..0152fb4321de 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -63,6 +63,7 @@ pub async fn update_trackers( three_ds_method_url, message_version, connector_metadata, + directory_server_id, } => storage::AuthenticationUpdate::PreAuthenticationUpdate { threeds_server_transaction_id, maximum_supported_3ds_version, @@ -77,6 +78,7 @@ pub async fn update_trackers( .map(|acquirer_details| acquirer_details.acquirer_bin.clone()), acquirer_merchant_id: acquirer_details .map(|acquirer_details| acquirer_details.acquirer_merchant_id), + directory_server_id, }, AuthenticationResponseData::AuthNResponse { authn_flow_type, @@ -181,6 +183,7 @@ pub async fn create_new_authentication( profile_id, payment_id, merchant_connector_id, + directory_server_id: None, }; state .store diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index a70a97e7bf0a..3a8cab8c5161 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -1,4 +1,3 @@ -pub mod api_error_response; pub mod customers_error_response; pub mod error_handlers; pub mod transformers; @@ -11,7 +10,10 @@ use std::fmt::Display; use actix_web::{body::BoxBody, ResponseError}; pub use common_utils::errors::{CustomResult, ParsingError, ValidationError}; use diesel_models::errors as storage_errors; -pub use hyperswitch_domain_models::errors::StorageError as DataStorageError; +pub use hyperswitch_domain_models::errors::{ + api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage}, + StorageError as DataStorageError, +}; pub use redis_interface::errors::RedisError; use scheduler::errors as sch_errors; use storage_impl::errors as storage_impl_errors; @@ -19,7 +21,6 @@ use storage_impl::errors as storage_impl_errors; pub use user::*; pub use self::{ - api_error_response::{ApiErrorResponse, NotImplementedMessage}, customers_error_response::CustomersErrorResponse, sch_errors::*, storage_errors::*, diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs deleted file mode 100644 index 0c492f0f089c..000000000000 --- a/crates/router/src/core/errors/api_error_response.rs +++ /dev/null @@ -1,328 +0,0 @@ -#![allow(dead_code, unused_variables)] - -use http::StatusCode; -use scheduler::errors::{PTError, ProcessTrackerError}; - -#[derive(Clone, Debug, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub enum ErrorType { - InvalidRequestError, - ObjectNotFound, - RouterError, - ProcessingError, - BadGateway, - ServerNotAvailable, - DuplicateRequest, - ValidationError, - ConnectorError, - LockTimeout, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, router_derive::ApiError)] -#[error(error_type_enum = ErrorType)] -pub enum ApiErrorResponse { - #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "{message:?}")] - NotImplemented { message: NotImplementedMessage }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_01", - message = "API key not provided or invalid API key used" - )] - Unauthorized, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL")] - InvalidRequestUrl, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "The HTTP method is not applicable for this API")] - InvalidHttpMethod, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "Missing required param: {field_name}")] - MissingRequiredField { field_name: &'static str }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_05", - message = "{field_name} contains invalid data. Expected format is {expected_format}" - )] - InvalidDataFormat { - field_name: String, - expected_format: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_06", message = "{message}")] - InvalidRequestData { message: String }, - /// Typically used when a field has invalid value, or deserialization of the value contained in a field fails. - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}")] - InvalidDataValue { field_name: &'static str }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret was not provided")] - ClientSecretNotGiven, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret has expired")] - ClientSecretExpired, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "The client_secret provided does not match the client_secret associated with the Payment")] - ClientSecretInvalid, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Customer has active mandate/subsciption")] - MandateActive, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_11", message = "Customer has already been redacted")] - CustomerRedacted, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")] - MaximumRefundCount, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")] - RefundAmountExceedsPaymentAmount, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")] - PaymentUnexpectedState { - current_flow: String, - field_name: String, - current_value: String, - states: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_15", message = "Invalid Ephemeral Key for the customer")] - InvalidEphemeralKey, - /// Typically used when information involving multiple fields or previously provided information doesn't satisfy a condition. - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_16", message = "{message}")] - PreconditionFailed { message: String }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_17", - message = "Access forbidden, invalid JWT token was used" - )] - InvalidJwtToken, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_18", - message = "{message}", - )] - GenericUnauthorized { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] - NotSupported { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_20", message = "{flow} flow not supported by the {connector} connector")] - FlowNotSupported { flow: String, connector: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_21", message = "Missing required params")] - MissingRequiredFields { field_names: Vec<&'static str> }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_22", message = "Access forbidden. Not authorized to access this resource {resource}")] - AccessForbidden { resource: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] - FileProviderNotSupported { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] - UnprocessableEntity { message: String }, - #[error( - error_type = ErrorType::ProcessingError, code = "IR_24", - message = "Invalid {wallet_name} wallet token" - )] - InvalidWalletToken { wallet_name: String }, - #[error(error_type = ErrorType::ConnectorError, code = "CE_00", message = "{code}: {message}", ignore = "status_code")] - ExternalConnectorError { - code: String, - message: String, - connector: String, - status_code: u16, - reason: Option, - }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed during authorization with connector. Retry payment")] - PaymentAuthorizationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed during authentication with connector. Retry payment")] - PaymentAuthenticationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector")] - PaymentCaptureFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_04", message = "The card data is invalid")] - InvalidCardData { data: Option }, - #[error(error_type = ErrorType::InvalidRequestError, code = "CE_04", message = "Payout validation failed")] - PayoutFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_05", message = "The card has expired")] - CardExpired { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_06", message = "Refund failed while processing with connector. Retry refund")] - RefundFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_07", message = "Verification failed while processing with connector. Retry operation")] - VerificationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_08", message = "Dispute operation failed while processing with connector. Retry operation")] - DisputeFailed { data: Option }, - #[error(error_type = ErrorType::ServerNotAvailable, code = "HE_00", message = "Something went wrong")] - InternalServerError, - #[error(error_type = ErrorType::LockTimeout, code = "HE_00", message = "Resource is busy. Please try again later.")] - ResourceBusy, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate refund request. Refund already attempted with the refund ID")] - DuplicateRefundRequest, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate mandate request. Mandate already attempted with the Mandate ID")] - DuplicateMandate, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant account with the specified details already exists in our records")] - DuplicateMerchantAccount, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")] - DuplicateMerchantConnectorAccount { - profile_id: String, - connector_label: String, - }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment method with the specified details already exists in our records")] - DuplicatePaymentMethod, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment with the specified payment_id already exists in our records")] - DuplicatePayment { payment_id: String }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payout with the specified payout_id '{payout_id}' already exists in our records")] - DuplicatePayout { payout_id: String }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The config with the specified key already exists in our records")] - DuplicateConfig, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Refund does not exist in our records")] - RefundNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Customer does not exist in our records")] - CustomerNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "RE_02", message = "Config key does not exist in our records.")] - ConfigNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment does not exist in our records")] - PaymentNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment method does not exist in our records")] - PaymentMethodNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant account does not exist in our records")] - MerchantAccountNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant connector account does not exist in our records")] - MerchantConnectorAccountNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Business profile with the given id '{id}' does not exist in our records")] - BusinessProfileNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Poll with the given id '{id}' does not exist in our records")] - PollNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")] - ResourceIdNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] - MandateNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")] - AuthenticationNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] - MandateUpdateFailed, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] - ApiKeyNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payout does not exist in our records")] - PayoutNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Event does not exist in our records")] - EventNotFound, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Invalid mandate id passed from connector")] - MandateSerializationFailed, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Unable to parse the mandate identifier passed from connector")] - MandateDeserializationFailed, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Return URL is not configured and not passed in payments request")] - ReturnUrlUnavailable, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard")] - RefundNotPossible { connector: String }, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Mandate Validation Failed" )] - MandateValidationFailed { reason: String }, - #[error(error_type= ErrorType::ValidationError, code = "HE_03", message = "The payment has not succeeded yet. Please pass a successful payment to initiate refund")] - PaymentNotSucceeded, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")] - MerchantConnectorAccountDisabled, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "{code}: {message}")] - PaymentBlockedError { - code: u16, - message: String, - status: String, - reason: String, - }, - #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")] - SuccessfulPaymentNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")] - IncorrectConnectorNameGiven, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Address does not exist in our records")] - AddressNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Dispute does not exist in our records")] - DisputeNotFound { dispute_id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File does not exist in our records")] - FileNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File not available")] - FileNotAvailable, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute status validation failed")] - DisputeStatusValidationFailed { reason: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Card with the provided iin does not exist")] - InvalidCardIin, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "The provided card IIN length is invalid, please provide an iin with 6 or 8 digits")] - InvalidCardIinLength, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "File validation failed")] - FileValidationFailed { reason: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File not found / valid in the request")] - MissingFile, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute id not found in the request")] - MissingDisputeId, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File purpose not found in the request or is invalid")] - MissingFilePurpose, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File content type not found / valid")] - MissingFileContentType, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_05", message = "{message}")] - GenericNotFoundError { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_01", message = "{message}")] - GenericDuplicateError { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] - WebhookAuthenticationFailed, - #[error(error_type = ErrorType::ObjectNotFound, code = "WE_04", message = "Webhook resource not found")] - WebhookResourceNotFound, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] - WebhookBadRequest, - #[error(error_type = ErrorType::RouterError, code = "WE_03", message = "There was some issue processing the webhook")] - WebhookProcessingFailure, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "required payment method is not configured or configured incorrectly for all configured connectors")] - IncorrectPaymentMethodConfiguration, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Unable to process the webhook body")] - WebhookUnprocessableEntity, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment Link does not exist in our records")] - PaymentLinkNotFound, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Merchant Secret set my merchant for webhook source verification is invalid")] - WebhookInvalidMerchantSecret, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] - CurrencyNotSupported { message: String }, - #[error(error_type = ErrorType::ServerNotAvailable, code= "HE_00", message = "{component} health check is failing with error: {message}")] - HealthCheckError { - component: &'static str, - message: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] - InvalidConnectorConfiguration { config: String }, - #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] - CurrencyConversionFailed, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] - PaymentMethodDeleteFailed, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_26", - message = "Invalid Cookie" - )] - InvalidCookie, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_27", message = "Extended card info does not exist")] - ExtendedCardInfoNotFound, -} - -impl PTError for ApiErrorResponse { - fn to_pt_error(&self) -> ProcessTrackerError { - ProcessTrackerError::EApiErrorResponse - } -} - -#[derive(Clone)] -pub enum NotImplementedMessage { - Reason(String), - Default, -} - -impl std::fmt::Debug for NotImplementedMessage { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Reason(message) => write!(fmt, "{message} is not implemented"), - Self::Default => { - write!( - fmt, - "This API is under development and will be made available soon." - ) - } - } - } -} - -impl ::core::fmt::Display for ApiErrorResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - r#"{{"error":{}}}"#, - serde_json::to_string(self).unwrap_or_else(|_| "API error response".to_string()) - ) - } -} - -impl actix_web::ResponseError for ApiErrorResponse { - fn status_code(&self) -> StatusCode { - common_utils::errors::ErrorSwitch::::switch( - self, - ) - .status_code() - } - - fn error_response(&self) -> actix_web::HttpResponse { - common_utils::errors::ErrorSwitch::::switch( - self, - ) - .error_response() - } -} - -impl crate::services::EmbedError for error_stack::Report {} diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index 880c0d7b20b1..df529f818034 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -1,312 +1,7 @@ -use api_models::errors::types::Extra; use common_utils::errors::ErrorSwitch; -use http::StatusCode; +use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; -use super::{ApiErrorResponse, ConnectorError, CustomersErrorResponse, StorageError}; - -impl ErrorSwitch for ApiErrorResponse { - fn switch(&self) -> api_models::errors::types::ApiErrorResponse { - use api_models::errors::types::{ApiError, ApiErrorResponse as AER}; - - match self { - Self::NotImplemented { message } => { - AER::NotImplemented(ApiError::new("IR", 0, format!("{message:?}"), None)) - } - Self::Unauthorized => AER::Unauthorized(ApiError::new( - "IR", - 1, - "API key not provided or invalid API key used", None - )), - Self::InvalidRequestUrl => { - AER::NotFound(ApiError::new("IR", 2, "Unrecognized request URL", None)) - } - Self::InvalidHttpMethod => AER::MethodNotAllowed(ApiError::new( - "IR", - 3, - "The HTTP method is not applicable for this API", None - )), - Self::MissingRequiredField { field_name } => AER::BadRequest( - ApiError::new("IR", 4, format!("Missing required param: {field_name}"), None), - ), - Self::InvalidDataFormat { - field_name, - expected_format, - } => AER::Unprocessable(ApiError::new( - "IR", - 5, - format!( - "{field_name} contains invalid data. Expected format is {expected_format}" - ), None - )), - Self::InvalidRequestData { message } => { - AER::Unprocessable(ApiError::new("IR", 6, message.to_string(), None)) - } - Self::InvalidDataValue { field_name } => AER::BadRequest(ApiError::new( - "IR", - 7, - format!("Invalid value provided: {field_name}"), None - )), - Self::ClientSecretNotGiven => AER::BadRequest(ApiError::new( - "IR", - 8, - "client_secret was not provided", None - )), - Self::ClientSecretInvalid => { - AER::BadRequest(ApiError::new("IR", 9, "The client_secret provided does not match the client_secret associated with the Payment", None)) - } - Self::CurrencyNotSupported { message } => { - AER::BadRequest(ApiError::new("IR", 9, message, None)) - } - Self::MandateActive => { - AER::BadRequest(ApiError::new("IR", 10, "Customer has active mandate/subsciption", None)) - } - Self::CustomerRedacted => { - AER::BadRequest(ApiError::new("IR", 11, "Customer has already been redacted", None)) - } - Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)), - Self::RefundAmountExceedsPaymentAmount => { - AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None)) - } - Self::PaymentUnexpectedState { - current_flow, - field_name, - current_value, - states, - } => AER::BadRequest(ApiError::new("IR", 14, format!("This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}"), None)), - Self::InvalidEphemeralKey => AER::Unauthorized(ApiError::new("IR", 15, "Invalid Ephemeral Key for the customer", None)), - Self::PreconditionFailed { message } => { - AER::BadRequest(ApiError::new("IR", 16, message.to_string(), None)) - } - Self::InvalidJwtToken => AER::Unauthorized(ApiError::new("IR", 17, "Access forbidden, invalid JWT token was used", None)), - Self::GenericUnauthorized { message } => { - AER::Unauthorized(ApiError::new("IR", 18, message.to_string(), None)) - }, - Self::ClientSecretExpired => AER::BadRequest(ApiError::new( - "IR", - 19, - "The provided client_secret has expired", None - )), - Self::MissingRequiredFields { field_names } => AER::BadRequest( - ApiError::new("IR", 21, "Missing required params".to_string(), Some(Extra {data: Some(serde_json::json!(field_names)), ..Default::default() })), - ), - Self::AccessForbidden {resource} => { - AER::ForbiddenCommonResource(ApiError::new("IR", 22, format!("Access forbidden. Not authorized to access this resource {resource}"), None)) - }, - Self::FileProviderNotSupported { message } => { - AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None)) - }, - Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 23, message.to_string(), None)), - Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new( - "IR", - 24, - format!("Invalid {wallet_name} wallet token"), None - )), - Self::ExternalConnectorError { - code, - message, - connector, - reason, - status_code, - } => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.to_owned().map(Into::into), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)), - Self::PaymentAuthorizationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 1, "Payment failed during authorization with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::PaymentAuthenticationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 2, "Payment failed during authentication with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::PaymentCaptureFailed { data } => { - AER::BadRequest(ApiError::new("CE", 3, "Capture attempt failed while processing with connector", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::DisputeFailed { data } => { - AER::BadRequest(ApiError::new("CE", 1, "Dispute operation failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::InvalidCardData { data } => AER::BadRequest(ApiError::new("CE", 4, "The card data is invalid", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::CardExpired { data } => AER::BadRequest(ApiError::new("CE", 5, "The card has expired", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::RefundFailed { data } => AER::BadRequest(ApiError::new("CE", 6, "Refund failed while processing with connector. Retry refund", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::VerificationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 7, "Verification failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) - }, - Self::MandateUpdateFailed | Self::MandateSerializationFailed | Self::MandateDeserializationFailed | Self::InternalServerError => { - AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None)) - }, - Self::HealthCheckError { message,component } => { - AER::InternalServerError(ApiError::new("HE",0,format!("{} health check failed with error: {}",component,message),None)) - }, - Self::PayoutFailed { data } => { - AER::BadRequest(ApiError::new("CE", 4, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) - }, - Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)), - Self::DuplicateMandate => AER::BadRequest(ApiError::new("HE", 1, "Duplicate mandate request. Mandate already attempted with the Mandate ID", None)), - Self::DuplicateMerchantAccount => AER::BadRequest(ApiError::new("HE", 1, "The merchant account with the specified details already exists in our records", None)), - Self::DuplicateMerchantConnectorAccount { profile_id, connector_label: connector_name } => { - AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_name}' already exists in our records"), None)) - } - Self::DuplicatePaymentMethod => AER::BadRequest(ApiError::new("HE", 1, "The payment method with the specified details already exists in our records", None)), - Self::DuplicatePayment { payment_id } => { - AER::BadRequest(ApiError::new("HE", 1, "The payment with the specified payment_id already exists in our records", Some(Extra {reason: Some(format!("{payment_id} already exists")), ..Default::default()}))) - } - Self::DuplicatePayout { payout_id } => { - AER::BadRequest(ApiError::new("HE", 1, format!("The payout with the specified payout_id '{payout_id}' already exists in our records"), None)) - } - Self::GenericDuplicateError { message } => { - AER::BadRequest(ApiError::new("HE", 1, message, None)) - } - Self::RefundNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Refund does not exist in our records.", None)) - } - Self::CustomerNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Customer does not exist in our records", None)) - } - Self::ConfigNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Config key does not exist in our records.", None)) - }, - Self::DuplicateConfig => { - AER::BadRequest(ApiError::new("HE", 1, "The config with the specified key already exists in our records", None)) - } - Self::PaymentNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment does not exist in our records", None)) - } - Self::PaymentMethodNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment method does not exist in our records", None)) - } - Self::MerchantAccountNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Merchant account does not exist in our records", None)) - } - Self::MerchantConnectorAccountNotFound {id } => { - AER::NotFound(ApiError::new("HE", 2, "Merchant connector account does not exist in our records", Some(Extra {reason: Some(format!("{id} does not exist")), ..Default::default()}))) - } - Self::MerchantConnectorAccountDisabled => { - AER::BadRequest(ApiError::new("HE", 3, "The selected merchant connector account is disabled", None)) - } - Self::ResourceIdNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Resource ID does not exist in our records", None)) - } - Self::MandateNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Mandate does not exist in our records", None)) - } - Self::PayoutNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payout does not exist in our records", None)) - } - Self::EventNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Event does not exist in our records", None)) - } - Self::ReturnUrlUnavailable => AER::NotFound(ApiError::new("HE", 3, "Return URL is not configured and not passed in payments request", None)), - Self::RefundNotPossible { connector } => { - AER::BadRequest(ApiError::new("HE", 3, format!("This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard"), None)) - } - Self::MandateValidationFailed { reason } => { - AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.to_owned()), ..Default::default() }))) - } - Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)), - Self::PaymentBlockedError { - message, - reason, - .. - } => AER::DomainError(ApiError::new("HE", 3, message, Some(Extra { reason: Some(reason.clone()), ..Default::default() }))), - Self::SuccessfulPaymentNotFound => { - AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None)) - } - Self::IncorrectConnectorNameGiven => { - AER::NotFound(ApiError::new("HE", 4, "The connector provided in the request is incorrect or not available", None)) - } - Self::AddressNotFound => { - AER::NotFound(ApiError::new("HE", 4, "Address does not exist in our records", None)) - }, - Self::GenericNotFoundError { message } => { - AER::NotFound(ApiError::new("HE", 5, message, None)) - }, - Self::ApiKeyNotFound => { - AER::NotFound(ApiError::new("HE", 2, "API Key does not exist in our records", None)) - } - Self::NotSupported { message } => { - AER::BadRequest(ApiError::new("HE", 3, "Payment method type not supported", Some(Extra {reason: Some(message.to_owned()), ..Default::default()}))) - }, - Self::InvalidCardIin => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN does not exist", None)), - Self::InvalidCardIinLength => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN length is invalid, please provide an IIN with 6 digits", None)), - Self::FlowNotSupported { flow, connector } => { - AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message - } - Self::DisputeNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) - }, - Self::AuthenticationNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None)) - }, - Self::BusinessProfileNotFound { id } => { - AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) - } - Self::FileNotFound => { - AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) - } - Self::PollNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Poll does not exist in our records", None)) - }, - Self::FileNotAvailable => { - AER::NotFound(ApiError::new("HE", 2, "File not available", None)) - } - Self::DisputeStatusValidationFailed { .. } => { - AER::BadRequest(ApiError::new("HE", 2, "Dispute status validation failed", None)) - } - Self::FileValidationFailed { reason } => { - AER::BadRequest(ApiError::new("HE", 2, format!("File validation failed {reason}"), None)) - } - Self::MissingFile => { - AER::BadRequest(ApiError::new("HE", 2, "File not found in the request", None)) - } - Self::MissingFilePurpose => { - AER::BadRequest(ApiError::new("HE", 2, "File purpose not found in the request or is invalid", None)) - } - Self::MissingFileContentType => { - AER::BadRequest(ApiError::new("HE", 2, "File content type not found", None)) - } - Self::MissingDisputeId => { - AER::BadRequest(ApiError::new("HE", 2, "Dispute id not found in the request", None)) - } - Self::WebhookAuthenticationFailed => { - AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) - } - Self::WebhookResourceNotFound => { - AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None)) - } - Self::WebhookBadRequest => { - AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None)) - } - Self::WebhookProcessingFailure => { - AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None)) - }, - Self::WebhookInvalidMerchantSecret => { - AER::BadRequest(ApiError::new("WE", 2, "Merchant Secret set for webhook source verificartion is invalid", None)) - } - Self::IncorrectPaymentMethodConfiguration => { - AER::BadRequest(ApiError::new("HE", 4, "No eligible connector was found for the current payment method configuration", None)) - } - Self::WebhookUnprocessableEntity => { - AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) - }, - Self::ResourceBusy => { - AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) - } - Self::PaymentLinkNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment Link does not exist in our records", None)) - } - Self::InvalidConnectorConfiguration {config} => { - AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) - } - Self::CurrencyConversionFailed => { - AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) - } - Self::PaymentMethodDeleteFailed => { - AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) - } - Self::InvalidCookie => { - AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None)) - } - Self::ExtendedCardInfoNotFound => { - AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None)) - } - } - } -} +use super::{ConnectorError, CustomersErrorResponse, StorageError}; impl ErrorSwitch for ConnectorError { fn switch(&self) -> ApiErrorResponse { diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index 678a351a4c31..1e2f5a2c3057 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -158,9 +158,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( - reason.to_string(), - ), + message: errors::NotImplementedMessage::Reason(reason.to_string()), } .into() } @@ -249,7 +247,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( reason.to_string(), ), } @@ -476,9 +474,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( - reason.to_string(), - ), + message: errors::NotImplementedMessage::Reason(reason.to_string()), } } _ => errors::ApiErrorResponse::InternalServerError, diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index d2a9b9204573..bc30ad83b145 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -5,20 +5,20 @@ use api_models::{ refunds::RefundResponse, }; use common_enums::FrmSuggestion; -use common_utils::pii::{Email, SecretSerdeValue}; +use common_utils::pii::SecretSerdeValue; use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; +pub use hyperswitch_domain_models::router_request_types::fraud_check::{ + Address, Destination, FrmFulfillmentRequest, FulfillmentStatus, Fulfillments, Product, +}; use masking::Serialize; use serde::Deserialize; use utoipa::ToSchema; use super::operation::BoxedFraudCheckOperation; -use crate::{ - pii::Secret, - types::{ - domain::MerchantAccount, - storage::{enums as storage_enums, fraud_check::FraudCheck}, - PaymentAddress, - }, +use crate::types::{ + domain::MerchantAccount, + storage::{enums as storage_enums, fraud_check::FraudCheck}, + PaymentAddress, }; #[derive(Clone, Default, Debug)] @@ -106,98 +106,6 @@ pub struct FrmFulfillmentSignifydApiRequest { pub fulfillments: Vec, } -#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] -#[serde(deny_unknown_fields)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct FrmFulfillmentRequest { - ///unique payment_id for the transaction - #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] - pub payment_id: String, - ///unique order_id for the order_details in the transaction - #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] - pub order_id: String, - ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED - #[schema(value_type = Option, example = "COMPLETE")] - pub fulfillment_status: Option, - ///contains details of the fulfillment - #[schema(value_type = Vec)] - pub fulfillments: Vec, - //name of the tracking Company - #[schema(max_length = 255, example = "fedex")] - pub tracking_company: Option, - //tracking ID of the product - #[schema(example = r#"["track_8327446667", "track_8327446668"]"#)] - pub tracking_numbers: Option>, - //tracking_url for tracking the product - pub tracking_urls: Option>, - // The name of the Shipper. - pub carrier: Option, - // Fulfillment method for the shipment. - pub fulfillment_method: Option, - // Statuses to indicate shipment state. - pub shipment_status: Option, - // The date and time items are ready to be shipped. - pub shipped_at: Option, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Fulfillments { - ///shipment_id of the shipped items - #[schema(max_length = 255, example = "ship_101")] - pub shipment_id: String, - ///products sent in the shipment - #[schema(value_type = Option>)] - pub products: Option>, - ///destination address of the shipment - #[schema(value_type = Destination)] - pub destination: Destination, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde(untagged)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub enum FulfillmentStatus { - PARTIAL, - COMPLETE, - REPLACEMENT, - CANCELED, -} - -#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Product { - pub item_name: String, - pub item_quantity: i64, - pub item_id: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Destination { - pub full_name: Secret, - pub organization: Option, - pub email: Option, - pub address: Address, -} - -#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Address { - pub street_address: Secret, - pub unit: Option>, - pub postal_code: Secret, - pub city: String, - pub province_code: Secret, - pub country_code: common_enums::CountryAlpha2, -} - #[derive(Debug, ToSchema, Clone, Serialize)] pub struct FrmFulfillmentResponse { ///unique order_id for the transaction diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 657ad6250458..28121ecc8010 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3525,7 +3525,22 @@ pub async fn list_customer_payment_method( ) .await .attach_printable("unable to decrypt payment method billing address details")?; - + let connector_mandate_details = pm + .connector_mandate_details + .clone() + .map(|val| { + val.parse_value::("PaymentsMandateReference") + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; + let mca_enabled = get_mca_status( + state, + &key_store, + &merchant_account.merchant_id, + connector_mandate_details, + ) + .await?; // Need validation for enabled payment method ,querying MCA let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.to_owned(), @@ -3537,7 +3552,7 @@ pub async fn list_customer_payment_method( card: payment_method_retrieval_context.card_details, metadata: pm.metadata, payment_method_issuer_code: pm.payment_method_issuer_code, - recurring_enabled: false, + recurring_enabled: mca_enabled, installment_payment_enabled: false, payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), created: Some(pm.created_at), @@ -3667,7 +3682,38 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } +pub async fn get_mca_status( + state: &routes::AppState, + key_store: &domain::MerchantKeyStore, + merchant_id: &str, + connector_mandate_details: Option, +) -> errors::RouterResult { + if let Some(connector_mandate_details) = connector_mandate_details { + let mcas = state + .store + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + merchant_id, + true, + key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_id.to_string(), + })?; + let mut mca_ids = HashSet::new(); + for mca in mcas { + mca_ids.insert(mca.merchant_connector_id); + } + + for mca_id in connector_mandate_details.keys() { + if !mca_ids.contains(mca_id) { + return Ok(true); + } + } + } + Ok(false) +} pub async fn decrypt_generic_data( data: Option, key: &[u8], diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 175400c54bf1..a6befa5269ef 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -34,10 +34,11 @@ use error_stack::{report, ResultExt}; use events::EventInfo; use futures::future::join_all; use helpers::ApplePayData; -use hyperswitch_domain_models::{ +pub use hyperswitch_domain_models::{ mandates::{CustomerAcceptance, MandateData}, payment_address::PaymentAddress, router_data::RouterData, + router_request_types::CustomerDetails, }; use masking::{ExposeInterface, Secret}; use redis_interface::errors::RedisError; @@ -2488,15 +2489,6 @@ pub struct IncrementalAuthorizationDetails { pub authorization_id: Option, } -#[derive(Debug, Default, Clone)] -pub struct CustomerDetails { - pub customer_id: Option, - pub name: Option>, - pub email: Option, - pub phone: Option>, - pub phone_country_code: Option, -} - pub trait CustomerDetailsExt { type Error; fn get_name(&self) -> Result, Self::Error>; @@ -3170,6 +3162,28 @@ where { routing_data.business_sub_label = choice.sub_label.clone(); } + + if payment_data.payment_attempt.payment_method_type + == Some(storage_enums::PaymentMethodType::ApplePay) + { + let retryable_connector_data = helpers::get_apple_pay_retryable_connectors( + state, + merchant_account, + payment_data, + key_store, + connector_data.clone(), + #[cfg(feature = "connector_choice_mca_id")] + choice.merchant_connector_id.clone().as_ref(), + #[cfg(not(feature = "connector_choice_mca_id"))] + None, + ) + .await?; + + if let Some(connector_data_list) = retryable_connector_data { + return Ok(ConnectorCallType::Retryable(connector_data_list)); + } + } + return Ok(ConnectorCallType::PreDetermined(connector_data)); } } diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index 92f815f6b5f4..95ca0c3e31c7 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use super::{ConstructFlowSpecificData, Feature}; use crate::{ core::{ - errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, }, routes::AppState, diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index 726993aa7fae..638efa054eb8 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use super::{ConstructFlowSpecificData, Feature}; use crate::{ core::{ - errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, }, routes::AppState, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 2eb0c921bbf2..76ff96c70215 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -122,36 +122,6 @@ fn is_dynamic_fields_required( .unwrap_or(false) } -fn get_applepay_metadata( - connector_metadata: Option, -) -> RouterResult { - connector_metadata - .clone() - .parse_value::( - "ApplepayCombinedSessionTokenData", - ) - .map(|combined_metadata| { - api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( - combined_metadata.apple_pay_combined, - ) - }) - .or_else(|_| { - connector_metadata - .parse_value::( - "ApplepaySessionTokenData", - ) - .map(|old_metadata| { - api_models::payments::ApplepaySessionTokenMetadata::ApplePay( - old_metadata.apple_pay, - ) - }) - }) - .change_context(errors::ApiErrorResponse::InvalidDataFormat { - field_name: "connector_metadata".to_string(), - expected_format: "applepay_metadata_format".to_string(), - }) -} - fn build_apple_pay_session_request( state: &routes::AppState, request: payment_types::ApplepaySessionRequest, @@ -196,7 +166,8 @@ async fn create_applepay_session_token( ) } else { // Get the apple pay metadata - let apple_pay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; + let apple_pay_metadata = + helpers::get_applepay_metadata(router_data.connector_meta_data.clone())?; // Get payment request data , apple pay session request and merchant keys let ( @@ -213,6 +184,8 @@ async fn create_applepay_session_token( payment_request_data, session_token_data, } => { + logger::info!("Apple pay simplified flow"); + let merchant_identifier = state .conf .applepay_merchant_configs @@ -254,6 +227,8 @@ async fn create_applepay_session_token( payment_request_data, session_token_data, } => { + logger::info!("Apple pay manual flow"); + let apple_pay_session_request = get_session_request_for_manual_apple_pay(session_token_data.clone()); @@ -269,6 +244,8 @@ async fn create_applepay_session_token( } }, payment_types::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => { + logger::info!("Apple pay manual flow"); + let apple_pay_session_request = get_session_request_for_manual_apple_pay( apple_pay_metadata.session_token_data.clone(), ); diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 415482ddb18c..81751e62f8b2 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3892,6 +3892,122 @@ pub fn validate_customer_access( Ok(()) } +pub fn is_apple_pay_simplified_flow( + connector_metadata: Option, +) -> CustomResult { + let apple_pay_metadata = get_applepay_metadata(connector_metadata)?; + + Ok(match apple_pay_metadata { + api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( + apple_pay_combined_metadata, + ) => match apple_pay_combined_metadata { + api_models::payments::ApplePayCombinedMetadata::Simplified { .. } => true, + api_models::payments::ApplePayCombinedMetadata::Manual { .. } => false, + }, + api_models::payments::ApplepaySessionTokenMetadata::ApplePay(_) => false, + }) +} + +pub fn get_applepay_metadata( + connector_metadata: Option, +) -> RouterResult { + connector_metadata + .clone() + .parse_value::( + "ApplepayCombinedSessionTokenData", + ) + .map(|combined_metadata| { + api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( + combined_metadata.apple_pay_combined, + ) + }) + .or_else(|_| { + connector_metadata + .parse_value::( + "ApplepaySessionTokenData", + ) + .map(|old_metadata| { + api_models::payments::ApplepaySessionTokenMetadata::ApplePay( + old_metadata.apple_pay, + ) + }) + }) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_metadata".to_string(), + expected_format: "applepay_metadata_format".to_string(), + }) +} + +pub async fn get_apple_pay_retryable_connectors( + state: AppState, + merchant_account: &domain::MerchantAccount, + payment_data: &mut PaymentData, + key_store: &domain::MerchantKeyStore, + decided_connector_data: api::ConnectorData, + merchant_connector_id: Option<&String>, +) -> CustomResult>, errors::ApiErrorResponse> +where + F: Send + Clone, +{ + let profile_id = &payment_data + .payment_intent + .profile_id + .clone() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "profile_id", + })?; + + let merchant_connector_account = get_merchant_connector_account( + &state, + merchant_account.merchant_id.as_str(), + payment_data.creds_identifier.to_owned(), + key_store, + profile_id, // need to fix this + &decided_connector_data.connector_name.to_string(), + merchant_connector_id, + ) + .await? + .get_metadata(); + + let connector_data_list = if is_apple_pay_simplified_flow(merchant_connector_account)? { + let merchant_connector_account_list = state + .store + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + merchant_account.merchant_id.as_str(), + true, + key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?; + + let mut connector_data_list = vec![decided_connector_data.clone()]; + + for merchant_connector_account in merchant_connector_account_list { + if is_apple_pay_simplified_flow(merchant_connector_account.metadata)? { + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &merchant_connector_account.connector_name.to_string(), + api::GetToken::Connector, + Some(merchant_connector_account.merchant_connector_id), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Invalid connector name received")?; + + if !connector_data_list.iter().any(|connector_details| { + connector_details.merchant_connector_id == connector_data.merchant_connector_id + }) { + connector_data_list.push(connector_data) + } + } + } + Some(connector_data_list) + } else { + None + }; + Ok(connector_data_list) +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ApplePayData { version: masking::Secret, @@ -4044,6 +4160,8 @@ impl ApplePayData { &self, symmetric_key: &[u8], ) -> CustomResult { + logger::info!("Decrypt apple pay token"); + let data = BASE64_ENGINE .decode(self.data.peek().as_bytes()) .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 8dbef81b9063..a3ed16def0c7 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -3,7 +3,8 @@ use std::marker::PhantomData; use api_models::{ admin::ExtendedCardInfoConfig, enums::FrmSuggestion, - payments::{AdditionalCardInfo, AdditionalPaymentData, ExtendedCardInfo}, + payment_methods::PaymentMethodsData, + payments::{AdditionalPaymentData, ExtendedCardInfo}, }; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, StringExt, ValueExt}; @@ -21,7 +22,6 @@ use crate::{ blocklist::utils as blocklist_utils, errors::{self, CustomResult, RouterResult, StorageErrorExt}, mandate::helpers as m_helpers, - payment_methods::cards, payments::{ self, helpers, operations, populate_surcharge_details, CustomerDetails, PaymentAddress, PaymentData, @@ -34,7 +34,7 @@ use crate::{ types::{ self, api::{self, ConnectorCallType, PaymentIdTypeExt}, - domain, + domain::{self, types::decrypt}, storage::{self, enums as storage_enums}, }, utils::{self, OptionExt}, @@ -1040,26 +1040,39 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to encode additional pm data")?; - let key = key_store.key.get_inner().peek(); + let encode_additional_pm_to_value = if let Some(ref pm) = payment_data.payment_method_info { + let key = key_store.key.get_inner().peek(); - let card_detail_from_locker = payment_data - .payment_method_info - .as_ref() - .async_map(|pm| async move { - cards::get_card_details_without_locker_fallback(pm, key, state).await + let card_detail_from_locker: Option = + decrypt::( + pm.payment_method_data.clone(), + key, + ) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("unable to decrypt card details") + .ok() + .flatten() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), + _ => None, + }); + + card_detail_from_locker.and_then(|card_details| { + let additional_data = card_details.into(); + let additional_data_payment = + AdditionalPaymentData::Card(Box::new(additional_data)); + additional_data_payment + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode additional pm data") + .ok() }) - .await - .transpose()?; - - let additional_data: Option = card_detail_from_locker.map(From::from); - - let encode_additional_pm_to_value = additional_data - .map(|additional_data| AdditionalPaymentData::Card(Box::new(additional_data))) - .as_ref() - .map(Encode::encode_to_value) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode additional pm data")?; + } else { + None + }; let business_sub_label = payment_data.payment_attempt.business_sub_label.clone(); let authentication_type = payment_data.payment_attempt.authentication_type; diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index ea1eac35c35e..3bafd7774099 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -150,7 +150,7 @@ where } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 972ee80ad5a1..11ce9cd7ede2 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -607,6 +607,9 @@ where three_ds_method_url: None, }), poll_config: api_models::payments::PollConfigResponse {poll_id: request_poll_id, delay_in_secs: poll_config.delay_in_secs, frequency: poll_config.frequency}, + message_version: authentication.message_version.as_ref() + .map(|version| version.to_string()), + directory_server_id: authentication.directory_server_id.clone(), }, }) }else{ diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index a620fb78dd75..eb3a711b5b9d 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -1,8 +1,7 @@ use std::{collections::HashMap, num::TryFromIntError}; -use api_models::{payment_methods::SurchargeDetailsResponse, payments::RequestSurchargeDetails}; +use api_models::payment_methods::SurchargeDetailsResponse; use common_utils::{ - consts, errors::CustomResult, ext_traits::{Encode, OptionExt}, types as common_types, @@ -10,6 +9,7 @@ use common_utils::{ use diesel_models::business_profile::BusinessProfile; use error_stack::ResultExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; +pub use hyperswitch_domain_models::router_request_types::{AuthenticationData, SurchargeDetails}; use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; @@ -186,40 +186,6 @@ impl MultipleCaptureData { } } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct SurchargeDetails { - /// original_amount - pub original_amount: common_types::MinorUnit, - /// surcharge value - pub surcharge: common_types::Surcharge, - /// tax on surcharge value - pub tax_on_surcharge: - Option>, - /// surcharge amount for this payment - pub surcharge_amount: common_types::MinorUnit, - /// tax on surcharge amount for this payment - pub tax_on_surcharge_amount: common_types::MinorUnit, - /// sum of original amount, - pub final_amount: common_types::MinorUnit, -} - -impl From<(&RequestSurchargeDetails, &PaymentAttempt)> for SurchargeDetails { - fn from( - (request_surcharge_details, payment_attempt): (&RequestSurchargeDetails, &PaymentAttempt), - ) -> Self { - let surcharge_amount = request_surcharge_details.surcharge_amount; - let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or_default(); - Self { - original_amount: payment_attempt.amount, - surcharge: common_types::Surcharge::Fixed(request_surcharge_details.surcharge_amount), // need to check this - tax_on_surcharge: None, - surcharge_amount, - tax_on_surcharge_amount, - final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount, - } - } -} - impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsResponse { type Error = TryFromIntError; fn foreign_try_from( @@ -250,20 +216,6 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe } } -impl SurchargeDetails { - pub fn is_request_surcharge_matching( - &self, - request_surcharge_details: RequestSurchargeDetails, - ) -> bool { - request_surcharge_details.surcharge_amount == self.surcharge_amount - && request_surcharge_details.tax_amount.unwrap_or_default() - == self.tax_on_surcharge_amount - } - pub fn get_total_surcharge_amount(&self) -> common_types::MinorUnit { - self.surcharge_amount + self.tax_on_surcharge_amount - } -} - #[derive(Eq, Hash, PartialEq, Clone, Debug, strum::Display)] pub enum SurchargeKey { Token(String), @@ -387,14 +339,6 @@ impl SurchargeMetadata { } } -#[derive(Debug, Clone)] -pub struct AuthenticationData { - pub eci: Option, - pub cavv: String, - pub threeds_server_transaction_id: String, - pub message_version: String, -} - impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { type Error = error_stack::Report; fn foreign_try_from(authentication: &storage::Authentication) -> Result { diff --git a/crates/router/src/core/payouts/retry.rs b/crates/router/src/core/payouts/retry.rs index a7dcfa864a0a..7cb929db94a2 100644 --- a/crates/router/src/core/payouts/retry.rs +++ b/crates/router/src/core/payouts/retry.rs @@ -81,7 +81,7 @@ pub async fn do_gsm_multiple_connector_actions( } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? @@ -145,7 +145,7 @@ pub async fn do_gsm_single_connector_actions( } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? diff --git a/crates/router/src/core/verification.rs b/crates/router/src/core/verification.rs index fe3d752497a7..802a4ec3c638 100644 --- a/crates/router/src/core/verification.rs +++ b/crates/router/src/core/verification.rs @@ -4,7 +4,7 @@ use common_utils::{errors::CustomResult, request::RequestContent}; use error_stack::ResultExt; use masking::ExposeInterface; -use crate::{core::errors::api_error_response, headers, logger, routes::AppState, services}; +use crate::{core::errors, headers, logger, routes::AppState, services}; const APPLEPAY_INTERNAL_MERCHANT_NAME: &str = "Applepay_merchant"; @@ -12,10 +12,8 @@ pub async fn verify_merchant_creds_for_applepay( state: AppState, body: verifications::ApplepayMerchantVerificationRequest, merchant_id: String, -) -> CustomResult< - services::ApplicationResponse, - api_error_response::ApiErrorResponse, -> { +) -> CustomResult, errors::ApiErrorResponse> +{ let applepay_merchant_configs = state.conf.applepay_merchant_configs.get_inner(); let applepay_internal_merchant_identifier = applepay_merchant_configs @@ -55,7 +53,7 @@ pub async fn verify_merchant_creds_for_applepay( utils::log_applepay_verification_response_if_error(&response); let applepay_response = - response.change_context(api_error_response::ApiErrorResponse::InternalServerError)?; + response.change_context(errors::ApiErrorResponse::InternalServerError)?; // Error is already logged match applepay_response { @@ -67,7 +65,7 @@ pub async fn verify_merchant_creds_for_applepay( body.domain_names.clone(), ) .await - .change_context(api_error_response::ApiErrorResponse::InternalServerError)?; + .change_context(errors::ApiErrorResponse::InternalServerError)?; Ok(services::api::ApplicationResponse::Json( ApplepayMerchantResponse { status_message: "Applepay verification Completed".to_string(), @@ -76,7 +74,7 @@ pub async fn verify_merchant_creds_for_applepay( } Err(error) => { logger::error!(?error); - Err(api_error_response::ApiErrorResponse::InvalidRequestData { + Err(errors::ApiErrorResponse::InvalidRequestData { message: "Applepay verification Failed".to_string(), } .into()) @@ -90,13 +88,13 @@ pub async fn get_verified_apple_domains_with_mid_mca_id( merchant_connector_id: String, ) -> CustomResult< services::ApplicationResponse, - api_error_response::ApiErrorResponse, + errors::ApiErrorResponse, > { let db = state.store.as_ref(); let key_store = db .get_merchant_key_store_by_merchant_id(&merchant_id, &db.get_master_key().to_vec().into()) .await - .change_context(api_error_response::ApiErrorResponse::MerchantAccountNotFound)?; + .change_context(errors::ApiErrorResponse::MerchantAccountNotFound)?; let verified_domains = db .find_by_merchant_connector_account_merchant_id_merchant_connector_id( @@ -105,7 +103,7 @@ pub async fn get_verified_apple_domains_with_mid_mca_id( &key_store, ) .await - .change_context(api_error_response::ApiErrorResponse::ResourceIdNotFound)? + .change_context(errors::ApiErrorResponse::ResourceIdNotFound)? .applepay_verified_domains .unwrap_or_default(); diff --git a/crates/router/src/db/authentication.rs b/crates/router/src/db/authentication.rs index 398af72f8bd8..4eff3dfdbfde 100644 --- a/crates/router/src/db/authentication.rs +++ b/crates/router/src/db/authentication.rs @@ -147,6 +147,7 @@ impl AuthenticationInterface for MockDb { profile_id: authentication.profile_id, payment_id: authentication.payment_id, merchant_connector_id: authentication.merchant_connector_id, + directory_server_id: authentication.directory_server_id, }; authentications.push(authentication.clone()); Ok(authentication) diff --git a/crates/router/src/routes/fraud_check.rs b/crates/router/src/routes/fraud_check.rs index f0b73015f3cb..70bd55b71056 100644 --- a/crates/router/src/routes/fraud_check.rs +++ b/crates/router/src/routes/fraud_check.rs @@ -34,9 +34,3 @@ impl ApiEventMetric for FraudCheckResponseData { Some(ApiEventsType::FraudCheck) } } - -impl ApiEventMetric for frm_core::types::FrmFulfillmentRequest { - fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::FraudCheck) - } -} diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 71c74099ded4..287f98cdb792 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1328,6 +1328,11 @@ impl EmbedError for Report { } } +impl EmbedError + for Report +{ +} + pub fn http_response_json(response: T) -> HttpResponse { HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 58657c59fd85..6f2aa9fcd100 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -259,7 +259,7 @@ impl KafkaProducer { .timestamp( event .creation_timestamp() - .unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp()), + .unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp() * 1_000), ), ) .map_err(|(error, record)| report!(error).attach_printable(format!("{record:?}"))) @@ -476,7 +476,7 @@ impl MessagingInterface for KafkaProducer { .key(&data.identifier()) .payload(&json_data) .timestamp( - (timestamp.assume_utc().unix_timestamp_nanos() / 1_000) + (timestamp.assume_utc().unix_timestamp_nanos() / 1_000_000) .to_i64() .unwrap_or_else(|| { // kafka producer accepts milliseconds diff --git a/crates/router/src/services/kafka/dispute.rs b/crates/router/src/services/kafka/dispute.rs index c01089855c65..1950fb253a79 100644 --- a/crates/router/src/services/kafka/dispute.rs +++ b/crates/router/src/services/kafka/dispute.rs @@ -70,10 +70,6 @@ impl<'a> super::KafkaMessage for KafkaDispute<'a> { ) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::Dispute } diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index dfa7da0d0a47..c321093776ca 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -52,6 +52,8 @@ pub struct KafkaPaymentAttempt<'a> { pub unified_code: Option<&'a String>, pub unified_message: Option<&'a String>, pub mandate_data: Option<&'a MandateDetails>, + pub client_source: Option<&'a String>, + pub client_version: Option<&'a String>, } impl<'a> KafkaPaymentAttempt<'a> { @@ -96,6 +98,8 @@ impl<'a> KafkaPaymentAttempt<'a> { unified_code: attempt.unified_code.as_ref(), unified_message: attempt.unified_message.as_ref(), mandate_data: attempt.mandate_data.as_ref(), + client_source: attempt.client_source.as_ref(), + client_version: attempt.client_version.as_ref(), } } } @@ -108,10 +112,6 @@ impl<'a> super::KafkaMessage for KafkaPaymentAttempt<'a> { ) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::PaymentAttempt } diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index ad61e1029555..81ac6454c91f 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -30,6 +30,7 @@ pub struct KafkaPaymentIntent<'a> { pub business_country: Option, pub business_label: Option<&'a String>, pub attempt_count: i16, + pub payment_confirm_source: Option, } impl<'a> KafkaPaymentIntent<'a> { @@ -57,6 +58,7 @@ impl<'a> KafkaPaymentIntent<'a> { business_country: intent.business_country, business_label: intent.business_label.as_ref(), attempt_count: intent.attempt_count, + payment_confirm_source: intent.payment_confirm_source, } } } @@ -66,10 +68,6 @@ impl<'a> super::KafkaMessage for KafkaPaymentIntent<'a> { format!("{}_{}", self.merchant_id, self.payment_id) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::PaymentIntent } diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs index 6b25c724eb8c..a07b90ee288b 100644 --- a/crates/router/src/services/kafka/payout.rs +++ b/crates/router/src/services/kafka/payout.rs @@ -80,10 +80,6 @@ impl<'a> super::KafkaMessage for KafkaPayout<'a> { format!("{}_{}", self.merchant_id, self.payout_attempt_id) } - fn creation_timestamp(&self) -> Option { - Some(self.last_modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::Payout } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index ac716c0693b2..e2f2f6c0b702 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -22,10 +22,11 @@ pub use api_models::{enums::Connector, mandates}; #[cfg(feature = "payouts")] pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; use common_enums::MandateStatus; -use common_utils::{pii, pii::Email}; -pub use common_utils::{request::RequestContent, types::MinorUnit}; -use error_stack::ResultExt; +pub use common_utils::request::RequestContent; +use common_utils::{pii, pii::Email, types::MinorUnit}; use hyperswitch_domain_models::mandates::{CustomerAcceptance, MandateData}; +#[cfg(feature = "payouts")] +pub use hyperswitch_domain_models::router_request_types::PayoutsData; pub use hyperswitch_domain_models::{ payment_address::PaymentAddress, router_data::{ @@ -33,9 +34,14 @@ pub use hyperswitch_domain_models::{ ApplePayPredecryptData, ConnectorAuthType, ConnectorResponseData, ErrorResponse, PaymentMethodBalance, PaymentMethodToken, RecurringMandatePaymentData, RouterData, }, + router_request_types::{ + AcceptDisputeRequestData, AccessTokenRequestData, BrowserInformation, ChargeRefunds, + ChargeRefundsOptions, DefendDisputeRequestData, DestinationChargeRefund, + DirectChargeRefund, RefundsData, ResponseId, RetrieveFileRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + }, }; use masking::Secret; -use serde::Serialize; use self::storage::enums as storage_enums; pub use crate::core::payments::CustomerDetails; @@ -51,7 +57,10 @@ use crate::{ payments::{types, PaymentData}, }, services, - types::{transformers::ForeignFrom, types::AuthenticationData}, + types::{ + transformers::{ForeignFrom, ForeignTryFrom}, + types::AuthenticationData, + }, }; pub type PaymentsAuthorizeRouterData = RouterData; @@ -275,20 +284,6 @@ pub type PayoutsRouterData = RouterData; pub type PayoutsResponseRouterData = ResponseRouterData; -#[cfg(feature = "payouts")] -#[derive(Debug, Clone)] -pub struct PayoutsData { - pub payout_id: String, - pub amount: i64, - pub connector_payout_id: Option, - pub destination_currency: storage_enums::Currency, - pub source_currency: storage_enums::Currency, - pub payout_type: storage_enums::PayoutType, - pub entity_type: storage_enums::PayoutEntityType, - pub customer_details: Option, - pub vendor_details: Option, -} - #[cfg(feature = "payouts")] pub trait PayoutIndividualDetailsExt { type Error; @@ -535,13 +530,6 @@ pub struct SetupMandateRequestData { pub metadata: Option, } -#[derive(Debug, Clone)] -pub struct AccessTokenRequestData { - pub app_id: Secret, - pub id: Option>, - // Add more keys if required -} - pub trait Capturable { fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where @@ -870,87 +858,6 @@ pub enum PreprocessingResponseId { ConnectorTransactionId(String), } -#[derive(Debug, Clone, Default, Serialize)] -pub enum ResponseId { - ConnectorTransactionId(String), - EncodedData(String), - #[default] - NoResponseId, -} - -impl ResponseId { - pub fn get_connector_transaction_id( - &self, - ) -> errors::CustomResult { - match self { - Self::ConnectorTransactionId(txn_id) => Ok(txn_id.to_string()), - _ => Err(errors::ValidationError::IncorrectValueProvided { - field_name: "connector_transaction_id", - }) - .attach_printable("Expected connector transaction ID not found"), - } - } -} - -#[derive(Debug, Clone)] -pub struct RefundsData { - pub refund_id: String, - pub connector_transaction_id: String, - - pub connector_refund_id: Option, - pub currency: storage_enums::Currency, - /// Amount for the payment against which this refund is issued - pub payment_amount: i64, - pub reason: Option, - pub webhook_url: Option, - /// Amount to be refunded - pub refund_amount: i64, - /// Arbitrary metadata required for refund - pub connector_metadata: Option, - pub browser_info: Option, - /// Charges associated with the payment - pub charges: Option, -} - -#[derive(Debug, serde::Deserialize, Clone)] -pub struct ChargeRefunds { - pub charge_id: String, - pub transfer_account_id: String, - pub charge_type: api_models::enums::PaymentChargeType, - pub options: ChargeRefundsOptions, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub enum ChargeRefundsOptions { - Destination(DestinationChargeRefund), - Direct(DirectChargeRefund), -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct DirectChargeRefund { - pub revert_platform_fee: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct DestinationChargeRefund { - pub revert_platform_fee: bool, - pub revert_transfer: bool, -} - -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct BrowserInformation { - pub color_depth: Option, - pub java_enabled: Option, - pub java_script_enabled: Option, - pub language: Option, - pub screen_height: Option, - pub screen_width: Option, - pub time_zone: Option, - pub ip_address: Option, - pub accept_header: Option, - pub user_agent: Option, -} - #[derive(Debug, Clone)] pub struct RefundsResponseData { pub connector_refund_id: String, @@ -964,13 +871,6 @@ pub enum Redirection { NoRedirect, } -#[derive(Debug, Clone)] -pub struct VerifyWebhookSourceRequestData { - pub webhook_headers: actix_web::http::header::HeaderMap, - pub webhook_body: Vec, - pub merchant_secret: api_models::webhooks::ConnectorWebhookSecrets, -} - #[derive(Debug, Clone)] pub struct VerifyWebhookSourceResponseData { pub verify_webhook_status: VerifyWebhookStatus, @@ -982,98 +882,28 @@ pub enum VerifyWebhookStatus { SourceNotVerified, } -#[derive(Default, Debug, Clone)] -pub struct AcceptDisputeRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, -} - #[derive(Default, Clone, Debug)] pub struct AcceptDisputeResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Default, Debug, Clone)] -pub struct SubmitEvidenceRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, - pub access_activity_log: Option, - pub billing_address: Option, - pub cancellation_policy: Option>, - pub cancellation_policy_provider_file_id: Option, - pub cancellation_policy_disclosure: Option, - pub cancellation_rebuttal: Option, - pub customer_communication: Option>, - pub customer_communication_provider_file_id: Option, - pub customer_email_address: Option, - pub customer_name: Option, - pub customer_purchase_ip: Option, - pub customer_signature: Option>, - pub customer_signature_provider_file_id: Option, - pub product_description: Option, - pub receipt: Option>, - pub receipt_provider_file_id: Option, - pub refund_policy: Option>, - pub refund_policy_provider_file_id: Option, - pub refund_policy_disclosure: Option, - pub refund_refusal_explanation: Option, - pub service_date: Option, - pub service_documentation: Option>, - pub service_documentation_provider_file_id: Option, - pub shipping_address: Option, - pub shipping_carrier: Option, - pub shipping_date: Option, - pub shipping_documentation: Option>, - pub shipping_documentation_provider_file_id: Option, - pub shipping_tracking_number: Option, - pub invoice_showing_distinct_transactions: Option>, - pub invoice_showing_distinct_transactions_provider_file_id: Option, - pub recurring_transaction_agreement: Option>, - pub recurring_transaction_agreement_provider_file_id: Option, - pub uncategorized_file: Option>, - pub uncategorized_file_provider_file_id: Option, - pub uncategorized_text: Option, -} - #[derive(Default, Clone, Debug)] pub struct SubmitEvidenceResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Default, Debug, Clone)] -pub struct DefendDisputeRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, -} - #[derive(Default, Debug, Clone)] pub struct DefendDisputeResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Clone, Debug, serde::Serialize)] -pub struct UploadFileRequestData { - pub file_key: String, - #[serde(skip)] - pub file: Vec, - #[serde(serialize_with = "crate::utils::custom_serde::display_serialize")] - pub file_type: mime::Mime, - pub file_size: i32, -} - #[derive(Default, Clone, Debug)] pub struct UploadFileResponse { pub provider_file_id: String, } - -#[derive(Clone, Debug)] -pub struct RetrieveFileRequestData { - pub provider_file_id: String, -} - #[derive(Clone, Debug)] pub struct RetrieveFileResponse { pub file_data: Vec, @@ -1239,9 +1069,9 @@ pub struct Response { pub status_code: u16, } -impl TryFrom for AccessTokenRequestData { +impl ForeignTryFrom for AccessTokenRequestData { type Error = errors::ApiErrorResponse; - fn try_from(connector_auth: ConnectorAuthType) -> Result { + fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result { match connector_auth { ConnectorAuthType::HeaderKey { api_key } => Ok(Self { app_id: api_key, @@ -1267,22 +1097,6 @@ impl TryFrom for AccessTokenRequestData { } } -impl From for ErrorResponse { - fn from(error: errors::ApiErrorResponse) -> Self { - Self { - code: error.error_code(), - message: error.error_message(), - reason: None, - status_code: match error { - errors::ApiErrorResponse::ExternalConnectorError { status_code, .. } => status_code, - _ => 500, - }, - attempt_status: None, - connector_transaction_id: None, - } - } -} - impl From<&&mut PaymentsAuthorizeRouterData> for AuthorizeSessionTokenData { fn from(data: &&mut PaymentsAuthorizeRouterData) -> Self { Self { diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index 0f86f4621672..9837059dc012 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use api_models::enums; use common_utils::errors::CustomResult; use error_stack::ResultExt; +pub use hyperswitch_domain_models::router_request_types::authentication::MessageCategory; use super::BoxedConnector; use crate::core::errors; @@ -66,12 +67,6 @@ pub struct PostAuthenticationResponse { pub eci: Option, } -#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] -pub enum MessageCategory { - Payment, - NonPayment, -} - #[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] pub struct ExternalAuthenticationPayload { pub trans_status: common_enums::TransactionStatus, diff --git a/crates/router/src/types/api/files.rs b/crates/router/src/types/api/files.rs index 873516ee71bc..688962bd8367 100644 --- a/crates/router/src/types/api/files.rs +++ b/crates/router/src/types/api/files.rs @@ -1,5 +1,6 @@ use api_models::enums::FileUploadProvider; use masking::{Deserialize, Serialize}; +use serde_with::serde_as; use super::ConnectorCommon; use crate::{ @@ -47,12 +48,13 @@ impl ForeignTryFrom<&types::Connector> for FileUploadProvider { } } +#[serde_as] #[derive(Debug, Clone, serde::Serialize)] pub struct CreateFileRequest { pub file: Vec, pub file_name: Option, pub file_size: i32, - #[serde(serialize_with = "crate::utils::custom_serde::display_serialize")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub file_type: mime::Mime, pub purpose: FilePurpose, pub dispute_id: Option, diff --git a/crates/router/src/types/authentication.rs b/crates/router/src/types/authentication.rs index 91ab768e28a1..97ad8506d4fe 100644 --- a/crates/router/src/types/authentication.rs +++ b/crates/router/src/types/authentication.rs @@ -18,6 +18,7 @@ pub enum AuthenticationResponseData { three_ds_method_url: Option, message_version: common_utils::types::SemanticVersion, connector_metadata: Option, + directory_server_id: Option, }, AuthNResponse { authn_flow_type: AuthNFlowType, diff --git a/crates/router/src/types/domain/payments.rs b/crates/router/src/types/domain/payments.rs index 9dc85c103e97..7b1f33654902 100644 --- a/crates/router/src/types/domain/payments.rs +++ b/crates/router/src/types/domain/payments.rs @@ -1,837 +1,10 @@ -use common_utils::pii::{self, Email}; -use masking::Secret; -use serde::{Deserialize, Serialize}; - -// We need to derive Serialize and Deserialize because some parts of payment method data are being -// stored in the database as serde_json::Value -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum PaymentMethodData { - Card(Card), - CardRedirect(CardRedirectData), - Wallet(WalletData), - PayLater(PayLaterData), - BankRedirect(BankRedirectData), - BankDebit(BankDebitData), - BankTransfer(Box), - Crypto(CryptoData), - MandatePayment, - Reward, - Upi(UpiData), - Voucher(VoucherData), - GiftCard(Box), - CardToken(CardToken), -} - -impl PaymentMethodData { - pub fn get_payment_method(&self) -> Option { - match self { - Self::Card(_) => Some(common_enums::PaymentMethod::Card), - Self::CardRedirect(_) => Some(common_enums::PaymentMethod::CardRedirect), - Self::Wallet(_) => Some(common_enums::PaymentMethod::Wallet), - Self::PayLater(_) => Some(common_enums::PaymentMethod::PayLater), - Self::BankRedirect(_) => Some(common_enums::PaymentMethod::BankRedirect), - Self::BankDebit(_) => Some(common_enums::PaymentMethod::BankDebit), - Self::BankTransfer(_) => Some(common_enums::PaymentMethod::BankTransfer), - Self::Crypto(_) => Some(common_enums::PaymentMethod::Crypto), - Self::Reward => Some(common_enums::PaymentMethod::Reward), - Self::Upi(_) => Some(common_enums::PaymentMethod::Upi), - Self::Voucher(_) => Some(common_enums::PaymentMethod::Voucher), - Self::GiftCard(_) => Some(common_enums::PaymentMethod::GiftCard), - Self::CardToken(_) | Self::MandatePayment => None, - } - } -} - -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] -pub struct Card { - pub card_number: cards::CardNumber, - pub card_exp_month: Secret, - pub card_exp_year: Secret, - pub card_cvc: Secret, - pub card_issuer: Option, - pub card_network: Option, - pub card_type: Option, - pub card_issuing_country: Option, - pub bank_code: Option, - pub nick_name: Option>, -} - -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum CardRedirectData { - Knet {}, - Benefit {}, - MomoAtm {}, - CardRedirect {}, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum PayLaterData { - KlarnaRedirect {}, - KlarnaSdk { token: String }, - AffirmRedirect {}, - AfterpayClearpayRedirect {}, - PayBrightRedirect {}, - WalleyRedirect {}, - AlmaRedirect {}, - AtomeRedirect {}, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub enum WalletData { - AliPayQr(Box), - AliPayRedirect(AliPayRedirection), - AliPayHkRedirect(AliPayHkRedirection), - MomoRedirect(MomoRedirection), - KakaoPayRedirect(KakaoPayRedirection), - GoPayRedirect(GoPayRedirection), - GcashRedirect(GcashRedirection), - ApplePay(ApplePayWalletData), - ApplePayRedirect(Box), - ApplePayThirdPartySdk(Box), - DanaRedirect {}, - GooglePay(GooglePayWalletData), - GooglePayRedirect(Box), - GooglePayThirdPartySdk(Box), - MbWayRedirect(Box), - MobilePayRedirect(Box), - PaypalRedirect(PaypalRedirection), - PaypalSdk(PayPalWalletData), - SamsungPay(Box), - TwintRedirect {}, - VippsRedirect {}, - TouchNGoRedirect(Box), - WeChatPayRedirect(Box), - WeChatPayQr(Box), - CashappQr(Box), - SwishQr(SwishQrData), -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct SamsungPayWalletData { - /// The encrypted payment token from Samsung - pub token: Secret, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct GooglePayWalletData { - /// The type of payment method - pub pm_type: String, - /// User-facing message to describe the payment method that funds this transaction. - pub description: String, - /// The information of the payment method - pub info: GooglePayPaymentMethodInfo, - /// The tokenization data of Google pay - pub tokenization_data: GpayTokenizationData, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayRedirectData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GooglePayRedirectData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GooglePayThirdPartySdkData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayThirdPartySdkData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPay {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPayQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct CashappQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct PaypalRedirection { - /// paypal's email address - pub email: Option, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayHkRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MomoRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct KakaoPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GoPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GcashRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MobilePayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MbWayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct GooglePayPaymentMethodInfo { - /// The name of the card network - pub card_network: String, - /// The details of the card - pub card_details: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct PayPalWalletData { - /// Token generated for the Apple pay - pub token: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct TouchNGoRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct SwishQrData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GpayTokenizationData { - /// The type of the token - pub token_type: String, - /// Token generated for the wallet - pub token: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayWalletData { - /// The payment data of Apple pay - pub payment_data: String, - /// The payment method of Apple pay - pub payment_method: ApplepayPaymentMethod, - /// The unique identifier for the transaction - pub transaction_identifier: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplepayPaymentMethod { - pub display_name: String, - pub network: String, - pub pm_type: String, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - -pub enum BankRedirectData { - BancontactCard { - card_number: Option, - card_exp_month: Option>, - card_exp_year: Option>, - }, - Bizum {}, - Blik { - blik_code: Option, - }, - Eps { - bank_name: Option, - }, - Giropay { - bank_account_bic: Option>, - bank_account_iban: Option>, - }, - Ideal { - bank_name: Option, - }, - Interac {}, - OnlineBankingCzechRepublic { - issuer: common_enums::BankNames, - }, - OnlineBankingFinland {}, - OnlineBankingPoland { - issuer: common_enums::BankNames, - }, - OnlineBankingSlovakia { - issuer: common_enums::BankNames, - }, - OpenBankingUk { - issuer: Option, - }, - Przelewy24 { - bank_name: Option, - }, - Sofort { - preferred_language: Option, - }, - Trustly {}, - OnlineBankingFpx { - issuer: common_enums::BankNames, - }, - OnlineBankingThailand { - issuer: common_enums::BankNames, - }, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub struct CryptoData { - pub pay_currency: Option, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub struct UpiData { - pub vpa_id: Option>, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum VoucherData { - Boleto(Box), - Efecty, - PagoEfectivo, - RedCompra, - RedPagos, - Alfamart(Box), - Indomaret(Box), - Oxxo, - SevenEleven(Box), - Lawson(Box), - MiniStop(Box), - FamilyMart(Box), - Seicomart(Box), - PayEasy(Box), -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct BoletoVoucherData { - /// The shopper's social security number - pub social_security_number: Option>, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct AlfamartVoucherData {} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct IndomaretVoucherData {} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct JCSVoucherData {} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum GiftCardData { - Givex(GiftCardDetails), - PaySafeCard {}, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct GiftCardDetails { - /// The gift card number - pub number: Secret, - /// The card verification code. - pub cvc: Secret, -} - -#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, Default)] -#[serde(rename_all = "snake_case")] -pub struct CardToken { - /// The card holder's name - pub card_holder_name: Option>, - - /// The CVC number for the card - pub card_cvc: Option>, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum BankDebitData { - AchBankDebit { - account_number: Secret, - routing_number: Secret, - bank_name: Option, - bank_type: Option, - bank_holder_type: Option, - }, - SepaBankDebit { - iban: Secret, - }, - BecsBankDebit { - account_number: Secret, - bsb_number: Secret, - }, - BacsBankDebit { - account_number: Secret, - sort_code: Secret, - }, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub enum BankTransferData { - AchBankTransfer {}, - SepaBankTransfer {}, - BacsBankTransfer {}, - MultibancoBankTransfer {}, - PermataBankTransfer {}, - BcaBankTransfer {}, - BniVaBankTransfer {}, - BriVaBankTransfer {}, - CimbVaBankTransfer {}, - DanamonVaBankTransfer {}, - MandiriVaBankTransfer {}, - Pix {}, - Pse {}, - LocalBankTransfer { bank_code: Option }, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct SepaAndBacsBillingDetails { - /// The Email ID for SEPA and BACS billing - pub email: Email, - /// The billing name for SEPA and BACS billing - pub name: Secret, -} - -impl From for PaymentMethodData { - fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self { - match api_model_payment_method_data { - api_models::payments::PaymentMethodData::Card(card_data) => { - Self::Card(Card::from(card_data)) - } - api_models::payments::PaymentMethodData::CardRedirect(card_redirect) => { - Self::CardRedirect(From::from(card_redirect)) - } - api_models::payments::PaymentMethodData::Wallet(wallet_data) => { - Self::Wallet(From::from(wallet_data)) - } - api_models::payments::PaymentMethodData::PayLater(pay_later_data) => { - Self::PayLater(From::from(pay_later_data)) - } - api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { - Self::BankRedirect(From::from(bank_redirect_data)) - } - api_models::payments::PaymentMethodData::BankDebit(bank_debit_data) => { - Self::BankDebit(From::from(bank_debit_data)) - } - api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { - Self::BankTransfer(Box::new(From::from(*bank_transfer_data))) - } - api_models::payments::PaymentMethodData::Crypto(crypto_data) => { - Self::Crypto(From::from(crypto_data)) - } - api_models::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, - api_models::payments::PaymentMethodData::Reward => Self::Reward, - api_models::payments::PaymentMethodData::Upi(upi_data) => { - Self::Upi(From::from(upi_data)) - } - api_models::payments::PaymentMethodData::Voucher(voucher_data) => { - Self::Voucher(From::from(voucher_data)) - } - api_models::payments::PaymentMethodData::GiftCard(gift_card) => { - Self::GiftCard(Box::new(From::from(*gift_card))) - } - api_models::payments::PaymentMethodData::CardToken(card_token) => { - Self::CardToken(From::from(card_token)) - } - } - } -} - -impl From for Card { - fn from(value: api_models::payments::Card) -> Self { - let api_models::payments::Card { - card_number, - card_exp_month, - card_exp_year, - card_holder_name: _, - card_cvc, - card_issuer, - card_network, - card_type, - card_issuing_country, - bank_code, - nick_name, - } = value; - - Self { - card_number, - card_exp_month, - card_exp_year, - card_cvc, - card_issuer, - card_network, - card_type, - card_issuing_country, - bank_code, - nick_name, - } - } -} - -impl From for CardRedirectData { - fn from(value: api_models::payments::CardRedirectData) -> Self { - match value { - api_models::payments::CardRedirectData::Knet {} => Self::Knet {}, - api_models::payments::CardRedirectData::Benefit {} => Self::Benefit {}, - api_models::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm {}, - api_models::payments::CardRedirectData::CardRedirect {} => Self::CardRedirect {}, - } - } -} - -impl From for WalletData { - fn from(value: api_models::payments::WalletData) -> Self { - match value { - api_models::payments::WalletData::AliPayQr(_) => Self::AliPayQr(Box::new(AliPayQr {})), - api_models::payments::WalletData::AliPayRedirect(_) => { - Self::AliPayRedirect(AliPayRedirection {}) - } - api_models::payments::WalletData::AliPayHkRedirect(_) => { - Self::AliPayHkRedirect(AliPayHkRedirection {}) - } - api_models::payments::WalletData::MomoRedirect(_) => { - Self::MomoRedirect(MomoRedirection {}) - } - api_models::payments::WalletData::KakaoPayRedirect(_) => { - Self::KakaoPayRedirect(KakaoPayRedirection {}) - } - api_models::payments::WalletData::GoPayRedirect(_) => { - Self::GoPayRedirect(GoPayRedirection {}) - } - api_models::payments::WalletData::GcashRedirect(_) => { - Self::GcashRedirect(GcashRedirection {}) - } - api_models::payments::WalletData::ApplePay(apple_pay_data) => { - Self::ApplePay(ApplePayWalletData::from(apple_pay_data)) - } - api_models::payments::WalletData::ApplePayRedirect(_) => { - Self::ApplePayRedirect(Box::new(ApplePayRedirectData {})) - } - api_models::payments::WalletData::ApplePayThirdPartySdk(_) => { - Self::ApplePayThirdPartySdk(Box::new(ApplePayThirdPartySdkData {})) - } - api_models::payments::WalletData::DanaRedirect {} => Self::DanaRedirect {}, - api_models::payments::WalletData::GooglePay(google_pay_data) => { - Self::GooglePay(GooglePayWalletData::from(google_pay_data)) - } - api_models::payments::WalletData::GooglePayRedirect(_) => { - Self::GooglePayRedirect(Box::new(GooglePayRedirectData {})) - } - api_models::payments::WalletData::GooglePayThirdPartySdk(_) => { - Self::GooglePayThirdPartySdk(Box::new(GooglePayThirdPartySdkData {})) - } - api_models::payments::WalletData::MbWayRedirect(..) => { - Self::MbWayRedirect(Box::new(MbWayRedirection {})) - } - api_models::payments::WalletData::MobilePayRedirect(_) => { - Self::MobilePayRedirect(Box::new(MobilePayRedirection {})) - } - api_models::payments::WalletData::PaypalRedirect(paypal_redirect_data) => { - Self::PaypalRedirect(PaypalRedirection { - email: paypal_redirect_data.email, - }) - } - api_models::payments::WalletData::PaypalSdk(paypal_sdk_data) => { - Self::PaypalSdk(PayPalWalletData { - token: paypal_sdk_data.token, - }) - } - api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { - Self::SamsungPay(Box::new(SamsungPayWalletData { - token: samsung_pay_data.token, - })) - } - api_models::payments::WalletData::TwintRedirect {} => Self::TwintRedirect {}, - api_models::payments::WalletData::VippsRedirect {} => Self::VippsRedirect {}, - api_models::payments::WalletData::TouchNGoRedirect(_) => { - Self::TouchNGoRedirect(Box::new(TouchNGoRedirection {})) - } - api_models::payments::WalletData::WeChatPayRedirect(_) => { - Self::WeChatPayRedirect(Box::new(WeChatPayRedirection {})) - } - api_models::payments::WalletData::WeChatPayQr(_) => { - Self::WeChatPayQr(Box::new(WeChatPayQr {})) - } - api_models::payments::WalletData::CashappQr(_) => { - Self::CashappQr(Box::new(CashappQr {})) - } - api_models::payments::WalletData::SwishQr(_) => Self::SwishQr(SwishQrData {}), - } - } -} - -impl From for GooglePayWalletData { - fn from(value: api_models::payments::GooglePayWalletData) -> Self { - Self { - pm_type: value.pm_type, - description: value.description, - info: GooglePayPaymentMethodInfo { - card_network: value.info.card_network, - card_details: value.info.card_details, - }, - tokenization_data: GpayTokenizationData { - token_type: value.tokenization_data.token_type, - token: value.tokenization_data.token, - }, - } - } -} - -impl From for ApplePayWalletData { - fn from(value: api_models::payments::ApplePayWalletData) -> Self { - Self { - payment_data: value.payment_data, - payment_method: ApplepayPaymentMethod { - display_name: value.payment_method.display_name, - network: value.payment_method.network, - pm_type: value.payment_method.pm_type, - }, - transaction_identifier: value.transaction_identifier, - } - } -} - -impl From for PayLaterData { - fn from(value: api_models::payments::PayLaterData) -> Self { - match value { - api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, - api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, - api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, - api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { - Self::AfterpayClearpayRedirect {} - } - api_models::payments::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect {}, - api_models::payments::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect {}, - api_models::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect {}, - api_models::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect {}, - } - } -} - -impl From for BankRedirectData { - fn from(value: api_models::payments::BankRedirectData) -> Self { - match value { - api_models::payments::BankRedirectData::BancontactCard { - card_number, - card_exp_month, - card_exp_year, - .. - } => Self::BancontactCard { - card_number, - card_exp_month, - card_exp_year, - }, - api_models::payments::BankRedirectData::Bizum {} => Self::Bizum {}, - api_models::payments::BankRedirectData::Blik { blik_code } => Self::Blik { blik_code }, - api_models::payments::BankRedirectData::Eps { bank_name, .. } => { - Self::Eps { bank_name } - } - api_models::payments::BankRedirectData::Giropay { - bank_account_bic, - bank_account_iban, - .. - } => Self::Giropay { - bank_account_bic, - bank_account_iban, - }, - api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { - Self::Ideal { bank_name } - } - api_models::payments::BankRedirectData::Interac { .. } => Self::Interac {}, - api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { issuer } => { - Self::OnlineBankingCzechRepublic { issuer } - } - api_models::payments::BankRedirectData::OnlineBankingFinland { .. } => { - Self::OnlineBankingFinland {} - } - api_models::payments::BankRedirectData::OnlineBankingPoland { issuer } => { - Self::OnlineBankingPoland { issuer } - } - api_models::payments::BankRedirectData::OnlineBankingSlovakia { issuer } => { - Self::OnlineBankingSlovakia { issuer } - } - api_models::payments::BankRedirectData::OpenBankingUk { issuer, .. } => { - Self::OpenBankingUk { issuer } - } - api_models::payments::BankRedirectData::Przelewy24 { bank_name, .. } => { - Self::Przelewy24 { bank_name } - } - api_models::payments::BankRedirectData::Sofort { - preferred_language, .. - } => Self::Sofort { preferred_language }, - api_models::payments::BankRedirectData::Trustly { .. } => Self::Trustly {}, - api_models::payments::BankRedirectData::OnlineBankingFpx { issuer } => { - Self::OnlineBankingFpx { issuer } - } - api_models::payments::BankRedirectData::OnlineBankingThailand { issuer } => { - Self::OnlineBankingThailand { issuer } - } - } - } -} - -impl From for CryptoData { - fn from(value: api_models::payments::CryptoData) -> Self { - let api_models::payments::CryptoData { pay_currency } = value; - Self { pay_currency } - } -} - -impl From for UpiData { - fn from(value: api_models::payments::UpiData) -> Self { - let api_models::payments::UpiData { vpa_id } = value; - Self { vpa_id } - } -} - -impl From for VoucherData { - fn from(value: api_models::payments::VoucherData) -> Self { - match value { - api_models::payments::VoucherData::Boleto(boleto_data) => { - Self::Boleto(Box::new(BoletoVoucherData { - social_security_number: boleto_data.social_security_number, - })) - } - api_models::payments::VoucherData::Alfamart(_) => { - Self::Alfamart(Box::new(AlfamartVoucherData {})) - } - api_models::payments::VoucherData::Indomaret(_) => { - Self::Indomaret(Box::new(IndomaretVoucherData {})) - } - api_models::payments::VoucherData::SevenEleven(_) - | api_models::payments::VoucherData::Lawson(_) - | api_models::payments::VoucherData::MiniStop(_) - | api_models::payments::VoucherData::FamilyMart(_) - | api_models::payments::VoucherData::Seicomart(_) - | api_models::payments::VoucherData::PayEasy(_) => { - Self::SevenEleven(Box::new(JCSVoucherData {})) - } - api_models::payments::VoucherData::Efecty => Self::Efecty, - api_models::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, - api_models::payments::VoucherData::RedCompra => Self::RedCompra, - api_models::payments::VoucherData::RedPagos => Self::RedPagos, - api_models::payments::VoucherData::Oxxo => Self::Oxxo, - } - } -} - -impl From for GiftCardData { - fn from(value: api_models::payments::GiftCardData) -> Self { - match value { - api_models::payments::GiftCardData::Givex(details) => Self::Givex(GiftCardDetails { - number: details.number, - cvc: details.cvc, - }), - api_models::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, - } - } -} - -impl From for CardToken { - fn from(value: api_models::payments::CardToken) -> Self { - let api_models::payments::CardToken { - card_holder_name, - card_cvc, - } = value; - Self { - card_holder_name, - card_cvc, - } - } -} - -impl From for BankDebitData { - fn from(value: api_models::payments::BankDebitData) -> Self { - match value { - api_models::payments::BankDebitData::AchBankDebit { - account_number, - routing_number, - bank_name, - bank_type, - bank_holder_type, - .. - } => Self::AchBankDebit { - account_number, - routing_number, - bank_name, - bank_type, - bank_holder_type, - }, - api_models::payments::BankDebitData::SepaBankDebit { iban, .. } => { - Self::SepaBankDebit { iban } - } - api_models::payments::BankDebitData::BecsBankDebit { - account_number, - bsb_number, - .. - } => Self::BecsBankDebit { - account_number, - bsb_number, - }, - api_models::payments::BankDebitData::BacsBankDebit { - account_number, - sort_code, - .. - } => Self::BacsBankDebit { - account_number, - sort_code, - }, - } - } -} - -impl From for BankTransferData { - fn from(value: api_models::payments::BankTransferData) -> Self { - match value { - api_models::payments::BankTransferData::AchBankTransfer { .. } => { - Self::AchBankTransfer {} - } - api_models::payments::BankTransferData::SepaBankTransfer { .. } => { - Self::SepaBankTransfer {} - } - api_models::payments::BankTransferData::BacsBankTransfer { .. } => { - Self::BacsBankTransfer {} - } - api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { - Self::MultibancoBankTransfer {} - } - api_models::payments::BankTransferData::PermataBankTransfer { .. } => { - Self::PermataBankTransfer {} - } - api_models::payments::BankTransferData::BcaBankTransfer { .. } => { - Self::BcaBankTransfer {} - } - api_models::payments::BankTransferData::BniVaBankTransfer { .. } => { - Self::BniVaBankTransfer {} - } - api_models::payments::BankTransferData::BriVaBankTransfer { .. } => { - Self::BriVaBankTransfer {} - } - api_models::payments::BankTransferData::CimbVaBankTransfer { .. } => { - Self::CimbVaBankTransfer {} - } - api_models::payments::BankTransferData::DanamonVaBankTransfer { .. } => { - Self::DanamonVaBankTransfer {} - } - api_models::payments::BankTransferData::MandiriVaBankTransfer { .. } => { - Self::MandiriVaBankTransfer {} - } - api_models::payments::BankTransferData::Pix {} => Self::Pix {}, - api_models::payments::BankTransferData::Pse {} => Self::Pse {}, - api_models::payments::BankTransferData::LocalBankTransfer { bank_code } => { - Self::LocalBankTransfer { bank_code } - } - } - } -} +pub use hyperswitch_domain_models::payment_method_data::{ + AliPayQr, ApplePayThirdPartySdkData, ApplePayWalletData, ApplepayPaymentMethod, BankDebitData, + BankRedirectData, BankTransferData, BoletoVoucherData, Card, CardRedirectData, CardToken, + CashappQr, CryptoData, GcashRedirection, GiftCardData, GiftCardDetails, GoPayRedirection, + GooglePayPaymentMethodInfo, GooglePayRedirectData, GooglePayThirdPartySdkData, + GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData, KakaoPayRedirection, + MbWayRedirection, PayLaterData, PaymentMethodData, SamsungPayWalletData, + SepaAndBacsBillingDetails, SwishQrData, TouchNGoRedirection, VoucherData, WalletData, + WeChatPayQr, +}; diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs index 12ab7e4a067a..59be546b1c5d 100644 --- a/crates/router/src/types/fraud_check.rs +++ b/crates/router/src/types/fraud_check.rs @@ -1,11 +1,12 @@ -use common_utils::pii::Email; +pub use hyperswitch_domain_models::router_request_types::fraud_check::{ + FraudCheckCheckoutData, FraudCheckFulfillmentData, FraudCheckRecordReturnData, + FraudCheckSaleData, FraudCheckTransactionData, RefundMethod, +}; use crate::{ - connector::signifyd::transformers::RefundMethod, - core::fraud_check::types::FrmFulfillmentRequest, pii::Serialize, services, - types::{self, api, storage_enums, ErrorResponse, ResponseId, RouterData}, + types::{api, storage_enums, ErrorResponse, ResponseId, RouterData}, }; pub type FrmSaleRouterData = RouterData; @@ -13,13 +14,6 @@ pub type FrmSaleRouterData = RouterData; -#[derive(Debug, Clone)] -pub struct FraudCheckSaleData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub email: Option, -} #[derive(Debug, Clone)] pub struct FrmRouterData { pub merchant_id: String, @@ -76,17 +70,6 @@ pub type FrmCheckoutType = dyn services::ConnectorIntegration< FraudCheckResponseData, >; -#[derive(Debug, Clone)] -pub struct FraudCheckCheckoutData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub browser_info: Option, - pub payment_method_data: Option, - pub email: Option, - pub gateway: Option, -} - pub type FrmTransactionRouterData = RouterData; @@ -96,19 +79,6 @@ pub type FrmTransactionType = dyn services::ConnectorIntegration< FraudCheckResponseData, >; -#[derive(Debug, Clone)] -pub struct FraudCheckTransactionData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub payment_method: Option, - pub error_code: Option, - pub error_message: Option, - pub connector_transaction_id: Option, - //The name of the payment gateway or financial institution that processed the transaction. - pub connector: Option, -} - pub type FrmFulfillmentRouterData = RouterData; @@ -125,18 +95,3 @@ pub type FrmRecordReturnType = dyn services::ConnectorIntegration< FraudCheckRecordReturnData, FraudCheckResponseData, >; - -#[derive(Debug, Clone)] -pub struct FraudCheckFulfillmentData { - pub amount: i64, - pub order_details: Option>>, - pub fulfillment_req: FrmFulfillmentRequest, -} - -#[derive(Debug, Clone)] -pub struct FraudCheckRecordReturnData { - pub amount: i64, - pub currency: Option, - pub refund_method: RefundMethod, - pub refund_transaction_id: Option, -} diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 059278a1b1c9..62c260de7139 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -1,7 +1,6 @@ #[cfg(feature = "olap")] pub mod connector_onboarding; pub mod currency; -pub mod custom_serde; pub mod db_utils; pub mod ext_traits; #[cfg(feature = "kv_store")] diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs index 03735e61cc70..aeea146df292 100644 --- a/crates/router/src/utils/connector_onboarding.rs +++ b/crates/router/src/utils/connector_onboarding.rs @@ -4,7 +4,7 @@ use error_stack::ResultExt; use super::errors::StorageErrorExt; use crate::{ consts, - core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + core::errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, routes::{app::settings, AppState}, types::{self, api::enums}, }; diff --git a/crates/router/src/utils/custom_serde.rs b/crates/router/src/utils/custom_serde.rs deleted file mode 100644 index dcdad3092b2f..000000000000 --- a/crates/router/src/utils/custom_serde.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub fn display_serialize(value: &T, serializer: S) -> Result -where - T: std::fmt::Display, - S: serde::ser::Serializer, -{ - serializer.serialize_str(&format!("{}", value)) -} diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index b98f212d6740..73832a1ad6a9 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true [features] default = ["kv_store", "olap"] -olap = ["storage_impl/olap"] +olap = ["storage_impl/olap", "hyperswitch_domain_models/olap"] kv_store = [] email = ["external_services/email"] @@ -35,6 +35,7 @@ masking = { version = "0.1.0", path = "../masking" } redis_interface = { version = "0.1.0", path = "../redis_interface" } router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } +hyperswitch_domain_models = {version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } # [[bin]] # name = "scheduler" diff --git a/crates/scheduler/src/errors.rs b/crates/scheduler/src/errors.rs index e4fd56ba8d29..e630287c316a 100644 --- a/crates/scheduler/src/errors.rs +++ b/crates/scheduler/src/errors.rs @@ -1,6 +1,7 @@ pub use common_utils::errors::{ParsingError, ValidationError}; #[cfg(feature = "email")] use external_services::email::EmailError; +use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; pub use redis_interface::errors::RedisError; pub use storage_impl::errors::ApplicationError; use storage_impl::errors::StorageError; @@ -88,6 +89,12 @@ impl From for ProcessTrackerError { } } +impl PTError for ApiErrorResponse { + fn to_pt_error(&self) -> ProcessTrackerError { + ProcessTrackerError::EApiErrorResponse + } +} + impl From> for ProcessTrackerError { diff --git a/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql new file mode 100644 index 000000000000..7025236fe882 --- /dev/null +++ b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql @@ -0,0 +1 @@ +ALTER TABLE authentication DROP COLUMN IF EXISTS directory_server_id; \ No newline at end of file diff --git a/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql new file mode 100644 index 000000000000..ecad9d16f6ee --- /dev/null +++ b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE authentication ADD COLUMN IF NOT EXISTS directory_server_id VARCHAR(128); \ No newline at end of file diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index fb66be21799f..47da1ae10742 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -18490,6 +18490,16 @@ }, "poll_config": { "$ref": "#/components/schemas/PollConfigResponse" + }, + "message_version": { + "type": "string", + "description": "Message Version", + "nullable": true + }, + "directory_server_id": { + "type": "string", + "description": "Directory Server ID", + "nullable": true } } }, diff --git a/postman/collection-json/cybersource.postman_collection.json b/postman/collection-json/cybersource.postman_collection.json index 128789666aa5..1616eb0c445f 100644 --- a/postman/collection-json/cybersource.postman_collection.json +++ b/postman/collection-json/cybersource.postman_collection.json @@ -15757,247 +15757,7 @@ ] }, { - "name": "Scenario11-Failure card", - "item": [ - { - "name": "Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"failed\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", - " },", - " );", - "}", - "", - "// Check if error code exists", - "pm.test(\"[POST]::/payments - Content check if error code exists\", function () {", - " pm.expect(jsonData.error_code).to.not.equal(null);;", - "});", - "", - "// Check if error message exists", - "pm.test(\"[POST]::/payments - Content check if error message exists\", function () {", - " pm.expect(jsonData.error_message).to.not.equal(null);;", - "});", - "", - "", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"email\":\"guest@example.com\",\"setup_future_usage\":\"on_session\",\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"412345678912345678914\",\"card_exp_month\":\"01\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"count_tickets\":1,\"transaction_number\":\"5590045\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Retrieve", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"failed\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", - " },", - " );", - "}", - "", - "// Check if error code exists", - "pm.test(\"[POST]::/payments - Content check if error code exists\", function () {", - " pm.expect(jsonData.error_code).to.not.equal(null);;", - "});", - "", - "// Check if error message exists", - "pm.test(\"[POST]::/payments - Content check if error message exists\", function () {", - " pm.expect(jsonData.error_message).to.not.equal(null);;", - "});", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" - }, - "response": [] - } - ] - }, - { - "name": "Scenario12-Refund exceeds amount captured", + "name": "Scenario11-Refund exceeds amount captured", "item": [ { "name": "Payments - Create", @@ -16455,7 +16215,7 @@ ] }, { - "name": "Scenario13-Revoke for revoked mandates", + "name": "Scenario12-Revoke for revoked mandates", "item": [ { "name": "Payments - Create", @@ -16879,7 +16639,7 @@ ] }, { - "name": "Scenario14-Recurring payment for revoked mandates", + "name": "Scenario13-Recurring payment for revoked mandates", "item": [ { "name": "Payments - Create", @@ -17327,7 +17087,7 @@ ] }, { - "name": "Scenario15-Setup_future_usage is off_session for normal payments", + "name": "Scenario14-Setup_future_usage is off_session for normal payments", "item": [ { "name": "Payments - Create", @@ -17446,7 +17206,7 @@ ] }, { - "name": "Scenario16-Setup_future_usage is on_session for mandates payments -confirm false", + "name": "Scenario15-Setup_future_usage is on_session for mandates payments -confirm false", "item": [ { "name": "Payments - Create", @@ -17732,7 +17492,7 @@ ] }, { - "name": "Scenario17-Setup_future_usage is null for normal payments", + "name": "Scenario16-Setup_future_usage is null for normal payments", "item": [ { "name": "Payments - Create", diff --git a/postman/collection-json/trustpay.postman_collection.json b/postman/collection-json/trustpay.postman_collection.json index a9051b994ca5..5c35168dca64 100644 --- a/postman/collection-json/trustpay.postman_collection.json +++ b/postman/collection-json/trustpay.postman_collection.json @@ -3177,7 +3177,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"NL\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3347,7 +3347,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}}}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"NL\"}}}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm",