mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 03:13:56 +08:00
feat(core): [proxy payments] send external vault proxy metadata to UCS (#9108)
This commit is contained in:
@ -156,6 +156,23 @@ pub struct ConnectorAuthMetadata {
|
||||
pub merchant_id: Secret<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
impl UnifiedConnectorServiceClient {
|
||||
/// Builds the connection to the gRPC service
|
||||
pub async fn build_connections(config: &GrpcClientSettings) -> Option<Self> {
|
||||
@ -206,13 +223,18 @@ impl UnifiedConnectorServiceClient {
|
||||
&self,
|
||||
payment_authorize_request: payments_grpc::PaymentServiceAuthorizeRequest,
|
||||
connector_auth_metadata: ConnectorAuthMetadata,
|
||||
external_vault_proxy_metadata: Option<String>,
|
||||
grpc_headers: GrpcHeaders,
|
||||
) -> UnifiedConnectorServiceResult<tonic::Response<PaymentServiceAuthorizeResponse>> {
|
||||
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<String>,
|
||||
grpc_headers: GrpcHeaders,
|
||||
) -> Result<MetadataMap, UnifiedConnectorServiceError> {
|
||||
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()
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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<String>,
|
||||
}
|
||||
#[cfg(feature = "v2")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountReferenceMap {
|
||||
|
||||
@ -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<domain::Customer>,
|
||||
call_connector_action: CallConnectorAction,
|
||||
schedule_time: Option<time::PrimitiveDateTime>,
|
||||
header_payload: HeaderPayload,
|
||||
frm_suggestion: Option<storage_enums::FrmSuggestion>,
|
||||
business_profile: &domain::Profile,
|
||||
is_retry_payment: bool,
|
||||
should_retry_with_pan: bool,
|
||||
all_keys_required: Option<bool>,
|
||||
) -> RouterResult<(
|
||||
domain::MerchantConnectorAccountTypeDetails,
|
||||
domain::MerchantConnectorAccountTypeDetails,
|
||||
Option<storage::CustomerUpdate>,
|
||||
RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
)>
|
||||
where
|
||||
F: Send + Clone + Sync,
|
||||
RouterDReq: Send + Sync,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
|
||||
D: ConstructFlowSpecificData<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
RouterData<F, RouterDReq, router_types::PaymentsResponseData>: Feature<F, RouterDReq> + Send,
|
||||
// To construct connector flow specific api
|
||||
dyn api::Connector:
|
||||
services::api::ConnectorIntegration<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
{
|
||||
// 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<F, RouterDReq, ApiRequest, D>(
|
||||
@ -4930,6 +5019,7 @@ pub async fn call_unified_connector_service_for_external_proxy<F, RouterDReq, Ap
|
||||
_should_retry_with_pan: bool,
|
||||
_return_raw_connector_response: Option<bool>,
|
||||
merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails,
|
||||
external_vault_merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails,
|
||||
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
|
||||
_updated_customer: Option<storage::CustomerUpdate>,
|
||||
) -> RouterResult<RouterData<F, RouterDReq, router_types::PaymentsResponseData>>
|
||||
@ -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?;
|
||||
|
||||
@ -218,6 +218,22 @@ pub trait Feature<F, T> {
|
||||
{
|
||||
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<F, T, types::PaymentsResponseData>,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether a capture API call should be made for a payment attempt
|
||||
|
||||
@ -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
|
||||
|
||||
@ -359,12 +359,12 @@ impl Feature<api::ExternalVaultProxy, types::ExternalVaultProxyPaymentsData>
|
||||
}
|
||||
}
|
||||
|
||||
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<api::ExternalVaultProxy, types::ExternalVaultProxyPaymentsData>
|
||||
.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
|
||||
|
||||
@ -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<String, UnifiedConnectorServiceError> {
|
||||
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<
|
||||
|
||||
Reference in New Issue
Block a user