Bug: [FEATURE] Add Gateway System Tracking in Feature Metadata for Routing Stickiness



# Add Gateway System Tracking in Feature Metadata for Routing Stickiness

  ## Problem Statement

  Currently, payment routing decisions between Direct routing (hyperswitch) and Unified Connector Service (UCS) are made
  independently for each payment operation. This creates inconsistency issues where different operations for the same payment
  intent may be routed through different gateway systems.

  ### Current Behavior
  - Payment creation may route through Direct routing
  - Payment confirmation may route through UCS
  - Payment sync may route through Direct routing again
  - Each operation makes routing decisions without considering previous choices

  ### Impact
  1. **Payment State Inconsistency**: Different operations using different gateway systems can lead to conflicting payment
  states
  2. **Routing Conflicts**: Operations may fail due to gateway system mismatches
  3. **Debugging Complexity**: Payment flows become difficult to trace when they span multiple gateway systems
  4. **Potential Data Loss**: Inconsistent routing may cause payment data to be scattered across systems
  5. **Merchant Experience**: Unpredictable payment behavior affects merchant confidence

  ## Proposed Solution

  Implement routing stickiness by tracking the gateway system used for initial payment routing and ensuring all subsequent
  operations use the same system.

  ### Technical Approach
  - Add `GatewaySystem` enum to track routing decisions (Direct vs UCS)
  - Store gateway system information in payment intent's feature metadata
  - Implement routing logic that considers previous gateway system usage
  - Add comprehensive logging for payment routing decisions
  - Update all payment operations to preserve routing consistency

  ### Key Components
  1. **Gateway System Enum**: Define clear categorization of routing types
  2. **Feature Metadata Storage**: Persist routing decisions for stickiness
  3. **Routing Logic Enhancement**: Consider previous decisions in routing algorithms
  4. **Observability**: Add logging for debugging and monitoring
  5. **Data Consistency**: Ensure metadata is properly maintained across operations
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 41938fc23d3..32b6cdf4116 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -2220,6 +2220,34 @@ pub enum PaymentMethod {
     MobilePayment,
 }
 
+/// Indicates the gateway system through which the payment is processed.
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Default,
+    Eq,
+    PartialOrd,
+    Ord,
+    Hash,
+    PartialEq,
+    serde::Deserialize,
+    serde::Serialize,
+    strum::Display,
+    strum::VariantNames,
+    strum::EnumIter,
+    strum::EnumString,
+    ToSchema,
+)]
+#[router_derive::diesel_enum(storage_type = "text")]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
+pub enum GatewaySystem {
+    #[default]
+    Direct,
+    UnifiedConnectorService,
+}
+
 /// The type of the payment that differentiates between normal and various types of mandate payments. Use 'setup_mandate' in case of zero auth flow.
 #[derive(
     Clone,
diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs
index 0f56be31309..acc8d4b21f8 100644
--- a/crates/diesel_models/src/payment_intent.rs
+++ b/crates/diesel_models/src/payment_intent.rs
@@ -2,6 +2,7 @@ use common_enums::{PaymentMethodType, RequestIncrementalAuthorization};
 use common_types::primitive_wrappers::RequestExtendedAuthorizationBool;
 use common_utils::{encryption::Encryption, pii, types::MinorUnit};
 use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
+use masking::ExposeInterface;
 use serde::{Deserialize, Serialize};
 use time::PrimitiveDateTime;
 
