diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index d6a9475b17..56e0791fdd 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -14,11 +14,15 @@ pub mod errors; pub mod events; pub mod ext_traits; pub mod fp_utils; +/// Used for hashing +pub mod hashing; pub mod id_type; #[cfg(feature = "keymanager")] pub mod keymanager; pub mod link_utils; pub mod macros; +#[cfg(feature = "metrics")] +pub mod metrics; pub mod new_type; pub mod payout_method_utils; pub mod pii; @@ -28,13 +32,14 @@ pub mod request; pub mod signals; pub mod transformers; pub mod types; +/// Unified Connector Service (UCS) interface definitions. +/// +/// This module defines types and traits for interacting with the Unified Connector Service. +/// It includes reference ID types for payments and refunds, and a trait for extracting +/// UCS reference information from requests. +pub mod ucs_types; pub mod validation; -/// Used for hashing -pub mod hashing; -#[cfg(feature = "metrics")] -pub mod metrics; - pub use base64_serializer::Base64Serializer; /// Date-time utilities. diff --git a/crates/common_utils/src/ucs_types.rs b/crates/common_utils/src/ucs_types.rs new file mode 100644 index 0000000000..25809819f5 --- /dev/null +++ b/crates/common_utils/src/ucs_types.rs @@ -0,0 +1,38 @@ +use crate::id_type; + +/// Represents a reference ID for the Unified Connector Service (UCS). +/// +/// This enum can hold either a payment reference ID or a refund reference ID, +/// allowing for a unified way to handle different types of transaction references +/// when interacting with the UCS. +#[derive(Debug)] +pub enum UcsReferenceId { + /// A payment reference ID. + /// + /// This variant wraps a [`PaymentReferenceId`](id_type::PaymentReferenceId) + /// and is used to identify a payment transaction within the UCS. + Payment(id_type::PaymentReferenceId), + /// A refund reference ID. + /// + /// This variant wraps a [`RefundReferenceId`](id_type::RefundReferenceId) + /// and is used to identify a refund transaction within the UCS. + Refund(id_type::RefundReferenceId), +} + +impl UcsReferenceId { + /// Returns the string representation of the reference ID. + /// + /// This method matches the enum variant and calls the `get_string_repr` + /// method of the underlying ID type (either `PaymentReferenceId` or `RefundReferenceId`) + /// to get its string representation. + /// + /// # Returns + /// + /// A string slice (`&str`) representing the reference ID. + pub fn get_string_repr(&self) -> &str { + match self { + Self::Payment(id) => id.get_string_repr(), + Self::Refund(id) => id.get_string_repr(), + } + } +} diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs index 650196f654..f829c031e7 100644 --- a/crates/external_services/src/grpc_client.rs +++ b/crates/external_services/src/grpc_client.rs @@ -14,7 +14,7 @@ use std::{fmt::Debug, sync::Arc}; #[cfg(feature = "dynamic_routing")] use common_utils::consts; -use common_utils::id_type; +use common_utils::{id_type, ucs_types}; #[cfg(feature = "dynamic_routing")] use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy}; #[cfg(feature = "dynamic_routing")] @@ -160,12 +160,19 @@ pub struct GrpcHeadersUcs { lineage_ids: LineageIds, /// External vault proxy metadata external_vault_proxy_metadata: Option, + /// Merchant Reference Id + merchant_reference_id: Option, } + /// Type aliase for GrpcHeaders builder in initial stage -pub type GrpcHeadersUcsBuilderInitial = GrpcHeadersUcsBuilder<((String,), (), ())>; +pub type GrpcHeadersUcsBuilderInitial = GrpcHeadersUcsBuilder<((String,), (), (), ())>; /// Type aliase for GrpcHeaders builder in intermediate stage -pub type GrpcHeadersUcsBuilderIntermediate = - GrpcHeadersUcsBuilder<((String,), (), (Option,))>; +pub type GrpcHeadersUcsBuilderIntermediate = GrpcHeadersUcsBuilder<( + (String,), + (), + (Option,), + (Option,), +)>; /// struct to represent set of Lineage ids #[derive(Debug, serde::Serialize)] 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 91dd0db400..582ef85d72 100644 --- a/crates/external_services/src/grpc_client/unified_connector_service.rs +++ b/crates/external_services/src/grpc_client/unified_connector_service.rs @@ -446,6 +446,16 @@ pub fn build_unified_connector_service_grpc_headers( parse(consts::UCS_LINEAGE_IDS, &lineage_ids_str)?, ); + if let Some(reference_id) = grpc_headers.merchant_reference_id { + metadata.append( + consts::UCS_HEADER_REFERENCE_ID, + parse( + consts::UCS_HEADER_REFERENCE_ID, + reference_id.get_string_repr(), + )?, + ); + }; + if let Err(err) = grpc_headers .tenant_id .parse() diff --git a/crates/external_services/src/lib.rs b/crates/external_services/src/lib.rs index 7f4cb239d1..23f0a59718 100644 --- a/crates/external_services/src/lib.rs +++ b/crates/external_services/src/lib.rs @@ -95,7 +95,11 @@ pub mod consts { /// Header key for sending the EXTERNAL VAULT METADATA in proxy payments pub(crate) const UCS_HEADER_EXTERNAL_VAULT_METADATA: &str = "x-external-vault-metadata"; + /// Header key for sending the list of lineage ids pub(crate) const UCS_LINEAGE_IDS: &str = "x-lineage-ids"; + + /// Header key for sending the merchant reference id to UCS + pub(crate) const UCS_HEADER_REFERENCE_ID: &str = "x-reference-id"; } /// Metrics for interactions with external systems. diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index c8ac34f861..370bafd2a9 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -844,6 +844,7 @@ pub struct HeaderPayload { pub locale: Option, pub x_app_id: Option, pub x_redirect_uri: Option, + pub x_reference_id: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -874,6 +875,7 @@ pub struct HeaderPayload { pub locale: Option, pub x_app_id: Option, pub x_redirect_uri: Option, + pub x_reference_id: Option, } impl HeaderPayload { diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 0a91a9207e..fe79b9a53d 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -140,7 +140,7 @@ pub struct ExternalVaultProxyPaymentsData { /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. - pub merchant_order_reference_id: Option, + pub merchant_order_reference_id: Option, pub integrity_object: Option, pub shipping_cost: Option, pub additional_payment_method_data: Option, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f7f445d1b6..3442a34be7 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -2780,7 +2780,7 @@ pub(crate) async fn payments_create_and_confirm_intent( merchant_context: domain::MerchantContext, profile: domain::Profile, request: payments_api::PaymentsRequest, - mut header_payload: HeaderPayload, + header_payload: HeaderPayload, ) -> RouterResponse { use hyperswitch_domain_models::{ payments::PaymentIntentData, router_flow_types::PaymentCreateIntent, @@ -4568,7 +4568,7 @@ where customer, &merchant_connector_account_type_details, None, - None, + Some(header_payload), ) .await?; @@ -4766,7 +4766,7 @@ where &None, &merchant_connector_account_type_details, None, - None, + Some(header_payload.clone()), ) .await?; @@ -5113,7 +5113,7 @@ where customer, &merchant_connector_account, merchant_recipient_data, - None, + Some(header_payload.clone()), ) .await?; @@ -5278,7 +5278,7 @@ where &None, &merchant_connector_account, None, - None, + Some(header_payload.clone()), ) .await?; @@ -5402,7 +5402,7 @@ where &None, &merchant_connector_account, None, - None, + Some(header_payload.clone()), ) .await?; @@ -5978,7 +5978,7 @@ where customer, &merchant_connector_account, None, - None, + Some(header_payload.clone()), ) .await?; @@ -6100,7 +6100,7 @@ where customer, &merchant_connector_account, None, - None, + Some(header_payload.clone()), ) .await?; diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index ca32e61bce..031fd0de7c 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -1,6 +1,9 @@ +use std::str::FromStr; + use async_trait::async_trait; use common_enums as enums; use common_types::payments as common_payments_types; +use common_utils::{id_type, ucs_types}; use error_stack::ResultExt; use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; #[cfg(feature = "v2")] @@ -853,9 +856,20 @@ async fn call_unified_connector_service_authorize( build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct request metadata")?; + let merchant_order_reference_id = router_data + .header_payload + .as_ref() + .and_then(|payload| payload.x_reference_id.clone()) + .map(|id| id_type::PaymentReferenceId::from_str(id.as_str())) + .transpose() + .inspect_err(|err| logger::warn!(error=?err, "Invalid Merchant ReferenceId found")) + .ok() + .flatten() + .map(ucs_types::UcsReferenceId::Payment); let headers_builder = state .get_grpc_headers_ucs() - .external_vault_proxy_metadata(None); + .external_vault_proxy_metadata(None) + .merchant_reference_id(merchant_order_reference_id); let updated_router_data = Box::pin(ucs_logging_wrapper( router_data.clone(), state, @@ -926,9 +940,20 @@ async fn call_unified_connector_service_repeat_payment( build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct request metadata")?; + let merchant_order_reference_id = router_data + .header_payload + .as_ref() + .and_then(|payload| payload.x_reference_id.clone()) + .map(|id| id_type::PaymentReferenceId::from_str(id.as_str())) + .transpose() + .inspect_err(|err| logger::warn!(error=?err, "Invalid Merchant ReferenceId found")) + .ok() + .flatten() + .map(ucs_types::UcsReferenceId::Payment); let headers_builder = state .get_grpc_headers_ucs() - .external_vault_proxy_metadata(None); + .external_vault_proxy_metadata(None) + .merchant_reference_id(merchant_order_reference_id); let updated_router_data = Box::pin(ucs_logging_wrapper( router_data.clone(), state, diff --git a/crates/router/src/core/payments/flows/external_proxy_flow.rs b/crates/router/src/core/payments/flows/external_proxy_flow.rs index c850eecb9e..fca6c1d3c0 100644 --- a/crates/router/src/core/payments/flows/external_proxy_flow.rs +++ b/crates/router/src/core/payments/flows/external_proxy_flow.rs @@ -1,5 +1,8 @@ +use std::str::FromStr; + use async_trait::async_trait; use common_enums as enums; +use common_utils::{id_type, ucs_types, ucs_types::UcsReferenceId}; use error_stack::ResultExt; use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; #[cfg(feature = "v2")] @@ -393,9 +396,20 @@ impl Feature ) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct external vault proxy metadata")?; + let merchant_order_reference_id = self + .header_payload + .as_ref() + .and_then(|payload| payload.x_reference_id.clone()) + .map(|id| id_type::PaymentReferenceId::from_str(id.as_str())) + .transpose() + .inspect_err(|err| logger::warn!(error=?err, "Invalid Merchant ReferenceId found")) + .ok() + .flatten() + .map(ucs_types::UcsReferenceId::Payment); let headers_builder = state .get_grpc_headers_ucs() - .external_vault_proxy_metadata(Some(external_vault_proxy_metadata)); + .external_vault_proxy_metadata(Some(external_vault_proxy_metadata)) + .merchant_reference_id(merchant_order_reference_id); let updated_router_data = Box::pin(ucs_logging_wrapper( self.clone(), state, diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index ad9760f4d8..9683129ca9 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, str::FromStr}; use async_trait::async_trait; +use common_utils::{id_type, ucs_types}; use error_stack::ResultExt; use masking::Secret; use unified_connector_service_client::payments as payments_grpc; @@ -266,9 +267,20 @@ impl Feature ) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct request metadata")?; + let merchant_reference_id = self + .header_payload + .as_ref() + .and_then(|payload| payload.x_reference_id.clone()) + .map(|id| id_type::PaymentReferenceId::from_str(id.as_str())) + .transpose() + .inspect_err(|err| logger::warn!(error=?err, "Invalid Merchant ReferenceId found")) + .ok() + .flatten() + .map(ucs_types::UcsReferenceId::Payment); let header_payload = state .get_grpc_headers_ucs() - .external_vault_proxy_metadata(None); + .external_vault_proxy_metadata(None) + .merchant_reference_id(merchant_reference_id); let updated_router_data = Box::pin(ucs_logging_wrapper( self.clone(), state, diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index e5a2947b35..6e30faf714 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -1,5 +1,8 @@ +use std::str::FromStr; + use async_trait::async_trait; use common_types::payments as common_payments_types; +use common_utils::{id_type, ucs_types}; use error_stack::ResultExt; use router_env::logger; use unified_connector_service_client::payments as payments_grpc; @@ -282,9 +285,20 @@ impl Feature for types::Setup ) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct request metadata")?; + let merchant_reference_id = self + .header_payload + .as_ref() + .and_then(|payload| payload.x_reference_id.clone()) + .map(|id| id_type::PaymentReferenceId::from_str(id.as_str())) + .transpose() + .inspect_err(|err| logger::warn!(error=?err, "Invalid Merchant ReferenceId found")) + .ok() + .flatten() + .map(ucs_types::UcsReferenceId::Payment); let header_payload = state .get_grpc_headers_ucs() - .external_vault_proxy_metadata(None); + .external_vault_proxy_metadata(None) + .merchant_reference_id(merchant_reference_id); let updated_router_data = Box::pin(ucs_logging_wrapper( self.clone(), state, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index f799fdebbf..c479b01999 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -655,7 +655,7 @@ pub async fn construct_external_vault_proxy_payment_router_data<'a>( authentication_data: None, customer_acceptance: None, split_payments: None, - merchant_order_reference_id: None, + merchant_order_reference_id: payment_data.payment_intent.merchant_reference_id.clone(), integrity_object: None, shipping_cost: payment_data.payment_intent.amount_details.shipping_cost, additional_payment_method_data: None, diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index 74e9dca7e4..32953cd5ea 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -775,6 +775,7 @@ pub async fn call_unified_connector_service_for_webhook( merchant_context.get_merchant_account().get_id().clone(), )) .external_vault_proxy_metadata(None) + .merchant_reference_id(None) .build(); // Make UCS call - client availability already verified diff --git a/crates/router/src/core/unified_connector_service/transformers.rs b/crates/router/src/core/unified_connector_service/transformers.rs index 6590186234..b7dc46788c 100644 --- a/crates/router/src/core/unified_connector_service/transformers.rs +++ b/crates/router/src/core/unified_connector_service/transformers.rs @@ -291,7 +291,13 @@ impl .request .request_extended_authorization .map(|request_extended_authorization| request_extended_authorization.is_true()), - merchant_order_reference_id: router_data.request.merchant_order_reference_id.clone(), + merchant_order_reference_id: router_data + .request + .merchant_order_reference_id + .as_ref() + .map(|merchant_order_reference_id| { + merchant_order_reference_id.get_string_repr().to_string() + }), shipping_cost: router_data .request .shipping_cost diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 00df2908f4..d92dc311e0 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -98,6 +98,8 @@ pub mod headers { pub const X_CONNECTOR_HTTP_STATUS_CODE: &str = "connector_http_status_code"; #[cfg(feature = "v2")] pub const X_CONNECTOR_HTTP_STATUS_CODE: &str = "x-connector-http-status-code"; + + pub const X_REFERENCE_ID: &str = "X-Reference-Id"; } pub mod pii { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index c04eb94582..3eea8b1006 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -24,6 +24,7 @@ use crate::{ headers::{ ACCEPT_LANGUAGE, BROWSER_NAME, X_APP_ID, X_CLIENT_PLATFORM, X_CLIENT_SOURCE, X_CLIENT_VERSION, X_MERCHANT_DOMAIN, X_PAYMENT_CONFIRM_SOURCE, X_REDIRECT_URI, + X_REFERENCE_ID, }, services::authentication::get_header_value_by_key, types::{ @@ -1339,6 +1340,8 @@ impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderP let x_redirect_uri = get_header_value_by_key(X_REDIRECT_URI.into(), headers)?.map(|val| val.to_string()); + let x_reference_id = + get_header_value_by_key(X_REFERENCE_ID.into(), headers)?.map(|val| val.to_string()); Ok(Self { payment_confirm_source, @@ -1351,6 +1354,7 @@ impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderP locale, x_app_id, x_redirect_uri, + x_reference_id, }) } } @@ -1427,6 +1431,8 @@ impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderP let x_redirect_uri = get_header_value_by_key(X_REDIRECT_URI.into(), headers)?.map(|val| val.to_string()); + let x_reference_id = + get_header_value_by_key(X_REFERENCE_ID.into(), headers)?.map(|val| val.to_string()); Ok(Self { payment_confirm_source, @@ -1439,6 +1445,7 @@ impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderP locale, x_app_id, x_redirect_uri, + x_reference_id, }) } }