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 81bb2dc898..c8c29d1e42 100644 --- a/crates/external_services/src/grpc_client/unified_connector_service.rs +++ b/crates/external_services/src/grpc_client/unified_connector_service.rs @@ -156,6 +156,23 @@ pub struct ConnectorAuthMetadata { pub merchant_id: Secret, } +/// External Vault Proxy Related Metadata +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +pub enum ExternalVaultProxyMetadata { + /// VGS proxy data variant + VgsMetadata(VgsMetadata), +} + +/// VGS proxy data +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct VgsMetadata { + /// External vault url + pub proxy_url: Url, + /// CA certificates to verify the vault server + pub certificate: Secret, +} + impl UnifiedConnectorServiceClient { /// Builds the connection to the gRPC service pub async fn build_connections(config: &GrpcClientSettings) -> Option { @@ -206,13 +223,18 @@ impl UnifiedConnectorServiceClient { &self, payment_authorize_request: payments_grpc::PaymentServiceAuthorizeRequest, connector_auth_metadata: ConnectorAuthMetadata, + external_vault_proxy_metadata: Option, grpc_headers: GrpcHeaders, ) -> UnifiedConnectorServiceResult> { let mut request = tonic::Request::new(payment_authorize_request); let connector_name = connector_auth_metadata.connector_name.clone(); - let metadata = - build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + let metadata = build_unified_connector_service_grpc_headers( + connector_auth_metadata, + external_vault_proxy_metadata, + grpc_headers, + )?; + *request.metadata_mut() = metadata; self.client @@ -241,8 +263,11 @@ impl UnifiedConnectorServiceClient { let mut request = tonic::Request::new(payment_get_request); let connector_name = connector_auth_metadata.connector_name.clone(); - let metadata = - build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + let metadata = build_unified_connector_service_grpc_headers( + connector_auth_metadata, + None, + grpc_headers, + )?; *request.metadata_mut() = metadata; self.client @@ -271,8 +296,11 @@ impl UnifiedConnectorServiceClient { let mut request = tonic::Request::new(payment_register_request); let connector_name = connector_auth_metadata.connector_name.clone(); - let metadata = - build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + let metadata = build_unified_connector_service_grpc_headers( + connector_auth_metadata, + None, + grpc_headers, + )?; *request.metadata_mut() = metadata; self.client @@ -302,8 +330,11 @@ impl UnifiedConnectorServiceClient { let mut request = tonic::Request::new(payment_repeat_request); let connector_name = connector_auth_metadata.connector_name.clone(); - let metadata = - build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + let metadata = build_unified_connector_service_grpc_headers( + connector_auth_metadata, + None, + grpc_headers, + )?; *request.metadata_mut() = metadata; self.client @@ -331,8 +362,11 @@ impl UnifiedConnectorServiceClient { let mut request = tonic::Request::new(webhook_transform_request); let connector_name = connector_auth_metadata.connector_name.clone(); - let metadata = - build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + let metadata = build_unified_connector_service_grpc_headers( + connector_auth_metadata, + None, + grpc_headers, + )?; *request.metadata_mut() = metadata; self.client @@ -354,6 +388,7 @@ impl UnifiedConnectorServiceClient { /// Build the gRPC Headers for Unified Connector Service Request pub fn build_unified_connector_service_grpc_headers( meta: ConnectorAuthMetadata, + external_vault_proxy_metadata: Option, grpc_headers: GrpcHeaders, ) -> Result { let mut metadata = MetadataMap::new(); @@ -405,6 +440,13 @@ pub fn build_unified_connector_service_grpc_headers( parse(common_utils_consts::X_MERCHANT_ID, meta.merchant_id.peek())?, ); + if let Some(external_vault_proxy_metadata) = external_vault_proxy_metadata { + metadata.append( + consts::UCS_HEADER_EXTERNAL_VAULT_METADATA, + parse("external_vault_metadata", &external_vault_proxy_metadata)?, + ); + }; + 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 aee7d232bb..dccc41f137 100644 --- a/crates/external_services/src/lib.rs +++ b/crates/external_services/src/lib.rs @@ -91,6 +91,9 @@ pub mod consts { /// Header key for sending the AUTH KEY MAP in currency-based authentication. pub(crate) const UCS_HEADER_AUTH_KEY_MAP: &str = "x-auth-key-map"; + + /// Header key for sending the EXTERNAL VAULT METADATA in proxy payments + pub(crate) const UCS_HEADER_EXTERNAL_VAULT_METADATA: &str = "x-external-vault-metadata"; } /// Metrics for interactions with external systems. diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index 0b1beda8dd..a4f49f1e84 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -312,6 +312,12 @@ pub struct RevenueRecoveryMetadata { pub mca_reference: AccountReferenceMap, } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Deserialize)] +pub struct ExternalVaultConnectorMetadata { + pub proxy_url: common_utils::types::Url, + pub certificate: Secret, +} #[cfg(feature = "v2")] #[derive(Debug, Clone)] pub struct AccountReferenceMap { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index da8d9321f2..77deaae2dc 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1576,8 +1576,8 @@ where let payment_data = match connector { ConnectorCallType::PreDetermined(connector_data) => { - let (mca_type_details, updated_customer, router_data) = - call_connector_service_prerequisites( + let (mca_type_details, external_vault_mca_type_details, updated_customer, router_data) = + call_connector_service_prerequisites_for_external_vault_proxy( state, req_state.clone(), &merchant_context, @@ -1614,6 +1614,7 @@ where false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType req.should_return_raw_response(), mca_type_details, + external_vault_mca_type_details, router_data, updated_customer, ) @@ -4595,6 +4596,94 @@ where )) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +#[allow(clippy::type_complexity)] +#[instrument(skip_all)] +pub async fn call_connector_service_prerequisites_for_external_vault_proxy< + F, + RouterDReq, + ApiRequest, + D, +>( + state: &SessionState, + req_state: ReqState, + merchant_context: &domain::MerchantContext, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + customer: &Option, + call_connector_action: CallConnectorAction, + schedule_time: Option, + header_payload: HeaderPayload, + frm_suggestion: Option, + business_profile: &domain::Profile, + is_retry_payment: bool, + should_retry_with_pan: bool, + all_keys_required: Option, +) -> RouterResult<( + domain::MerchantConnectorAccountTypeDetails, + domain::MerchantConnectorAccountTypeDetails, + Option, + RouterData, +)> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + // get merchant connector account related to external vault + let external_vault_source: id_type::MerchantConnectorAccountId = business_profile + .external_vault_connector_details + .clone() + .map(|connector_details| connector_details.vault_connector_id.clone()) + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("mca_id not present for external vault")?; + + let external_vault_merchant_connector_account_type_details = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new( + helpers::get_merchant_connector_account_v2( + state, + merchant_context.get_merchant_key_store(), + Some(&external_vault_source), + ) + .await?, + )); + + let (merchant_connector_account_type_details, updated_customer, router_data) = + call_connector_service_prerequisites( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + should_retry_with_pan, + all_keys_required, + ) + .await?; + Ok(( + merchant_connector_account_type_details, + external_vault_merchant_connector_account_type_details, + updated_customer, + router_data, + )) +} + #[cfg(feature = "v2")] #[instrument(skip_all)] pub async fn internal_call_connector_service_prerequisites( @@ -4930,6 +5019,7 @@ pub async fn call_unified_connector_service_for_external_proxy, merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, + external_vault_merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, mut router_data: RouterData, _updated_customer: Option, ) -> RouterResult> @@ -4962,9 +5052,10 @@ where .await?; router_data - .call_unified_connector_service( + .call_unified_connector_service_with_external_vault_proxy( state, merchant_connector_account_type_details.clone(), + external_vault_merchant_connector_account_type_details.clone(), merchant_context, ) .await?; diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 3a568a5639..d9e3e9ee34 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -218,6 +218,22 @@ pub trait Feature { { Ok(()) } + + #[cfg(feature = "v2")] + async fn call_unified_connector_service_with_external_vault_proxy<'a>( + &mut self, + _state: &SessionState, + _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + _external_vault_merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + _merchant_context: &domain::MerchantContext, + ) -> RouterResult<()> + where + F: Clone, + Self: Sized, + dyn api::Connector: services::ConnectorIntegration, + { + Ok(()) + } } /// Determines whether a capture API call should be made for a payment attempt diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index a90e6a5873..1c689943d7 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -858,6 +858,7 @@ async fn call_unified_connector_service_authorize( .payment_authorize( payment_authorize_request, connector_auth_metadata, + None, state.get_grpc_headers(), ) .await 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 636b3c23f7..ac491685b5 100644 --- a/crates/router/src/core/payments/flows/external_proxy_flow.rs +++ b/crates/router/src/core/payments/flows/external_proxy_flow.rs @@ -359,12 +359,12 @@ impl Feature } } - async fn call_unified_connector_service<'a>( + #[cfg(feature = "v2")] + async fn call_unified_connector_service_with_external_vault_proxy<'a>( &mut self, state: &SessionState, - #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, - #[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + external_vault_merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, merchant_context: &domain::MerchantContext, ) -> RouterResult<()> { let client = state @@ -387,10 +387,18 @@ impl Feature .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct request metadata")?; + let external_vault_proxy_metadata = + unified_connector_service::build_unified_connector_service_external_vault_proxy_metadata( + external_vault_merchant_connector_account + ) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct external vault proxy metadata")?; + let response = client .payment_authorize( payment_authorize_request, connector_auth_metadata, + Some(external_vault_proxy_metadata), state.get_grpc_headers(), ) .await diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index f35b90a7ed..9722f36ae4 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -1,7 +1,11 @@ use std::str::FromStr; use api_models::admin; +#[cfg(feature = "v2")] +use base64::Engine; use common_enums::{connector_enums::Connector, AttemptStatus, GatewaySystem, PaymentMethodType}; +#[cfg(feature = "v2")] +use common_utils::consts::BASE64_ENGINE; use common_utils::{errors::CustomResult, ext_traits::ValueExt}; use diesel_models::types::FeatureMetadata; use error_stack::ResultExt; @@ -10,7 +14,9 @@ use external_services::grpc_client::unified_connector_service::{ }; use hyperswitch_connectors::utils::CardData; #[cfg(feature = "v2")] -use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccountTypeDetails; +use hyperswitch_domain_models::merchant_connector_account::{ + ExternalVaultConnectorMetadata, MerchantConnectorAccountTypeDetails, +}; use hyperswitch_domain_models::{ merchant_context::MerchantContext, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, @@ -24,6 +30,8 @@ use unified_connector_service_client::payments::{ PaymentServiceAuthorizeResponse, RewardPaymentMethodType, }; +#[cfg(feature = "v2")] +use crate::types::api::enums as api_enums; use crate::{ consts, core::{ @@ -507,6 +515,58 @@ pub fn build_unified_connector_service_auth_metadata( } } +#[cfg(feature = "v2")] +pub fn build_unified_connector_service_external_vault_proxy_metadata( + external_vault_merchant_connector_account: MerchantConnectorAccountTypeDetails, +) -> CustomResult { + let external_vault_metadata = external_vault_merchant_connector_account + .get_metadata() + .ok_or(UnifiedConnectorServiceError::ParsingFailed) + .attach_printable("Failed to obtain ConnectorMetadata")?; + + let connector_name = external_vault_merchant_connector_account + .get_connector_name() + .map(|connector| connector.to_string()) + .ok_or(UnifiedConnectorServiceError::MissingConnectorName) + .attach_printable("Missing connector name")?; // always get the connector name from this call + + let external_vault_connector = api_enums::VaultConnectors::from_str(&connector_name) + .change_context(UnifiedConnectorServiceError::InvalidConnectorName) + .attach_printable("Failed to parse Vault connector")?; + + let unified_service_vault_metdata = match external_vault_connector { + api_enums::VaultConnectors::Vgs => { + let vgs_metadata: ExternalVaultConnectorMetadata = external_vault_metadata + .expose() + .parse_value("ExternalVaultConnectorMetadata") + .change_context(UnifiedConnectorServiceError::ParsingFailed) + .attach_printable("Failed to parse Vgs connector metadata")?; + + Some(external_services::grpc_client::unified_connector_service::ExternalVaultProxyMetadata::VgsMetadata( + external_services::grpc_client::unified_connector_service::VgsMetadata { + proxy_url: vgs_metadata.proxy_url, + certificate: vgs_metadata.certificate, + } + )) + } + api_enums::VaultConnectors::HyperswitchVault => None, + }; + + match unified_service_vault_metdata { + Some(metdata) => { + let external_vault_metadata_bytes = serde_json::to_vec(&metdata) + .change_context(UnifiedConnectorServiceError::ParsingFailed) + .attach_printable("Failed to convert External vault metadata to bytes")?; + + Ok(BASE64_ENGINE.encode(&external_vault_metadata_bytes)) + } + None => Err(UnifiedConnectorServiceError::NotImplemented( + "External vault proxy metadata is not supported for {connector_name}".to_string(), + ) + .into()), + } +} + pub fn handle_unified_connector_service_response_for_payment_authorize( response: PaymentServiceAuthorizeResponse, ) -> CustomResult<