Bug: add support to store signature_network and is_regulated in payment attempts



Extended the AdditionalCardInfo struct to include new fields: is_regulated (indicates if the issuer is regulated under interchange fee caps) and signature_network (the card's primary global brand).
diff --git a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql
index 16815e9a33d..ffe706cab08 100644
--- a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql
+++ b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql
@@ -45,6 +45,8 @@ CREATE TABLE payment_attempt_queue (
     `card_network` Nullable(String),
     `routing_approach` LowCardinality(Nullable(String)),
     `debit_routing_savings` Nullable(UInt32),
+    `signature_network` Nullable(String),
+    `is_issuer_regulated` Nullable(Bool),
     `sign_flag` Int8
 ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092',
 kafka_topic_list = 'hyperswitch-payment-attempt-events',
@@ -100,6 +102,8 @@ CREATE TABLE payment_attempts (
     `card_network` Nullable(String),
     `routing_approach` LowCardinality(Nullable(String)),
     `debit_routing_savings` Nullable(UInt32),
+    `signature_network` Nullable(String),
+    `is_issuer_regulated` Nullable(Bool),
     `sign_flag` Int8,
     INDEX connectorIndex connector TYPE bloom_filter GRANULARITY 1,
     INDEX paymentMethodIndex payment_method TYPE bloom_filter GRANULARITY 1,
@@ -158,6 +162,8 @@ CREATE MATERIALIZED VIEW payment_attempt_mv TO payment_attempts (
     `card_network` Nullable(String),
     `routing_approach` LowCardinality(Nullable(String)),
     `debit_routing_savings` Nullable(UInt32),
+    `signature_network` Nullable(String),
+    `is_issuer_regulated` Nullable(Bool),
     `sign_flag` Int8
 ) AS
 SELECT
@@ -208,6 +214,8 @@ SELECT
     card_network,
     routing_approach,
     debit_routing_savings,
+    signature_network,
+    is_issuer_regulated,
     sign_flag
 FROM
     payment_attempt_queue
diff --git a/crates/analytics/src/payments/accumulator.rs b/crates/analytics/src/payments/accumulator.rs
index 6d7dca22a30..a266e1366af 100644
--- a/crates/analytics/src/payments/accumulator.rs
+++ b/crates/analytics/src/payments/accumulator.rs
@@ -63,6 +63,8 @@ pub struct ProcessedAmountAccumulator {
 pub struct DebitRoutingAccumulator {
     pub transaction_count: u64,
     pub savings_amount: u64,
+    pub signature_network: Option<String>,
+    pub is_issuer_regulated: Option<bool>,
 }
 
 #[derive(Debug, Default)]
@@ -191,7 +193,13 @@ impl PaymentMetricAccumulator for SuccessRateAccumulator {
 }
 
 impl PaymentMetricAccumulator for DebitRoutingAccumulator {
-    type MetricOutput = (Option<u64>, Option<u64>, Option<u64>);
+    type MetricOutput = (
+        Option<u64>,
+        Option<u64>,
+        Option<u64>,
+        Option<String>,
+        Option<bool>,
+    );
 
     fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) {
         if let Some(count) = metrics.count {
@@ -200,6 +208,12 @@ impl PaymentMetricAccumulator for DebitRoutingAccumulator {
         if let Some(total) = metrics.total.as_ref().and_then(ToPrimitive::to_u64) {
             self.savings_amount += total;
         }
+        if let Some(signature_network) = &metrics.signature_network {
+            self.signature_network = Some(signature_network.clone());
+        }
+        if let Some(is_issuer_regulated) = metrics.is_issuer_regulated {
+            self.is_issuer_regulated = Some(is_issuer_regulated);
+        }
     }
 
     fn collect(self) -> Self::MetricOutput {
@@ -207,6 +221,8 @@ impl PaymentMetricAccumulator for DebitRoutingAccumulator {
             Some(self.transaction_count),
             Some(self.savings_amount),
             Some(0),
+            self.signature_network,
+            self.is_issuer_regulated,
         )
     }
 }
@@ -468,8 +484,13 @@ impl PaymentMetricsAccumulator {
         ) = self.payments_distribution.collect();
         let (failure_reason_count, failure_reason_count_without_smart_retries) =
             self.failure_reasons_distribution.collect();
-        let (debit_routed_transaction_count, debit_routing_savings, debit_routing_savings_in_usd) =
-            self.debit_routing.collect();
+        let (
+            debit_routed_transaction_count,
+            debit_routing_savings,
+            debit_routing_savings_in_usd,
+            signature_network,
+            is_issuer_regulated,
+        ) = self.debit_routing.collect();
 
         PaymentMetricsBucketValue {
             payment_success_rate: self.payment_success_rate.collect(),
@@ -497,6 +518,8 @@ impl PaymentMetricsAccumulator {
             debit_routed_transaction_count,
             debit_routing_savings,
             debit_routing_savings_in_usd,
+            signature_network,
+            is_issuer_regulated,
         }
     }
 }
diff --git a/crates/analytics/src/payments/metrics.rs b/crates/analytics/src/payments/metrics.rs
index b19c661322d..914eac7116b 100644
--- a/crates/analytics/src/payments/metrics.rs
+++ b/crates/analytics/src/payments/metrics.rs
@@ -53,6 +53,8 @@ pub struct PaymentMetricRow {
     pub total: Option<bigdecimal::BigDecimal>,
     pub count: Option<i64>,
     pub routing_approach: Option<DBEnumWrapper<storage_enums::RoutingApproach>>,
+    pub signature_network: Option<String>,
+    pub is_issuer_regulated: Option<bool>,
     #[serde(with = "common_utils::custom_serde::iso8601::option")]
     pub start_bucket: Option<PrimitiveDateTime>,
     #[serde(with = "common_utils::custom_serde::iso8601::option")]
diff --git a/crates/analytics/src/payments/metrics/debit_routing.rs b/crates/analytics/src/payments/metrics/debit_routing.rs
index 2caec813ec8..309b2131c63 100644
--- a/crates/analytics/src/payments/metrics/debit_routing.rs
+++ b/crates/analytics/src/payments/metrics/debit_routing.rs
@@ -56,6 +56,14 @@ where
             })
             .switch()?;
         query_builder.add_select_column("currency").switch()?;
+
+        query_builder
+            .add_select_column("signature_network")
+            .switch()?;
+        query_builder
+            .add_select_column("is_issuer_regulated")
+            .switch()?;
+
         query_builder
             .add_select_column(Aggregate::Min {
                 field: "created_at",
@@ -85,6 +93,16 @@ where
                 .switch()?;
         }
 
+        query_builder
+            .add_group_by_clause("signature_network")
+            .attach_printable("Error grouping by signature_network")
+            .switch()?;
+
+        query_builder
+            .add_group_by_clause("is_issuer_regulated")
+            .attach_printable("Error grouping by is_issuer_regulated")
+            .switch()?;
+
         query_builder
             .add_group_by_clause("currency")
             .attach_printable("Error grouping by currency")
diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs
index 10f039fcc72..38746ce5951 100644
--- a/crates/analytics/src/sqlx.rs
+++ b/crates/analytics/src/sqlx.rs
@@ -736,6 +736,16 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow {
                 ColumnNotFound(_) => Ok(Default::default()),
                 e => Err(e),
             })?;
+        let signature_network: Option<String> =
+            row.try_get("signature_network").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
+        let is_issuer_regulated: Option<bool> =
+            row.try_get("is_issuer_regulated").or_else(|e| match e {
+                ColumnNotFound(_) => Ok(Default::default()),
+                e => Err(e),
+            })?;
         let total: Option<bigdecimal::BigDecimal> = row.try_get("total").or_else(|e| match e {
             ColumnNotFound(_) => Ok(Default::default()),
             e => Err(e),
@@ -768,6 +778,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow {
             error_reason,
             first_attempt,
             routing_approach,
+            signature_network,
+            is_issuer_regulated,
             total,
             count,
             start_bucket,
diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs
index 193fdf26cc7..70583079930 100644
--- a/crates/api_models/src/analytics/payments.rs
+++ b/crates/api_models/src/analytics/payments.rs
@@ -319,6 +319,8 @@ pub struct PaymentMetricsBucketValue {
     pub debit_routed_transaction_count: Option<u64>,
     pub debit_routing_savings: Option<u64>,
     pub debit_routing_savings_in_usd: Option<u64>,
+    pub signature_network: Option<String>,
+    pub is_issuer_regulated: Option<bool>,
 }
 
 #[derive(Debug, serde::Serialize)]
diff --git a/crates/api_models/src/open_router.rs b/crates/api_models/src/open_router.rs
index 0365d4b6d50..c2e6bc76c06 100644
--- a/crates/api_models/src/open_router.rs
+++ b/crates/api_models/src/open_router.rs
@@ -124,6 +124,13 @@ impl CoBadgedCardNetworks {
     pub fn get_card_networks(&self) -> Vec<common_enums::CardNetwork> {
         self.0.iter().map(|info| info.network.clone()).collect()
     }
+
+    pub fn get_signature_network(&self) -> Option<common_enums::CardNetwork> {
+        self.0
+            .iter()
+            .find(|info| info.network.is_signature_network())
+            .map(|info| info.network.clone())
+    }
 }
 
 impl From<&DebitRoutingOutput> for payment_methods::CoBadgedCardData {
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index d3a502c877b..2e9478dc250 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -1229,6 +1229,8 @@ impl From<CardDetailFromLocker> for payments::AdditionalCardInfo {
             card_holder_name: item.card_holder_name,
             payment_checks: None,
             authentication_data: None,
+            is_regulated: None,
+            signature_network: None,
         }
     }
 }
@@ -1252,6 +1254,8 @@ impl From<CardDetailFromLocker> for payments::AdditionalCardInfo {
             card_holder_name: item.card_holder_name,
             payment_checks: None,
             authentication_data: None,
+            is_regulated: None,
+            signature_network: None,
         }
     }
 }
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 714ad7cb50a..d757c6cfb6f 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -2970,6 +2970,15 @@ pub struct AdditionalCardInfo {
     /// Details about the threeds environment.
     /// This is a free form field and the structure varies from processor to processor
     pub authentication_data: Option<serde_json::Value>,
+
+    /// Indicates if the card issuer is regulated under government-imposed interchange fee caps.
+    /// In the United States, this includes debit cards that fall under the Durbin Amendment,
+    /// which imposes capped interchange fees.
+    pub is_regulated: Option<bool>,
+
+    /// The global signature network under which the card is issued.
+    /// This represents the primary global card brand, even if the transaction uses a local network
+    pub signature_network: Option<api_enums::CardNetwork>,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index a895f0710c4..ec8d55ed000 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -2599,7 +2599,7 @@ pub enum DecisionEngineMerchantCategoryCode {
 }
 
 impl CardNetwork {
-    pub fn is_global_network(&self) -> bool {
+    pub fn is_signature_network(&self) -> bool {
         match self {
             Self::Interac
             | Self::Star
diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
index ceff3d161d6..a9d44566069 100644
--- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
+++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
@@ -37,6 +37,8 @@ use error_stack::ResultExt;
 #[cfg(feature = "v2")]
 use masking::PeekInterface;
 use masking::Secret;
+#[cfg(feature = "v1")]
+use router_env::logger;
 #[cfg(feature = "v2")]
 use rustc_hash::FxHashMap;
 #[cfg(feature = "v1")]
@@ -1109,6 +1111,19 @@ impl PaymentAttempt {
             .and_then(|data| data.get_additional_card_info())
             .and_then(|card_info| card_info.card_network)
     }
+
+    pub fn get_payment_method_data(&self) -> Option<api_models::payments::AdditionalPaymentData> {
+        self.payment_method_data
+            .clone()
+            .and_then(|data| match data {
+                serde_json::Value::Null => None,
+                _ => Some(data.parse_value("AdditionalPaymentData")),
+            })
+            .transpose()
+            .map_err(|err| logger::error!("Failed to parse AdditionalPaymentData {err:?}"))
+            .ok()
+            .flatten()
+    }
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
diff --git a/crates/router/src/core/debit_routing.rs b/crates/router/src/core/debit_routing.rs
index 0aeb2b9777b..34eaed42908 100644
--- a/crates/router/src/core/debit_routing.rs
+++ b/crates/router/src/core/debit_routing.rs
@@ -726,7 +726,7 @@ fn find_matching_networks(
     fee_sorted_debit_networks
         .iter()
         .filter(|network| merchant_debit_networks.contains(network))
-        .filter(|network| is_routing_enabled || network.is_global_network())
+        .filter(|network| is_routing_enabled || network.is_signature_network())
         .map(|network| {
             if network.is_us_local_network() {
                 *has_us_local_network = true;
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index 8dd79f95d97..c8320bcc631 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -866,7 +866,7 @@ fn get_card_network_with_us_local_debit_network_override(
                 data.co_badged_card_networks_info
                     .0
                     .iter()
-                    .find(|info| info.network.is_global_network())
+                    .find(|info| info.network.is_signature_network())
                     .cloned()
             });
         info.map(|data| data.network)
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 87f47a9b2bb..1523a4b3c2b 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -4867,19 +4867,30 @@ pub async fn get_additional_payment_data(
                     "Card cobadge check failed due to an invalid card network regex",
                 )?;
 
-            let card_network = match (
-                is_cobadged_based_on_regex,
-                card_data.co_badged_card_data.is_some(),
-            ) {
-                (false, false) => {
+            let (card_network, signature_network, is_regulated) = card_data
+                .co_badged_card_data
+                .as_ref()
+                .map(|co_badged_data| {
+                    logger::debug!("Co-badged card data found");
+
+                    (
+                        card_data.card_network.clone(),
+                        co_badged_data
+                            .co_badged_card_networks_info
+                            .get_signature_network(),
+                        Some(co_badged_data.is_regulated),
+                    )
+                })
+                .or_else(|| {
+                    is_cobadged_based_on_regex.then(|| {
+                        logger::debug!("Card network is cobadged (regex-based detection)");
+                        (card_data.card_network.clone(), None, None)
+                    })
+                })
+                .unwrap_or_else(|| {
                     logger::debug!("Card network is not cobadged");
-                    None
-                }
-                _ => {
-                    logger::debug!("Card network is cobadged");
-                    card_data.card_network.clone()
-                }
-            };
+                    (None, None, None)
+                });
 
             let last4 = Some(card_data.card_number.get_last4());
             if card_data.card_issuer.is_some()
@@ -4904,6 +4915,8 @@ pub async fn get_additional_payment_data(
                         // These are filled after calling the processor / connector
                         payment_checks: None,
                         authentication_data: None,
+                        is_regulated,
+                        signature_network: signature_network.clone(),
                     }),
                 )))
             } else {
@@ -4934,6 +4947,8 @@ pub async fn get_additional_payment_data(
                                 // These are filled after calling the processor / connector
                                 payment_checks: None,
                                 authentication_data: None,
+                                is_regulated,
+                                signature_network: signature_network.clone(),
                             },
                         ))
                     });
@@ -4954,6 +4969,8 @@ pub async fn get_additional_payment_data(
                             // These are filled after calling the processor / connector
                             payment_checks: None,
                             authentication_data: None,
+                            is_regulated,
+                            signature_network: signature_network.clone(),
                         },
                     ))
                 })))
