diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 61892d1b29..e9b5039532 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9098,6 +9098,15 @@ }, "additionalProperties": false }, + "CardDiscovery": { + "type": "string", + "description": "Indicates the method by which a card is discovered during a payment", + "enum": [ + "manual", + "saved_card", + "click_to_pay" + ] + }, "CardNetwork": { "type": "string", "description": "Indicates the card network.", @@ -19220,6 +19229,14 @@ "type": "string", "description": "Connector Identifier for the payment method", "nullable": true + }, + "card_discovery": { + "allOf": [ + { + "$ref": "#/components/schemas/CardDiscovery" + } + ], + "nullable": true } } }, @@ -20463,6 +20480,14 @@ "type": "string", "description": "Connector Identifier for the payment method", "nullable": true + }, + "card_discovery": { + "allOf": [ + { + "$ref": "#/components/schemas/CardDiscovery" + } + ], + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index bec25529af..47f43f982f 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4854,6 +4854,10 @@ pub struct PaymentsResponse { /// Connector Identifier for the payment method pub connector_mandate_id: Option, + + /// Method through which card was discovered + #[schema(value_type = Option, example = "manual")] + pub card_discovery: Option, } // Serialize is implemented because, this will be serialized in the api events. @@ -5552,6 +5556,8 @@ pub struct PaymentListFilterConstraints { pub card_network: Option>, /// The identifier for merchant order reference id pub merchant_order_reference_id: Option, + /// Indicates the method by which a card is discovered during a payment + pub card_discovery: Option>, } impl PaymentListFilterConstraints { @@ -5562,6 +5568,7 @@ impl PaymentListFilterConstraints { && self.authentication_type.is_none() && self.merchant_connector_id.is_none() && self.card_network.is_none() + && self.card_discovery.is_none() } } @@ -5595,6 +5602,8 @@ pub struct PaymentListFiltersV2 { pub authentication_type: Vec, /// The list of available card networks pub card_network: Vec, + /// The list of available Card discovery methods + pub card_discovery: Vec, } #[derive(Clone, Debug, serde::Serialize)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 2a5219f4de..99442d3080 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -194,6 +194,7 @@ impl AttemptStatus { serde::Serialize, strum::Display, strum::EnumString, + strum::EnumIter, ToSchema, )] #[router_derive::diesel_enum(storage_type = "db_enum")] diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 749ed41bcb..6039321196 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -424,6 +424,7 @@ impl PaymentAttempt { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, ) -> StorageResult { let mut filter = ::table() .count() @@ -450,6 +451,9 @@ impl PaymentAttempt { if let Some(card_network) = card_network { filter = filter.filter(dsl::card_network.eq_any(card_network)) } + if let Some(card_discovery) = card_discovery { + filter = filter.filter(dsl::card_discovery.eq_any(card_discovery)) + } router_env::logger::debug!(query = %debug_query::(&filter).to_string()); diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ee8b24c27b..af462443cd 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -196,6 +196,7 @@ pub trait PaymentAttemptInterface { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index cd16fbcb25..e20b086016 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1113,6 +1113,7 @@ pub struct PaymentIntentListParams { pub limit: Option, pub order: api_models::payments::Order, pub card_network: Option>, + pub card_discovery: Option>, pub merchant_order_reference_id: Option, } @@ -1148,6 +1149,7 @@ impl From for PaymentIntentFetchCo limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), order: Default::default(), card_network: None, + card_discovery: None, merchant_order_reference_id: None, })) } @@ -1174,6 +1176,7 @@ impl From for PaymentIntentFetchConstraints { limit: None, order: Default::default(), card_network: None, + card_discovery: None, merchant_order_reference_id: None, })) } @@ -1198,6 +1201,7 @@ impl From for PaymentIntentF merchant_connector_id, order, card_network, + card_discovery, merchant_order_reference_id, } = value; if let Some(payment_intent_id) = payment_id { @@ -1222,6 +1226,7 @@ impl From for PaymentIntentF limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V2)), order, card_network, + card_discovery, merchant_order_reference_id, })) } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 4a4e7f5c8e..eb5edb08fa 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -312,6 +312,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PaymentMethodStatus, api_models::enums::UIWidgetFormLayout, api_models::enums::PaymentConnectorCategory, + api_models::enums::CardDiscovery, api_models::enums::FeatureStatus, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index fd2950f506..6bee4263b9 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5182,6 +5182,7 @@ pub async fn apply_filters_on_payments( constraints.authentication_type, constraints.merchant_connector_id, constraints.card_network, + constraints.card_discovery, merchant.storage_scheme, ) .await @@ -5320,6 +5321,7 @@ pub async fn get_payment_filters( payment_method: payment_method_types_map, authentication_type: enums::AuthenticationType::iter().collect(), card_network: enums::CardNetwork::iter().collect(), + card_discovery: enums::CardDiscovery::iter().collect(), }, )) } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 3075c9f26d..81393e792b 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2496,6 +2496,7 @@ where order_tax_amount, connector_mandate_id, shipping_cost: payment_intent.shipping_cost, + card_discovery: payment_attempt.card_discovery, }; services::ApplicationResponse::JsonWithHeaders((payments_response, headers)) @@ -2752,6 +2753,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay order_tax_amount: None, connector_mandate_id:None, shipping_cost: None, + card_discovery: pa.card_discovery } } } diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 2dc7191863..5d961317a1 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1715,6 +1715,7 @@ impl PaymentAttemptInterface for KafkaStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.diesel_store @@ -1727,6 +1728,7 @@ impl PaymentAttemptInterface for KafkaStore { authentication_type, merchant_connector_id, card_network, + card_discovery, storage_scheme, ) .await diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index adfff7450b..6232bbcbf0 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -57,6 +57,7 @@ pub struct KafkaPaymentAttempt<'a> { pub profile_id: &'a id_type::ProfileId, pub organization_id: &'a id_type::OrganizationId, pub card_network: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -115,6 +116,9 @@ impl<'a> KafkaPaymentAttempt<'a> { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()), + card_discovery: attempt + .card_discovery + .map(|discovery| discovery.to_string()), } } } diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index ec67dc7595..f3fcd70184 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -58,6 +58,7 @@ pub struct KafkaPaymentAttemptEvent<'a> { pub profile_id: &'a id_type::ProfileId, pub organization_id: &'a id_type::OrganizationId, pub card_network: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -116,6 +117,9 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()), + card_discovery: attempt + .card_discovery + .map(|discovery| discovery.to_string()), } } } diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index e1fe40b420..82871fcfe2 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -451,6 +451,7 @@ async fn payments_create_core() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -715,6 +716,7 @@ async fn payments_create_core_adyen_no_redirect() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }, vec![], )); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 49d2e12b81..63df5a2dec 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -212,6 +212,7 @@ async fn payments_create_core() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }; let expected_response = @@ -485,6 +486,7 @@ async fn payments_create_core_adyen_no_redirect() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }, vec![], )); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 7bf4266bf5..c9d250d306 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -53,6 +53,7 @@ impl PaymentAttemptInterface for MockDb { _authentication_type: Option>, _merchanat_connector_id: Option>, _card_network: Option>, + _card_discovery: Option>, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { Err(StorageError::MockDbError)? diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 5b5f50f5aa..7fb1d3ab80 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -474,6 +474,7 @@ impl PaymentAttemptInterface for RouterStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = self @@ -498,6 +499,7 @@ impl PaymentAttemptInterface for RouterStore { authentication_type, merchant_connector_id, card_network, + card_discovery, ) .await .map_err(|er| { @@ -1404,6 +1406,7 @@ impl PaymentAttemptInterface for KVRouterStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.router_store @@ -1416,6 +1419,7 @@ impl PaymentAttemptInterface for KVRouterStore { authentication_type, merchant_connector_id, card_network, + card_discovery, storage_scheme, ) .await diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 5290c2bfca..a515898006 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -1044,6 +1044,11 @@ impl PaymentIntentInterface for crate::RouterStore { if let Some(card_network) = ¶ms.card_network { query = query.filter(pa_dsl::card_network.eq_any(card_network.clone())); } + + if let Some(card_discovery) = ¶ms.card_discovery { + query = query.filter(pa_dsl::card_discovery.eq_any(card_discovery.clone())); + } + query } };