From 75bf58d656cd0ddd1538342a6e6ce3d1a07ec98a Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Tue, 9 Sep 2025 19:11:33 +0530 Subject: [PATCH] feat(core): Add Network Details in Payments Response (#9273) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/v1/openapi_spec_v1.json | 25 ++++++++++++ crates/api_models/src/payments.rs | 9 +++++ crates/diesel_models/src/payment_attempt.rs | 39 +++++++++++++++++++ crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/schema_v2.rs | 1 + crates/diesel_models/src/user/sample_data.rs | 4 +- .../src/payments/payment_attempt.rs | 12 +++++- crates/openapi/src/openapi.rs | 1 + crates/router/src/core/payments/helpers.rs | 1 + .../payments/operations/payment_create.rs | 1 + .../payments/operations/payment_response.rs | 14 ++++--- crates/router/src/core/payments/retry.rs | 4 +- .../router/src/core/payments/transformers.rs | 34 +++++++++++++++- crates/router/src/db/events.rs | 1 + .../src/types/storage/payment_attempt.rs | 3 ++ crates/router/src/utils/user/sample_data.rs | 1 + crates/router/src/workflows/payment_sync.rs | 1 + crates/router/tests/payments.rs | 2 + crates/router/tests/payments2.rs | 2 + .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 5 +++ .../down.sql | 1 + .../up.sql | 1 + 23 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 migrations/2025-09-09-171443_add_network_details_in_attempt_table/down.sql create mode 100644 migrations/2025-09-09-171443_add_network_details_in_attempt_table/up.sql diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index dd0d8cd010..e60d1eab85 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -19243,6 +19243,15 @@ } } }, + "NetworkDetails": { + "type": "object", + "properties": { + "network_advice_code": { + "type": "string", + "nullable": true + } + } + }, "NetworkTransactionIdAndCardDetails": { "type": "object", "required": [ @@ -24166,6 +24175,14 @@ "type": "boolean", "description": "Boolean indicating whether overcapture is effectively enabled for this payment", "nullable": true + }, + "network_details": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetails" + } + ], + "nullable": true } } }, @@ -25577,6 +25594,14 @@ "type": "boolean", "description": "Boolean indicating whether overcapture is effectively enabled for this payment", "nullable": true + }, + "network_details": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetails" + } + ], + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 1e98f73f5d..bb53eb1b6e 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2036,6 +2036,11 @@ impl Default for MandateType { } } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Eq, PartialEq, ToSchema)] +pub struct NetworkDetails { + pub network_advice_code: Option, +} + #[derive(Default, Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct Card { /// The card number @@ -5574,6 +5579,10 @@ pub struct PaymentsResponse { /// Boolean indicating whether overcapture is effectively enabled for this payment #[schema(value_type = Option)] pub is_overcapture_enabled: Option, + + /// Contains card network response details (e.g., Visa/Mastercard advice codes). + #[schema(value_type = Option)] + pub network_details: Option, } #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index fd1856977b..d42b9d8d10 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -34,6 +34,14 @@ impl ConnectorMandateReferenceId { self.connector_mandate_request_reference_id.clone() } } +common_utils::impl_to_sql_from_sql_json!(NetworkDetails); +#[derive( + Clone, Default, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct NetworkDetails { + pub network_advice_code: Option, +} #[cfg(feature = "v2")] #[derive( @@ -98,6 +106,7 @@ pub struct PaymentAttempt { pub connector_request_reference_id: Option, pub network_transaction_id: Option, pub is_overcapture_enabled: Option, + pub network_details: Option, #[diesel(deserialize_as = RequiredFromNullable)] pub payment_method_type_v2: storage_enums::PaymentMethod, pub connector_payment_id: Option, @@ -214,6 +223,7 @@ pub struct PaymentAttempt { pub connector_request_reference_id: Option, pub network_transaction_id: Option, pub is_overcapture_enabled: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -314,6 +324,7 @@ pub struct PaymentAttemptNew { pub error_reason: Option, pub connector_response_reference_id: Option, pub network_transaction_id: Option, + pub network_details: Option, pub multiple_capture_count: Option, pub amount_capturable: MinorUnit, pub updated_by: String, @@ -437,6 +448,7 @@ pub struct PaymentAttemptNew { pub routing_approach: Option, pub connector_request_reference_id: Option, pub network_transaction_id: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -598,6 +610,7 @@ pub enum PaymentAttemptUpdate { authentication_type: Option, issuer_error_code: Option, issuer_error_message: Option, + network_details: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -997,6 +1010,7 @@ impl PaymentAttemptUpdateInternal { connector_request_reference_id: connector_request_reference_id .or(source.connector_request_reference_id), is_overcapture_enabled: source.is_overcapture_enabled, + network_details: source.network_details, } } } @@ -1065,6 +1079,7 @@ pub struct PaymentAttemptUpdateInternal { pub connector_request_reference_id: Option, pub network_transaction_id: Option, pub is_overcapture_enabled: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -1258,6 +1273,7 @@ impl PaymentAttemptUpdate { connector_request_reference_id, network_transaction_id, is_overcapture_enabled, + network_details, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1329,6 +1345,7 @@ impl PaymentAttemptUpdate { .or(source.connector_request_reference_id), network_transaction_id: network_transaction_id.or(source.network_transaction_id), is_overcapture_enabled: is_overcapture_enabled.or(source.is_overcapture_enabled), + network_details: network_details.or(source.network_details), ..source } } @@ -2662,6 +2679,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2728,6 +2746,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2828,6 +2847,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id, network_transaction_id, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2895,6 +2915,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2963,6 +2984,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -3031,6 +3053,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::ConnectorMandateDetailUpdate { connector_mandate_detail, @@ -3097,6 +3120,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -3163,6 +3187,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -3259,6 +3284,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id, is_overcapture_enabled, + network_details: None, } } PaymentAttemptUpdate::ErrorUpdate { @@ -3276,6 +3302,7 @@ impl From for PaymentAttemptUpdateInternal { authentication_type, issuer_error_code, issuer_error_message, + network_details, } => { let (connector_transaction_id, processor_transaction_data) = connector_transaction_id @@ -3344,6 +3371,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details, } } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { @@ -3408,6 +3436,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -3481,6 +3510,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -3560,6 +3590,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, } } PaymentAttemptUpdate::PreprocessingUpdate { @@ -3638,6 +3669,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, } } PaymentAttemptUpdate::CaptureUpdate { @@ -3706,6 +3738,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -3773,6 +3806,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -3849,6 +3883,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3916,6 +3951,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3985,6 +4021,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -4063,6 +4100,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, } } PaymentAttemptUpdate::PostSessionTokensUpdate { @@ -4130,6 +4168,7 @@ impl From for PaymentAttemptUpdateInternal { connector_request_reference_id: None, network_transaction_id: None, is_overcapture_enabled: None, + network_details: None, }, } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 4b5176b9d2..79df62ac4b 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -986,6 +986,7 @@ diesel::table! { #[max_length = 255] network_transaction_id -> Nullable, is_overcapture_enabled -> Nullable, + network_details -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 1a0ce0ec6e..b2e674f34a 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -930,6 +930,7 @@ diesel::table! { #[max_length = 255] network_transaction_id -> Nullable, is_overcapture_enabled -> Nullable, + network_details -> Nullable, payment_method_type_v2 -> Nullable, #[max_length = 128] connector_payment_id -> Nullable, diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 846c0852b6..9dc2f03a5b 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -18,7 +18,7 @@ use time::PrimitiveDateTime; use crate::{ enums::{MandateDataType, MandateDetails}, schema::payment_attempt, - ConnectorMandateReferenceId, PaymentAttemptNew, + ConnectorMandateReferenceId, NetworkDetails, PaymentAttemptNew, }; // #[cfg(feature = "v2")] @@ -219,6 +219,7 @@ pub struct PaymentAttemptBatchNew { pub routing_approach: Option, pub connector_request_reference_id: Option, pub network_transaction_id: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -307,6 +308,7 @@ impl PaymentAttemptBatchNew { routing_approach: self.routing_approach, connector_request_reference_id: self.connector_request_reference_id, network_transaction_id: self.network_transaction_id, + network_details: self.network_details, } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 3ee63c728c..7f8b902061 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -23,7 +23,7 @@ use common_utils::{ }; #[cfg(feature = "v1")] use diesel_models::{ - ConnectorMandateReferenceId, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, + ConnectorMandateReferenceId, NetworkDetails, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; use diesel_models::{ PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, @@ -1031,6 +1031,7 @@ pub struct PaymentAttempt { pub debit_routing_savings: Option, pub network_transaction_id: Option, pub is_overcapture_enabled: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -1328,6 +1329,7 @@ pub struct PaymentAttemptNew { pub routing_approach: Option, pub connector_request_reference_id: Option, pub network_transaction_id: Option, + pub network_details: Option, } #[cfg(feature = "v1")] @@ -1484,6 +1486,7 @@ pub enum PaymentAttemptUpdate { authentication_type: Option, issuer_error_code: Option, issuer_error_message: Option, + network_details: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -1822,6 +1825,7 @@ impl PaymentAttemptUpdate { authentication_type, issuer_error_code, issuer_error_message, + network_details, } => DieselPaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -1837,6 +1841,7 @@ impl PaymentAttemptUpdate { authentication_type, issuer_error_code, issuer_error_message, + network_details, }, Self::CaptureUpdate { multiple_capture_count, @@ -2158,6 +2163,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_request_reference_id: self.connector_request_reference_id, network_transaction_id: self.network_transaction_id, is_overcapture_enabled: self.is_overcapture_enabled, + network_details: self.network_details, }) } @@ -2258,6 +2264,7 @@ impl behaviour::Conversion for PaymentAttempt { debit_routing_savings: None, network_transaction_id: storage_model.network_transaction_id, is_overcapture_enabled: storage_model.is_overcapture_enabled, + network_details: storage_model.network_details, }) } .await @@ -2349,6 +2356,7 @@ impl behaviour::Conversion for PaymentAttempt { routing_approach: self.routing_approach, connector_request_reference_id: self.connector_request_reference_id, network_transaction_id: self.network_transaction_id, + network_details: self.network_details, }) } } @@ -2519,6 +2527,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_request_reference_id, network_transaction_id, is_overcapture_enabled: None, + network_details: None, }) } @@ -2796,6 +2805,7 @@ impl behaviour::Conversion for PaymentAttempt { processor_merchant_id: Some(processor_merchant_id), created_by: created_by.map(|cb| cb.to_string()), connector_request_reference_id, + network_details: None, }) } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 0adc53a841..a95f4fa9a0 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -576,6 +576,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GpayShippingAddressParameters, api_models::payments::GpayBillingAddressParameters, api_models::payments::GpayBillingAddressFormat, + api_models::payments::NetworkDetails, api_models::payments::SepaBankTransferInstructions, api_models::payments::BacsBankTransferInstructions, api_models::payments::RedirectResponse, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 32960f6997..d93c627813 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4659,6 +4659,7 @@ impl AttemptType { routing_approach: old_payment_attempt.routing_approach, connector_request_reference_id: None, network_transaction_id: None, + network_details: None, } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 171266fbb5..a28816cc6a 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1393,6 +1393,7 @@ impl PaymentCreate { routing_approach: Some(common_enums::RoutingApproach::default()), connector_request_reference_id: None, network_transaction_id:None, + network_details:None, }, additional_pm_data, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 7e093f8b3b..2b1d1024e7 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1572,9 +1572,9 @@ async fn payment_response_update_tracker( Some(storage::PaymentAttemptUpdate::ErrorUpdate { connector: None, status, - error_message: Some(Some(err.message)), - error_code: Some(Some(err.code)), - error_reason: Some(err.reason), + error_message: Some(Some(err.message.clone())), + error_code: Some(Some(err.code.clone())), + error_reason: Some(err.reason.clone()), amount_capturable: router_data .request .get_amount_capturable( @@ -1588,11 +1588,12 @@ async fn payment_response_update_tracker( updated_by: storage_scheme.to_string(), unified_code: Some(Some(unified_code)), unified_message: Some(unified_translated_message), - connector_transaction_id: err.connector_transaction_id, + connector_transaction_id: err.connector_transaction_id.clone(), payment_method_data: additional_payment_method_data, authentication_type: auth_update, - issuer_error_code: err.network_decline_code, - issuer_error_message: err.network_error_message, + issuer_error_code: err.network_decline_code.clone(), + issuer_error_message: err.network_error_message.clone(), + network_details: Some(ForeignFrom::foreign_from(&err)), }), option_gsm.and_then(|option_gsm| option_gsm.error_category), ) @@ -1633,6 +1634,7 @@ async fn payment_response_update_tracker( authentication_type: auth_update, issuer_error_code: None, issuer_error_message: None, + network_details: None, }), None, ) diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 2fe6c550f3..89bfe11e91 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -25,7 +25,7 @@ use crate::{ metrics, }, services, - types::{self, api, domain, storage}, + types::{self, api, domain, storage, transformers::ForeignFrom}, }; #[instrument(skip_all)] @@ -573,6 +573,7 @@ where authentication_type: auth_update, issuer_error_code: error_response.network_decline_code.clone(), issuer_error_message: error_response.network_error_message.clone(), + network_details: Some(ForeignFrom::foreign_from(error_response)), }; #[cfg(feature = "v1")] @@ -721,6 +722,7 @@ pub fn make_new_payment_attempt( routing_approach: old_payment_attempt.routing_approach, connector_request_reference_id: Default::default(), network_transaction_id: old_payment_attempt.network_transaction_id, + network_details: Default::default(), } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 152f4089ee..5b9ed1ec91 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, marker::PhantomData, str::FromStr}; use api_models::payments::{ Address, ConnectorMandateReferenceId, CustomerDetails, CustomerDetailsResponse, FrmMessage, - MandateIds, RequestSurchargeDetails, + MandateIds, NetworkDetails, RequestSurchargeDetails, }; use common_enums::{Currency, RequestIncrementalAuthorization}; use common_utils::{ @@ -15,7 +15,10 @@ use common_utils::{ }; use diesel_models::{ ephemeral_key, - payment_attempt::ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, + payment_attempt::{ + ConnectorMandateReferenceId as DieselConnectorMandateReferenceId, + NetworkDetails as DieselNetworkDetails, + }, }; use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] @@ -3406,6 +3409,9 @@ where enable_partial_authorization: payment_intent.enable_partial_authorization, enable_overcapture: payment_intent.enable_overcapture, is_overcapture_enabled: payment_attempt.is_overcapture_enabled, + network_details: payment_attempt + .network_details + .map(NetworkDetails::foreign_from), }; services::ApplicationResponse::JsonWithHeaders((payments_response, headers)) @@ -3705,6 +3711,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay enable_partial_authorization: pi.enable_partial_authorization, enable_overcapture: pi.enable_overcapture, is_overcapture_enabled: pa.is_overcapture_enabled, + network_details: pa.network_details.map(NetworkDetails::foreign_from), } } } @@ -5952,6 +5959,22 @@ impl ForeignFrom for DieselConnectorMandateReferenc } } +impl ForeignFrom for NetworkDetails { + fn foreign_from(value: DieselNetworkDetails) -> Self { + Self { + network_advice_code: value.network_advice_code, + } + } +} + +impl ForeignFrom for DieselNetworkDetails { + fn foreign_from(value: NetworkDetails) -> Self { + Self { + network_advice_code: value.network_advice_code, + } + } +} + #[cfg(feature = "v2")] impl ForeignFrom for Option @@ -6050,6 +6073,13 @@ impl From for domain::NetworkTokenData { } } } +impl ForeignFrom<&hyperswitch_domain_models::router_data::ErrorResponse> for DieselNetworkDetails { + fn foreign_from(err: &hyperswitch_domain_models::router_data::ErrorResponse) -> Self { + Self { + network_advice_code: err.network_advice_code.clone(), + } + } +} impl ForeignFrom for common_enums::AuthenticationType diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index c9afaa5685..94adf92e3d 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -1408,6 +1408,7 @@ mod tests { enable_partial_authorization: None, is_overcapture_enabled: None, enable_overcapture: None, + network_details: None, }; let content = api_webhooks::OutgoingWebhookContent::PaymentDetails(Box::new(expected_response)); diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index c0ccc2ee37..d8d3935e6f 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -228,6 +228,7 @@ mod tests { routing_approach: Default::default(), connector_request_reference_id: Default::default(), network_transaction_id: Default::default(), + network_details: Default::default(), }; let store = state @@ -321,6 +322,7 @@ mod tests { routing_approach: Default::default(), connector_request_reference_id: Default::default(), network_transaction_id: Default::default(), + network_details: Default::default(), }; let store = state .stores @@ -427,6 +429,7 @@ mod tests { routing_approach: Default::default(), connector_request_reference_id: Default::default(), network_transaction_id: Default::default(), + network_details: Default::default(), }; let store = state .stores diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index e4266298d0..a49e544097 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -393,6 +393,7 @@ pub async fn generate_sample_data( routing_approach: None, connector_request_reference_id: None, network_transaction_id: None, + network_details: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 52a72a2d02..d7ac92e131 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -158,6 +158,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { authentication_type: None, issuer_error_code: None, issuer_error_message: None, + network_details:None }; payment_data.payment_attempt = db diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index e70f1fd6aa..f52e4c2b03 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -470,6 +470,7 @@ async fn payments_create_core() { enable_partial_authorization: None, is_overcapture_enabled: None, enable_overcapture: None, + network_details: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -753,6 +754,7 @@ async fn payments_create_core_adyen_no_redirect() { enable_partial_authorization: None, is_overcapture_enabled: None, enable_overcapture: None, + network_details: None, }, vec![], )); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index c5f51ac674..8307d11eef 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -232,6 +232,7 @@ async fn payments_create_core() { enable_partial_authorization: None, is_overcapture_enabled: None, enable_overcapture: None, + network_details: None, }; let expected_response = @@ -523,6 +524,7 @@ async fn payments_create_core_adyen_no_redirect() { enable_partial_authorization: None, is_overcapture_enabled: None, enable_overcapture: None, + network_details: None, }, vec![], )); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index c66f1a3146..609bb531a8 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -240,6 +240,7 @@ impl PaymentAttemptInterface for MockDb { debit_routing_savings: None, network_transaction_id: payment_attempt.network_transaction_id, is_overcapture_enabled: None, + network_details: payment_attempt.network_details, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index a03e1bd3fd..6a739f0a6c 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -694,6 +694,7 @@ impl PaymentAttemptInterface for KVRouterStore { debit_routing_savings: None, network_transaction_id: payment_attempt.network_transaction_id.clone(), is_overcapture_enabled: None, + network_details: payment_attempt.network_details.clone(), }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1904,6 +1905,7 @@ impl DataModelExt for PaymentAttempt { connector_request_reference_id: self.connector_request_reference_id, network_transaction_id: self.network_transaction_id, is_overcapture_enabled: self.is_overcapture_enabled, + network_details: self.network_details, } } @@ -1999,6 +2001,7 @@ impl DataModelExt for PaymentAttempt { debit_routing_savings: None, network_transaction_id: storage_model.network_transaction_id, is_overcapture_enabled: storage_model.is_overcapture_enabled, + network_details: storage_model.network_details, } } } @@ -2091,6 +2094,7 @@ impl DataModelExt for PaymentAttemptNew { routing_approach: self.routing_approach, connector_request_reference_id: self.connector_request_reference_id, network_transaction_id: self.network_transaction_id, + network_details: self.network_details, } } @@ -2176,6 +2180,7 @@ impl DataModelExt for PaymentAttemptNew { routing_approach: storage_model.routing_approach, connector_request_reference_id: storage_model.connector_request_reference_id, network_transaction_id: storage_model.network_transaction_id, + network_details: storage_model.network_details, } } } diff --git a/migrations/2025-09-09-171443_add_network_details_in_attempt_table/down.sql b/migrations/2025-09-09-171443_add_network_details_in_attempt_table/down.sql new file mode 100644 index 0000000000..183970f72a --- /dev/null +++ b/migrations/2025-09-09-171443_add_network_details_in_attempt_table/down.sql @@ -0,0 +1 @@ +ALTER TABLE payment_attempt DROP COLUMN network_details; diff --git a/migrations/2025-09-09-171443_add_network_details_in_attempt_table/up.sql b/migrations/2025-09-09-171443_add_network_details_in_attempt_table/up.sql new file mode 100644 index 0000000000..ffab6b8d54 --- /dev/null +++ b/migrations/2025-09-09-171443_add_network_details_in_attempt_table/up.sql @@ -0,0 +1 @@ +ALTER TABLE payment_attempt ADD COLUMN network_details JSONB;