@@ -5199,6 +5216,8 @@ pub async fn get_additional_payment_data(
                         // These are filled after calling the processor / connector
                         payment_checks: None,
                         authentication_data: None,
+                        is_regulated: None,
+                        signature_network: None,
                     }),
                 )))
             } else {
@@ -5229,6 +5248,8 @@ pub async fn get_additional_payment_data(
                                 // These are filled after calling the processor / connector
                                 payment_checks: None,
                                 authentication_data: None,
+                                is_regulated: None,
+                                signature_network: None,
                             },
                         ))
                     });
@@ -5249,6 +5270,8 @@ pub async fn get_additional_payment_data(
                             // These are filled after calling the processor / connector
                             payment_checks: None,
                             authentication_data: None,
+                            is_regulated: None,
+                            signature_network: None,
                         },
                     ))
                 })))
diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs
index 8a900019b7c..0d8dfd8704a 100644
--- a/crates/router/src/services/kafka/payment_attempt.rs
+++ b/crates/router/src/services/kafka/payment_attempt.rs
@@ -71,11 +71,16 @@ pub struct KafkaPaymentAttempt<'a> {
     pub card_discovery: Option<String>,
     pub routing_approach: Option<storage_enums::RoutingApproach>,
     pub debit_routing_savings: Option<MinorUnit>,