@@ -492,6 +493,7 @@ pub enum PaymentIntentUpdate {
         fingerprint_id: Option<String>,
         updated_by: String,
         incremental_authorization_allowed: Option<bool>,
+        feature_metadata: Option<masking::Secret<serde_json::Value>>,
     },
     MetadataUpdate {
         metadata: serde_json::Value,
@@ -517,6 +519,7 @@ pub enum PaymentIntentUpdate {
         status: storage_enums::IntentStatus,
         updated_by: String,
         incremental_authorization_allowed: Option<bool>,
+        feature_metadata: Option<masking::Secret<serde_json::Value>>,
     },
     PaymentAttemptAndAttemptCountUpdate {
         active_attempt_id: String,
@@ -625,6 +628,7 @@ pub struct PaymentIntentUpdateFields {
     pub force_3ds_challenge: Option<bool>,
     pub is_iframe_redirection_enabled: Option<bool>,
     pub payment_channel: Option<common_enums::PaymentChannel>,
+    pub feature_metadata: Option<masking::Secret<serde_json::Value>>,
     pub tax_status: Option<common_enums::TaxStatus>,
     pub discount_amount: Option<MinorUnit>,
     pub order_date: Option<PrimitiveDateTime>,
@@ -845,6 +849,7 @@ pub struct PaymentIntentUpdateInternal {
     pub is_iframe_redirection_enabled: Option<bool>,
     pub extended_return_url: Option<String>,
     pub payment_channel: Option<common_enums::PaymentChannel>,
+    pub feature_metadata: Option<masking::Secret<serde_json::Value>>,
     pub tax_status: Option<common_enums::TaxStatus>,
     pub discount_amount: Option<MinorUnit>,
     pub order_date: Option<PrimitiveDateTime>,
@@ -897,6 +902,7 @@ impl PaymentIntentUpdate {
             is_iframe_redirection_enabled,
             extended_return_url,
             payment_channel,
+            feature_metadata,
             tax_status,
             discount_amount,
             order_date,
@@ -953,6 +959,9 @@ impl PaymentIntentUpdate {
                 .or(source.is_iframe_redirection_enabled),
             extended_return_url: extended_return_url.or(source.extended_return_url),
             payment_channel: payment_channel.or(source.payment_channel),
+            feature_metadata: feature_metadata
+                .map(|value| value.expose())
+                .or(source.feature_metadata),
             tax_status: tax_status.or(source.tax_status),
             discount_amount: discount_amount.or(source.discount_amount),
             order_date: order_date.or(source.order_date),
@@ -1013,6 +1022,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1062,6 +1072,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: value.is_iframe_redirection_enabled,
                 extended_return_url: value.return_url,
                 payment_channel: value.payment_channel,
+                feature_metadata: value.feature_metadata,
                 tax_status: value.tax_status,
                 discount_amount: value.discount_amount,
                 order_date: value.order_date,
@@ -1118,6 +1129,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: return_url,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1129,6 +1141,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 status,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self {
                 status: Some(status),
                 modified_at: common_utils::date_time::now(),
@@ -1170,6 +1183,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1223,6 +1237,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1239,6 +1254,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 // customer_id,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self {
                 // amount,
                 // currency: Some(currency),
@@ -1283,6 +1299,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1335,6 +1352,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1388,6 +1406,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1440,6 +1459,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1492,6 +1512,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1543,6 +1564,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1591,6 +1613,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1641,6 +1664,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1691,6 +1715,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1739,6 +1764,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
@@ -1792,6 +1818,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 is_iframe_redirection_enabled: None,
                 extended_return_url: None,
                 payment_channel: None,
+                feature_metadata: None,
                 tax_status: None,
                 discount_amount: None,
                 order_date: None,
diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs
index 0cdb553059c..a13e1c7f265 100644
--- a/crates/diesel_models/src/types.rs
+++ b/crates/diesel_models/src/types.rs
@@ -101,7 +101,7 @@ impl FeatureMetadata {
 }
 
 #[cfg(feature = "v1")]
-#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)]
+#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)]
 #[diesel(sql_type = Json)]
 pub struct FeatureMetadata {
     /// Redirection response coming in request as metadata field only for redirection scenarios
@@ -110,6 +110,8 @@ pub struct FeatureMetadata {
     pub search_tags: Option<Vec<HashedString<WithType>>>,
     /// Recurring payment details required for apple pay Merchant Token
     pub apple_pay_recurring_details: Option<ApplePayRecurringDetails>,
+    /// The system that the gateway is integrated with, e.g., `Direct`(through hyperswitch), `UnifiedConnectorService`(through ucs), etc.
+    pub gateway_system: Option<common_enums::GatewaySystem>,
 }
 
 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)]
diff --git a/crates/external_services/src/grpc_client/unified_connector_service.rs b/crates/external_services/src/grpc_client/unified_connector_service.rs
index 4f6313ea31a..699d8dad7ee 100644
--- a/crates/external_services/src/grpc_client/unified_connector_service.rs
+++ b/crates/external_services/src/grpc_client/unified_connector_service.rs
@@ -186,7 +186,10 @@ impl UnifiedConnectorServiceClient {
                     }
                 }
             }
-            None => None,
+            None => {
+                router_env::logger::error!(?config.unified_connector_service, "Unified Connector Service config is missing");
+                None
+            }
         }
     }
 
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index dac5aa13410..70f7c4830cf 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -121,6 +121,7 @@ impl ApiModelToDieselModelConvertor<ApiFeatureMetadata> for FeatureMetadata {
             search_tags,
             apple_pay_recurring_details: apple_pay_recurring_details
                 .map(ApplePayRecurringDetails::convert_from),
+            gateway_system: None,
         }
     }
 
@@ -129,6 +130,7 @@ impl ApiModelToDieselModelConvertor<ApiFeatureMetadata> for FeatureMetadata {
             redirect_response,
             search_tags,
             apple_pay_recurring_details,
+            ..
         } = self;
 
         ApiFeatureMetadata {
diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
index 55f8252ac21..60158ebdc12 100644
--- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
+++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs
@@ -249,6 +249,7 @@ pub struct PaymentIntentUpdateFields {
     pub duty_amount: Option<MinorUnit>,
     pub is_confirm_operation: bool,
     pub payment_channel: Option<common_enums::PaymentChannel>,
+    pub feature_metadata: Option<Secret<serde_json::Value>>,
     pub enable_partial_authorization: Option<bool>,
 }
 
@@ -261,6 +262,7 @@ pub enum PaymentIntentUpdate {
         updated_by: String,
         fingerprint_id: Option<String>,
         incremental_authorization_allowed: Option<bool>,
+        feature_metadata: Option<Secret<serde_json::Value>>,
     },
     MetadataUpdate {
         metadata: serde_json::Value,
@@ -286,6 +288,7 @@ pub enum PaymentIntentUpdate {
         status: common_enums::IntentStatus,
         incremental_authorization_allowed: Option<bool>,
         updated_by: String,
+        feature_metadata: Option<Secret<serde_json::Value>>,
     },
     PaymentAttemptAndAttemptCountUpdate {
         active_attempt_id: String,
@@ -437,6 +440,7 @@ pub struct PaymentIntentUpdateInternal {
     pub force_3ds_challenge: Option<bool>,
     pub is_iframe_redirection_enabled: Option<bool>,
     pub payment_channel: Option<common_enums::PaymentChannel>,
+    pub feature_metadata: Option<Secret<serde_json::Value>>,
     pub tax_status: Option<common_enums::TaxStatus>,
     pub discount_amount: Option<MinorUnit>,
     pub order_date: Option<PrimitiveDateTime>,
@@ -874,11 +878,13 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 status,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self {
                 status: Some(status),
                 modified_at: Some(common_utils::date_time::now()),
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
                 ..Default::default()
             },
             PaymentIntentUpdate::MerchantStatusUpdate {
@@ -903,6 +909,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 // customer_id,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self {
                 // amount,
                 // currency: Some(currency),
@@ -913,6 +920,7 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
                 modified_at: Some(common_utils::date_time::now()),
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
                 ..Default::default()
             },
             PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate {
@@ -1034,12 +1042,14 @@ impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
                 fingerprint_id,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self::ResponseUpdate {
                 status,
                 amount_captured,
                 fingerprint_id,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             },
             PaymentIntentUpdate::MetadataUpdate {
                 metadata,
@@ -1081,6 +1091,7 @@ impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
                     force_3ds_challenge: value.force_3ds_challenge,
                     is_iframe_redirection_enabled: value.is_iframe_redirection_enabled,
                     payment_channel: value.payment_channel,
+                    feature_metadata: value.feature_metadata,
                     tax_status: value.tax_status,
                     discount_amount: value.discount_amount,
                     order_date: value.order_date,
@@ -1121,10 +1132,12 @@ impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
                 status,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             } => Self::PGStatusUpdate {
                 status,
                 updated_by,
                 incremental_authorization_allowed,
+                feature_metadata,
             },
             PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate {
                 active_attempt_id,
@@ -1246,6 +1259,7 @@ impl From<PaymentIntentUpdateInternal> for diesel_models::PaymentIntentUpdateInt
             force_3ds_challenge,
             is_iframe_redirection_enabled,
             payment_channel,
+            feature_metadata,
             tax_status,
             discount_amount,
             order_date,
@@ -1294,6 +1308,7 @@ impl From<PaymentIntentUpdateInternal> for diesel_models::PaymentIntentUpdateInt
             is_iframe_redirection_enabled,
             extended_return_url: return_url,
             payment_channel,
+            feature_metadata,
             tax_status,
             discount_amount,
             order_date,
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index b31550d4122..2637e134690 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -34,7 +34,7 @@ use api_models::{
     mandates::RecurringDetails,
     payments::{self as payments_api},
 };
-pub use common_enums::enums::CallConnectorAction;
+pub use common_enums::enums::{CallConnectorAction, GatewaySystem};
 use common_types::payments as common_payments_types;
 use common_utils::{
     ext_traits::{AsyncExt, StringExt},
@@ -91,6 +91,8 @@ use self::{
     operations::{BoxedOperation, Operation, PaymentResponse},
     routing::{self as self_routing, SessionFlowRoutingInput},
 };
+#[cfg(feature = "v1")]
+use super::unified_connector_service::update_gateway_system_in_feature_metadata;
 use super::{
     errors::StorageErrorExt, payment_methods::surcharge_decision_configs, routing::TransactionData,
     unified_connector_service::should_call_unified_connector_service,
@@ -4043,7 +4045,22 @@ where
         services::api::ConnectorIntegration<F, RouterDReq, router_types::PaymentsResponseData>,
 {
     record_time_taken_with(|| async {
-        if should_call_unified_connector_service(state, merchant_context, &router_data).await? {
+        if should_call_unified_connector_service(
+            state,
+            merchant_context,
+            &router_data,
+            Some(payment_data),
+        )
+        .await?
+        {
+            router_env::logger::info!(
+                "Processing payment through UCS gateway system - payment_id={}, attempt_id={}",
+                payment_data
+                    .get_payment_intent()
+                    .payment_id
+                    .get_string_repr(),
+                payment_data.get_payment_attempt().attempt_id
+            );
             if should_add_task_to_process_tracker(payment_data) {
                 operation
                     .to_domain()?
@@ -4058,6 +4075,12 @@ where
                     .ok();
             }
 
+            // Update feature metadata to track UCS usage for stickiness
+            update_gateway_system_in_feature_metadata(
+                payment_data,
+                GatewaySystem::UnifiedConnectorService,
+            )?;
+
             (_, *payment_data) = operation
                 .to_update_tracker()?
                 .update_trackers(
@@ -4083,6 +4106,18 @@ where
 
             Ok((router_data, merchant_connector_account))
         } else {
+            router_env::logger::info!(
+                "Processing payment through Direct gateway system - payment_id={}, attempt_id={}",
+                payment_data
+                    .get_payment_intent()
+                    .payment_id
+                    .get_string_repr(),
+                payment_data.get_payment_attempt().attempt_id
+            );
+
+            // Update feature metadata to track Direct routing usage for stickiness
+            update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?;
+
             call_connector_service(
                 state,
                 req_state,
@@ -4440,8 +4475,13 @@ where
         .await?;
 
     // do order creation
-    let should_call_unified_connector_service =
-        should_call_unified_connector_service(state, merchant_context, &router_data).await?;
+    let should_call_unified_connector_service = should_call_unified_connector_service(
+        state,
+        merchant_context,
+        &router_data,
+        Some(payment_data),
+    )
+    .await?;
 
     let (connector_request, should_continue_further) = if !should_call_unified_connector_service {
         let mut should_continue_further = true;
@@ -4501,6 +4541,12 @@ where
 
     record_time_taken_with(|| async {
         if should_call_unified_connector_service {
+            router_env::logger::info!(
+                "Processing payment through UCS gateway system- payment_id={}, attempt_id={}",
+                payment_data.get_payment_intent().id.get_string_repr(),
+                payment_data.get_payment_attempt().id.get_string_repr()
+            );
+
             router_data
                 .call_unified_connector_service(
                     state,
@@ -4556,7 +4602,19 @@ where
         services::api::ConnectorIntegration<F, RouterDReq, router_types::PaymentsResponseData>,
 {
     record_time_taken_with(|| async {
-        if should_call_unified_connector_service(state, merchant_context, &router_data).await? {
+        if should_call_unified_connector_service(
+            state,
+            merchant_context,
+            &router_data,
+            Some(payment_data),
+        )
+        .await?
+        {
+            router_env::logger::info!(
+                "Executing payment through UCS gateway system - payment_id={}, attempt_id={}",
+                payment_data.get_payment_intent().id.get_string_repr(),
+                payment_data.get_payment_attempt().id.get_string_repr()
+            );
             if should_add_task_to_process_tracker(payment_data) {
                 operation
                     .to_domain()?
@@ -4596,6 +4654,12 @@ where
 
             Ok(router_data)
         } else {
+            router_env::logger::info!(
+                "Processing payment through Direct gateway system - payment_id={}, attempt_id={}",
+                payment_data.get_payment_intent().id.get_string_repr(),
+                payment_data.get_payment_attempt().id.get_string_repr()
+            );
+
             call_connector_service(
                 state,
                 req_state,
diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs
index dfe3657b0f7..92c84985c9d 100644
--- a/crates/router/src/core/payments/operations/payment_cancel.rs
+++ b/crates/router/src/core/payments/operations/payment_cancel.rs
@@ -256,6 +256,11 @@ impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsCancelReques
                     status: enums::IntentStatus::Cancelled,
                     updated_by: storage_scheme.to_string(),
                     incremental_authorization_allowed: None,
+                    feature_metadata: payment_data
+                        .payment_intent
+                        .feature_metadata
+                        .clone()
+                        .map(masking::Secret::new),
                 };
                 (Some(payment_intent_update), enums::AttemptStatus::Voided)
             } else {
diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs
index 9b2f2d85e8e..7cc48fd6f44 100644
--- a/crates/router/src/core/payments/operations/payment_confirm.rs
+++ b/crates/router/src/core/payments/operations/payment_confirm.rs
@@ -2056,6 +2056,11 @@ impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for
                             .is_iframe_redirection_enabled,
                         is_confirm_operation: true, // Indicates that this is a confirm operation
                         payment_channel: payment_data.payment_intent.payment_channel,
+                        feature_metadata: payment_data
+                            .payment_intent
+                            .feature_metadata
+                            .clone()
+                            .map(masking::Secret::new),
                         tax_status: payment_data.payment_intent.tax_status,
                         discount_amount: payment_data.payment_intent.discount_amount,
                         order_date: payment_data.payment_intent.order_date,
diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs
index 450ca96630f..29a1e43ec96 100644
--- a/crates/router/src/core/payments/operations/payment_response.rs
+++ b/crates/router/src/core/payments/operations/payment_response.rs
@@ -2113,6 +2113,11 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
             updated_by: storage_scheme.to_string(),
             // make this false only if initial payment fails, if incremental authorization call fails don't make it false
             incremental_authorization_allowed: Some(false),
+            feature_metadata: payment_data
+                .payment_intent
+                .feature_metadata
+                .clone()
+                .map(masking::Secret::new),
         },
         Ok(_) => storage::PaymentIntentUpdate::ResponseUpdate {
             status: api_models::enums::IntentStatus::foreign_from(
@@ -2124,6 +2129,11 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
             incremental_authorization_allowed: payment_data
                 .payment_intent
                 .incremental_authorization_allowed,
+            feature_metadata: payment_data
+                .payment_intent
+                .feature_metadata
+                .clone()
+                .map(masking::Secret::new),
         },
     };
 
diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs
index e2e4fb824bd..6026a82e0d4 100644
--- a/crates/router/src/core/payments/operations/payment_update.rs
+++ b/crates/router/src/core/payments/operations/payment_update.rs
@@ -953,6 +953,11 @@ impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for
                         .is_iframe_redirection_enabled,
                     is_confirm_operation: false, // this is not a confirm operation
                     payment_channel: payment_data.payment_intent.payment_channel,
+                    feature_metadata: payment_data
+                        .payment_intent
+                        .feature_metadata
+                        .clone()
+                        .map(masking::Secret::new),
                     tax_status: payment_data.payment_intent.tax_status,
                     discount_amount: payment_data.payment_intent.discount_amount,
                     order_date: payment_data.payment_intent.order_date,
diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs
index 4e32095dea7..b00440e89f3 100644
--- a/crates/router/src/core/unified_connector_service.rs
+++ b/crates/router/src/core/unified_connector_service.rs
@@ -1,6 +1,7 @@
 use api_models::admin;
-use common_enums::{AttemptStatus, PaymentMethodType};
+use common_enums::{AttemptStatus, GatewaySystem, PaymentMethodType};
 use common_utils::{errors::CustomResult, ext_traits::ValueExt};
+use diesel_models::types::FeatureMetadata;
 use error_stack::ResultExt;
 use external_services::grpc_client::unified_connector_service::{
     ConnectorAuthMetadata, UnifiedConnectorServiceError,
@@ -23,9 +24,12 @@ use unified_connector_service_client::payments::{
 use crate::{
     consts,
     core::{
-        errors::{ApiErrorResponse, RouterResult},
-        payments::helpers::{
-            is_ucs_enabled, should_execute_based_on_rollout, MerchantConnectorAccountType,
+        errors::{self, RouterResult},
+        payments::{
+            helpers::{
+                is_ucs_enabled, should_execute_based_on_rollout, MerchantConnectorAccountType,
+            },
+            OperationSessionGetters, OperationSessionSetters,
         },
         utils::get_flow_name,
     },
@@ -39,21 +43,62 @@ pub mod transformers;
 // Re-export webhook transformer types for easier access
 pub use transformers::WebhookTransformData;
 
-pub async fn should_call_unified_connector_service<F: Clone, T>(
+/// Generic version of should_call_unified_connector_service that works with any type
+/// implementing OperationSessionGetters trait
+pub async fn should_call_unified_connector_service<F: Clone, T, D>(
     state: &SessionState,
     merchant_context: &MerchantContext,
     router_data: &RouterData<F, T, PaymentsResponseData>,
-) -> RouterResult<bool> {
+    payment_data: Option<&D>,
+) -> RouterResult<bool>
+where
+    D: OperationSessionGetters<F>,
+{
+    // Check basic UCS availability first
     if state.grpc_client.unified_connector_service_client.is_none() {
+        router_env::logger::debug!(
+            "Unified Connector Service client is not available, skipping UCS decision"
+        );
         return Ok(false);
     }
 
     let ucs_config_key = consts::UCS_ENABLED;
-
     if !is_ucs_enabled(state, ucs_config_key).await {
+        router_env::logger::debug!(
+            "Unified Connector Service is not enabled, skipping UCS decision"
+        );
         return Ok(false);
     }
 
+    // Apply stickiness logic if payment_data is available
+    if let Some(payment_data) = payment_data {
+        let previous_gateway_system = extract_gateway_system_from_payment_intent(payment_data);
+
+        match previous_gateway_system {
+            Some(GatewaySystem::UnifiedConnectorService) => {
+                // Payment intent previously used UCS, maintain stickiness to UCS
+                router_env::logger::info!(
+                    "Payment gateway system decision: UCS (sticky) - payment intent previously used UCS"
+                );
+                return Ok(true);
+            }
+            Some(GatewaySystem::Direct) => {
+                // Payment intent previously used Direct, maintain stickiness to Direct (return false for UCS)
+                router_env::logger::info!(
+                    "Payment gateway system decision: Direct (sticky) - payment intent previously used Direct"
+                );
+                return Ok(false);
+            }
+            None => {
+                // No previous gateway system set, continue with normal gateway system logic
+                router_env::logger::debug!(
+                    "UCS stickiness: No previous gateway system set, applying normal gateway system logic"
+                );
+            }
+        }
+    }
+
+    // Continue with normal UCS gateway system logic
     let merchant_id = merchant_context
         .get_merchant_account()
         .get_id()
@@ -71,8 +116,13 @@ pub async fn should_call_unified_connector_service<F: Clone, T>(
         .is_some_and(|config| config.ucs_only_connectors.contains(&connector_name));
 
     if is_ucs_only_connector {
+        router_env::logger::info!(
+            "Payment gateway system decision: UCS (forced) - merchant_id={}, connector={}, payment_method={}, flow={}",
+            merchant_id, connector_name, payment_method, flow_name
+        );
         return Ok(true);
     }
+
     let config_key = format!(
         "{}_{}_{}_{}_{}",
         consts::UCS_ROLLOUT_PERCENT_CONFIG_PREFIX,
@@ -83,9 +133,87 @@ pub async fn should_call_unified_connector_service<F: Clone, T>(
     );
 
     let should_execute = should_execute_based_on_rollout(state, &config_key).await?;
+
+    // Log gateway system decision
+    if should_execute {
+        router_env::logger::info!(
+            "Payment gateway system decision: UCS - merchant_id={}, connector={}, payment_method={}, flow={}",
+            merchant_id, connector_name, payment_method, flow_name
+        );
+    } else {
+        router_env::logger::info!(
+            "Payment gateway system decision: Direct - merchant_id={}, connector={}, payment_method={}, flow={}",
+            merchant_id, connector_name, payment_method, flow_name
+        );
+    }
+
     Ok(should_execute)
 }
 
+/// Extracts the gateway system from the payment intent's feature metadata
+/// Returns None if metadata is missing, corrupted, or doesn't contain gateway_system
+fn extract_gateway_system_from_payment_intent<F: Clone, D>(
+    payment_data: &D,
+) -> Option<GatewaySystem>
+where
+    D: OperationSessionGetters<F>,
+{
+    #[cfg(feature = "v1")]
+    {
+        payment_data
+            .get_payment_intent()
+            .feature_metadata
+            .as_ref()
+            .and_then(|metadata| {
+                // Try to parse the JSON value as FeatureMetadata
+                // Log errors but don't fail the flow for corrupted metadata
+                match serde_json::from_value::<FeatureMetadata>(metadata.clone()) {
+                    Ok(feature_metadata) => feature_metadata.gateway_system,
+                    Err(err) => {
+                        router_env::logger::warn!(
+                            "Failed to parse feature_metadata for gateway_system extraction: {}",
+                            err
+                        );
+                        None
+                    }
+                }
+            })
+    }
+    #[cfg(feature = "v2")]
+    {
+        None // V2 does not use feature metadata for gateway system tracking
+    }
+}
+
+/// Updates the payment intent's feature metadata to track the gateway system being used
+#[cfg(feature = "v1")]
+pub fn update_gateway_system_in_feature_metadata<F: Clone, D>(
+    payment_data: &mut D,
+    gateway_system: GatewaySystem,
+) -> RouterResult<()>
+where
+    D: OperationSessionGetters<F> + OperationSessionSetters<F>,
+{
+    let mut payment_intent = payment_data.get_payment_intent().clone();
+
+    let existing_metadata = payment_intent.feature_metadata.as_ref();
+
+    let mut feature_metadata = existing_metadata
+        .and_then(|metadata| serde_json::from_value::<FeatureMetadata>(metadata.clone()).ok())
+        .unwrap_or_default();
+
+    feature_metadata.gateway_system = Some(gateway_system);
+
+    let updated_metadata = serde_json::to_value(feature_metadata)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to serialize feature metadata")?;
+
+    payment_intent.feature_metadata = Some(updated_metadata.clone());
+    payment_data.set_payment_intent(payment_intent);
+
+    Ok(())
+}
+
 pub async fn should_call_unified_connector_service_for_webhooks(
     state: &SessionState,
     merchant_context: &MerchantContext,
@@ -422,7 +550,7 @@ pub async fn call_unified_connector_service_for_webhook(
         .unified_connector_service_client
         .as_ref()
         .ok_or_else(|| {
-            error_stack::report!(ApiErrorResponse::WebhookProcessingFailure)
+            error_stack::report!(errors::ApiErrorResponse::WebhookProcessingFailure)
                 .attach_printable("UCS client is not available for webhook processing")
         })?;
 
@@ -472,10 +600,10 @@ pub async fn call_unified_connector_service_for_webhook(
             build_unified_connector_service_auth_metadata(mca_type, merchant_context)
         })
         .transpose()
-        .change_context(ApiErrorResponse::InternalServerError)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Failed to build UCS auth metadata")?
         .ok_or_else(|| {
-            error_stack::report!(ApiErrorResponse::InternalServerError).attach_printable(
+            error_stack::report!(errors::ApiErrorResponse::InternalServerError).attach_printable(
                 "Missing merchant connector account for UCS webhook transformation",
             )
         })?;
@@ -504,7 +632,7 @@ pub async fn call_unified_connector_service_for_webhook(
         }
         Err(err) => {
             // When UCS is configured, we don't fall back to direct connector processing
-            Err(ApiErrorResponse::WebhookProcessingFailure)
+            Err(errors::ApiErrorResponse::WebhookProcessingFailure)
                 .attach_printable(format!("UCS webhook processing failed: {err}"))
         }
     }
diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs
index c5c25bcc3e3..52a72a2d020 100644
--- a/crates/router/src/workflows/payment_sync.rs
+++ b/crates/router/src/workflows/payment_sync.rs
@@ -139,7 +139,7 @@ impl ProcessTrackerWorkflow<SessionState> for PaymentsSyncWorkflow {
                         .as_ref()
                         .is_none()
                 {
-                    let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::PGStatusUpdate { status: api_models::enums::IntentStatus::Failed,updated_by: merchant_account.storage_scheme.to_string(), incremental_authorization_allowed: Some(false) };
+                    let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::PGStatusUpdate { status: api_models::enums::IntentStatus::Failed,updated_by: merchant_account.storage_scheme.to_string(), incremental_authorization_allowed: Some(false), feature_metadata: payment_data.payment_intent.feature_metadata.clone().map(masking::Secret::new), };
                     let payment_attempt_update =
                         hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ErrorUpdate {
                             connector: None,

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

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates

## Description

This change adds gateway system tracking in the feature metadata to enable routing stickiness and improve payment flow routing decisions. The implementation includes:

- Adding a new `GatewaySystem` enum to track whether payments are processed through Direct routing (hyperswitch) or Unified Connector Service (UCS)
- Storing the gateway system information in payment intent's feature metadata for routing stickiness
- Implementing routing logic that considers previous gateway system usage when making routing decisions
- Adding comprehensive logging for payment routing decisions for better observability
- Updating all payment update operations to preserve feature metadata consistency

### Additional Changes

- [x] 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

Previously, payment routing decisions between Direct routing and UCS were made independently for each payment operation, which could lead to inconsistent routing for the same payment intent across different operations (confirm, capture, sync, etc.). This lack of stickiness could cause issues with:

1. Payment state consistency across different operations
2. Potential routing conflicts when different operations use different gateway systems
3. Difficulty in tracking and debugging payment flows that span multiple operations

This change ensures that once a payment intent is routed through a specific gateway system (Direct or UCS), all subsequent operations for that payment maintain the same routing decision.

## How did you test it?

- Tested payment creation, confirmation, and sync operations with both Direct and UCS routing
- Verified that routing decisions are properly logged and persisted in feature metadata
- Confirmed that stickiness logic correctly routes subsequent operations through the same gateway system
- Tested error scenarios to ensure graceful fallback when metadata is corrupted or missing

##### Authorize 
```bash
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: ***************' \
--data-raw '{
    "amount": 1000,
    "currency": "INR",
    
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 1000,
    "customer_id": "Uzzu54523",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request.",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "payment_method": "upi",
    
    
    "payment_method_type": "upi_collect",
    
    "payment_method_data": {
        "upi": {
            "upi_collect": {
                "vpa_id": "success@razorpay"
            }
            
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "city": "San Fransico",
                "state": "California",
                "zip": "94122",
                "country": "IN",
                "first_name": "Swangi",
                "last_name": "Kumari"
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": "swangi.kumari@juspay.in"
        }
        
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "46282",
            "country": "IN",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "46282",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "all_keys_required": true
}'
```

```json
{
    "payment_id": "pay_1NzPXUQFRkXlvHOIW3Jq",
    "merchant_id": "merchant_1754554769",
    "status": "requires_customer_action",
    "amount": 1000,
    "net_amount": 1000,
    "shipping_cost": null,
    "amount_capturable": 1000,
    "amount_received": null,
    "connector": "razorpay",
    "client_secret": "pay_1NzPXUQFRkXlvHOIW3Jq_secret_bhXTPfgXbwB11rmrC0DR",
    "created": "2025-08-08T10:58:58.767Z",
    "currency": "INR",
    "customer_id": "Uzzu54523",
    "customer": {
        "id": "Uzzu54523",
        "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": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "upi",
    "payment_method_data": {
        "upi": {
            "upi_collect": {
                "vpa_id": "su*****@razorpay"
            }
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "IN",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "California",
                "first_name": "Swangi",
                "last_name": "Kumari",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": "swangi.kumari@juspay.in"
        }
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "46282",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "IN",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "46282",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "upi_collect",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "Uzzu54523",
        "created_at": 1754650738,
        "expires": 1754654338,
        "secret": "epk_7a28f568d1834cf983b36bc50e45970f"
    },
    "manual_retry_allowed": null,
    "connector_transaction_id": "pay_R2odTWpZA226ue",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "order_R2odSokuwuMexQ",
    "payment_link": null,
    "profile_id": "pro_NkNnZmnjCEuoOKyqoev5",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_JtzW87JrlcfmX6fssydo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-08T11:13:58.767Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-08-08T10:59:02.234Z",
    "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": null,
    "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": "{\"razorpay_payment_id\":\"pay_R2odTWpZA226ue\"}",
    "enable_partial_authorization": null
}
```

##### Psync

```bash
curl --location 'http://localhost:8080/payments/pay_1NzPXUQFRkXlvHOIW3Jq?force_sync=true&expand_captures=true&expand_attempts=true' \
--header 'Accept: application/json' \
--header 'api-key: *****************************'
```

```json
{
    "payment_id": "pay_4dezIe3S2nraarfCsnly",
    "merchant_id": "merchant_1754554769",
    "status": "succeeded",
    "amount": 1000,
    "net_amount": 1000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 1000,
    "connector": "razorpay",
    "client_secret": "pay_4dezIe3S2nraarfCsnly_secret_fPafwmfIQcGWTr96zQtm",
    "created": "2025-08-07T11:07:00.954Z",
    "currency": "INR",
    "customer_id": "Uzzu54523",
    "customer": {
        "id": "Uzzu54523",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request.",
    "refunds": null,
    "disputes": null,
    "attempts": [
        {
            "attempt_id": "pay_4dezIe3S2nraarfCsnly_1",
            "status": "authentication_pending",
            "amount": 1000,
            "order_tax_amount": null,
            "currency": "INR",
            "connector": "razorpay",
            "error_message": null,
            "payment_method": "upi",
            "connector_transaction_id": "pay_R2QErM6QamRIFx",
            "capture_method": "automatic",
            "authentication_type": "no_three_ds",
            "created_at": "2025-08-07T11:07:00.954Z",
            "modified_at": "2025-08-07T11:07:05.271Z",
            "cancellation_reason": null,
            "mandate_id": null,
            "error_code": null,
            "payment_token": null,
            "connector_metadata": null,
            "payment_experience": null,
            "payment_method_type": "upi_collect",
            "reference_id": "order_R2QEqgtrS7lSPD",
            "unified_code": null,
            "unified_message": null,
            "client_source": null,
            "client_version": null
        }
    ],
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "upi",
    "payment_method_data": {
        "upi": {
            "upi_collect": {
                "vpa_id": "su*****@razorpay"
            }
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "IN",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "California",
                "first_name": "Swangi",
                "last_name": "Kumari",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": "swangi.kumari@juspay.in"
        }
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "46282",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "IN",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "46282",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi.kumari@juspay.in"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "upi_collect",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": false,
    "connector_transaction_id": "pay_R2QErM6QamRIFx",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "unified_connector_service"
    },
    "reference_id": "order_R2QEqgtrS7lSPD",
    "payment_link": null,
    "profile_id": "pro_NkNnZmnjCEuoOKyqoev5",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_JtzW87JrlcfmX6fssydo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-07T11:22:00.954Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-08-07T11:11:29.281Z",
    "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": null,
    "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": "{\"entity\":\"collection\",\"count\":1,\"items\":[{\"id\":\"pay_R2QErM6QamRIFx\",\"entity\":\"payment\",\"amount\":1000,\"currency\":\"INR\",\"status\":\"captured\",\"order_id\":\"order_R2QEqgtrS7lSPD\",\"invoice_id\":null,\"international\":false,\"method\":\"upi\",\"amount_refunded\":0,\"refund_status\":null,\"captured\":true,\"description\":\"Payment via Razorpay\",\"card_id\":null,\"bank\":null,\"wallet\":null,\"vpa\":\"success@razorpay\",\"email\":\"swangi.kumari@juspay.in\",\"contact\":\"+918056594427\",\"notes\":{\"optimizer_provider_name\":\"razorpay\"},\"fee\":25,\"tax\":4,\"error_code\":null,\"error_description\":null,\"error_source\":null,\"error_step\":null,\"error_reason\":null,\"acquirer_data\":{\"rrn\":\"232178449311\",\"upi_transaction_id\":\"007AC04E0A02D40AA98FE252C69C7305\"},\"gateway_provider\":\"Razorpay\",\"created_at\":1754564823,\"upi\":{\"vpa\":\"success@razorpay\"}}]}",
    "enable_partial_authorization": null
}
```


#### What to look for?
- in logs you will be able to see 

```sh 
info:: Processing payment through Direct gateway system - payment_id=pay_4dezIe3S2nraarfCsnly, attempt_id=pay_4dezIe3S2nraarfCsnly_1
```

```sh
info:: Payment gateway system decision: Direct (sticky) - payment intent previously used Direct
```

###### Additional Note: 
This is also pushed to kafka topic for sessionizing
<img width="1295" height="882" alt="Screenshot 2025-08-07 at 4 46 52 PM" src="https://github.com/user-attachments/assets/d4c69638-0191-4cda-96be-35cee3015e33" />


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

- [x] I formatted the code `cargo +nightly fmt`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