+    pub signature_network: Option<common_enums::CardNetwork>,
+    pub is_issuer_regulated: Option<bool>,
 }
 
 #[cfg(feature = "v1")]
 impl<'a> KafkaPaymentAttempt<'a> {
     pub fn from_storage(attempt: &'a PaymentAttempt) -> Self {
+        let card_payment_method_data = attempt
+            .get_payment_method_data()
+            .and_then(|data| data.get_additional_card_info());
         Self {
             payment_id: &attempt.payment_id,
             merchant_id: &attempt.merchant_id,
@@ -134,6 +139,10 @@ impl<'a> KafkaPaymentAttempt<'a> {
                 .map(|discovery| discovery.to_string()),
             routing_approach: attempt.routing_approach.clone(),
             debit_routing_savings: attempt.debit_routing_savings,
+            signature_network: card_payment_method_data
+                .as_ref()
+                .and_then(|data| data.signature_network.clone()),
+            is_issuer_regulated: card_payment_method_data.and_then(|data| data.is_regulated),
         }
     }
 }
diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs
index eea3916f803..5f87e017f85 100644
--- a/crates/router/src/services/kafka/payment_attempt_event.rs
+++ b/crates/router/src/services/kafka/payment_attempt_event.rs
@@ -72,11 +72,16 @@ pub struct KafkaPaymentAttemptEvent<'a> {
     pub card_discovery: Option<String>,
     pub routing_approach: Option<storage_enums::RoutingApproach>,
     pub debit_routing_savings: Option<MinorUnit>,
+    pub signature_network: Option<common_enums::CardNetwork>,
+    pub is_issuer_regulated: Option<bool>,
 }
 
 #[cfg(feature = "v1")]
 impl<'a> KafkaPaymentAttemptEvent<'a> {
     pub fn from_storage(attempt: &'a PaymentAttempt) -> Self {
+        let card_payment_method_data = attempt
+            .get_payment_method_data()
+            .and_then(|data| data.get_additional_card_info());
         Self {
             payment_id: &attempt.payment_id,
             merchant_id: &attempt.merchant_id,
@@ -135,6 +140,10 @@ impl<'a> KafkaPaymentAttemptEvent<'a> {
                 .map(|discovery| discovery.to_string()),
             routing_approach: attempt.routing_approach.clone(),
             debit_routing_savings: attempt.debit_routing_savings,
+            signature_network: card_payment_method_data
+                .as_ref()
+                .and_then(|data| data.signature_network.clone()),
+            is_issuer_regulated: card_payment_method_data.and_then(|data| data.is_regulated),
         }
     }
 }

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This pull request enhances how co-badged card information is handled, especially around identifying and propagating the card's "signature network" and whether the card issuer is regulated. 

* Extended the `AdditionalCardInfo` struct to include new fields: `is_regulated` (indicates if the issuer is regulated under interchange fee caps) and `signature_network` (the card's primary global brand).

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
-> Enable debit routing for a profile
-> Create adyen connector and enable local debit networks
-> Make a card payment
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-feature: router-custom' \
--header 'x-tenant-id: hyperswitch' \
--header 'api-key: dev_5sRc7V4OXL2Qk52mr4qp5JLcgLPu3TlNOZ8M31eALWZcuOFaXhZX4FAZVT8VKgbX' \
--data-raw '{
    "amount": 1000000,
    "amount_to_capture": 1000000,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "setup_future_usage": "on_session",
    "capture_on": "2022-09-10T10:11:12Z",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "customer_id": "cu_1754896317",
    "return_url": "http://127.0.0.1:4040",
    "payment_method": "card",
    "payment_method_type": "debit",
    "payment_method_data": {
        "card": {
            "card_number": "4400 0020 0000 0004",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "737"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        },
        "email": "raj@gmail.com"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "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": "125.0.0.1"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
```
{
    "payment_id": "pay_C6Da6MjduSo3GYZD5Rv4",
    "merchant_id": "merchant_1754887772",
    "status": "processing",
    "amount": 1000000,
    "net_amount": 1000000,
    "shipping_cost": null,
    "amount_capturable": 1000000,
    "amount_received": null,
    "connector": "adyen",
    "client_secret": "pay_C6Da6MjduSo3GYZD5Rv4_secret_OnariiuxjA1PeO2GOpKW",
    "created": "2025-08-11T04:51:47.628Z",
    "currency": "USD",
    "customer_id": "cu_1754887908",
    "customer": {
        "id": "cu_1754887908",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0004",
            "card_type": null,
            "card_network": "Accel",
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "440000",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "San",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": "ss",
            "origin_zip": null
        },
        "phone": null,
        "email": "raj@gmail.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "http://127.0.0.1:4040/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": "905_1",
    "error_message": "Could not find an acquirer account for the provided txvariant (accel), currency (USD), and action (AUTH).",
    "unified_code": "UE_9000",
    "unified_message": "Something went wrong",
    "payment_experience": null,
    "payment_method_type": "debit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "cu_1754887908",
        "created_at": 1754887907,
        "expires": 1754891507,
        "secret": "epk_779c3dcfdc894107918575b408446962"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "SCTLC5JVNSRZ5TT5",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_UsCIuOTX3On1bJUP7Dw3",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_HWuMPavh4o8pMXXypjKT",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-11T05:06:47.628Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "125.0.0.1",
        "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",
        "color_depth": 24,
        "java_enabled": true,
        "screen_width": 1536,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 723,
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-08-11T04:51:48.673Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": null
}
```
Payment attempt db storage
<img width="2206" height="891" alt="image" src="https://github.com/user-attachments/assets/d6cbb348-2f49-4603-b10a-a684df003232" />

-> Payment metric
```
curl --location 'http://localhost:8080/analytics/v1/org/metrics/payments' \
--header 'Accept: */*' \
--header 'Accept-Language: en-US,en;q=0.9' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:9000' \
--header 'Referer: http://localhost:9000/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: same-site' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' \
--header 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWQxMGYxOGUtZjMzNy00NDU5LWFhNTEtOTE3YWRiYTlmYWJiIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzU1MDA1MTY4Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc1NTE3Nzk4Miwib3JnX2lkIjoib3JnX2NmYTlEaTJwdEN2YjFHU05jOXN1IiwicHJvZmlsZV9pZCI6InByb19NYTRrVzJjOFpobDJEeUFBQVZrTyIsInRlbmFudF9pZCI6InB1YmxpYyJ9.lP65o0BdE7w9La48HPn2MasV9MZaAaBp_WWI7A7iUdU' \
--data '[
    {
        "timeRange": {
            "startTime": "2025-08-01T18:30:00Z",
            "endTime": "2025-09-30T09:22:00Z"
        },
        "groupByNames": [
            "card_network"
        ],
        
        
        "filters": {
          "routing_approach": [
            "debit_routing"
          ]
        },
        "source": "BATCH",
        "metrics": [
            "debit_routing"
        ],
        
        
        
        "delta": true
    }
]'
```
```
{
    "queryData": [
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 0,
            "payment_processed_amount_in_usd": null,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": null,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "debit_routed_transaction_count": 1,
            "debit_routing_savings": 0,
            "debit_routing_savings_in_usd": null,
            "signature_network": "Visa",
            "is_issuer_regulated": true,
            "currency": "USD",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": "Accel",
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "routing_approach": null,
            "time_range": {
                "start_time": "2025-08-01T18:30:00.000Z",
                "end_time": "2025-09-30T09:22:00.000Z"
            },
            "time_bucket": "2025-08-01 18:30:00"
        },
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 0,
            "payment_processed_amount_in_usd": null,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": null,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "debit_routed_transaction_count": 4,
            "debit_routing_savings": 0,
            "debit_routing_savings_in_usd": null,
            "signature_network": "Visa",
            "is_issuer_regulated": true,
            "currency": "USD",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": "Star",
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "routing_approach": null,
            "time_range": {
                "start_time": "2025-08-01T18:30:00.000Z",
                "end_time": "2025-09-30T09:22:00.000Z"
            },
            "time_bucket": "2025-08-01 18:30:00"
        }
    ],
    "metaData": [
        {
            "total_payment_processed_amount": 0,
            "total_payment_processed_amount_in_usd": null,
            "total_payment_processed_amount_without_smart_retries": 0,
            "total_payment_processed_amount_without_smart_retries_usd": null,
            "total_payment_processed_count": 0,
            "total_payment_processed_count_without_smart_retries": 0,
            "total_failure_reasons_count": 0,
            "total_failure_reasons_count_without_smart_retries": 0
        }
    ]
}
```



## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
