From 767dee9eb0b0dc8de471b47c123aee7cb516b998 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:23:02 +0530 Subject: [PATCH] feat(router): Add tokenization support for proxy and update the route for proxy (#8530) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/id_type/global_id/token.rs | 15 ++ crates/router/src/core/proxy.rs | 24 +--- crates/router/src/core/proxy/utils.rs | 131 ++++++++++-------- crates/router/src/routes/app.rs | 2 +- 4 files changed, 94 insertions(+), 78 deletions(-) diff --git a/crates/common_utils/src/id_type/global_id/token.rs b/crates/common_utils/src/id_type/global_id/token.rs index 2ea927d675..ad14458254 100644 --- a/crates/common_utils/src/id_type/global_id/token.rs +++ b/crates/common_utils/src/id_type/global_id/token.rs @@ -1,3 +1,9 @@ +use std::borrow::Cow; + +use error_stack::ResultExt; + +use crate::errors::{CustomResult, ValidationError}; + crate::global_id_type!( GlobalTokenId, "A global id that can be used to identify a token. @@ -17,6 +23,15 @@ impl GlobalTokenId { self.0.get_string_repr() } + ///Get GlobalTokenId from a string + pub fn from_string(token_string: &str) -> CustomResult { + let token = super::GlobalId::from_string(Cow::Owned(token_string.to_string())) + .change_context(ValidationError::IncorrectValueProvided { + field_name: "GlobalTokenId", + })?; + Ok(Self(token)) + } + /// Generate a new GlobalTokenId from a cell id pub fn generate(cell_id: &crate::id_type::CellId) -> Self { let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Token); diff --git a/crates/router/src/core/proxy.rs b/crates/router/src/core/proxy.rs index b6114a3e1c..0817058a48 100644 --- a/crates/router/src/core/proxy.rs +++ b/crates/router/src/core/proxy.rs @@ -3,7 +3,7 @@ use crate::{logger, routes::SessionState, services, types::domain}; pub mod utils; use api_models::proxy as proxy_api_models; use common_utils::{ - ext_traits::{BytesExt, Encode}, + ext_traits::BytesExt, request::{self, RequestBuilder}, }; use error_stack::ResultExt; @@ -24,25 +24,9 @@ pub async fn proxy_core( ) .await?; - let vault_id = proxy_record.get_vault_id()?; - let customer_id = proxy_record.get_customer_id()?; - - let vault_response = - super::payment_methods::vault::retrieve_payment_method_from_vault_internal( - &state, - &merchant_context, - &vault_id, - &customer_id, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while fetching data from vault")?; - - let vault_data = vault_response - .data - .encode_to_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to serialize vault data")?; + let vault_data = proxy_record + .get_vault_data(&state, merchant_context) + .await?; let processed_body = interpolate_token_references_with_vault_data(req.request_body.clone(), &vault_data)?; diff --git a/crates/router/src/core/proxy/utils.rs b/crates/router/src/core/proxy/utils.rs index 3bf5327aa5..5fc56bef15 100644 --- a/crates/router/src/core/proxy/utils.rs +++ b/crates/router/src/core/proxy/utils.rs @@ -1,10 +1,12 @@ use api_models::{payment_methods::PaymentMethodId, proxy as proxy_api_models}; -use common_utils::{ext_traits::OptionExt, id_type}; -use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::{ - errors::api_error_response::NotImplementedMessage, payment_methods, +use common_utils::{ + ext_traits::{Encode, OptionExt}, + id_type, }; +use error_stack::ResultExt; +use hyperswitch_domain_models::payment_methods; use masking::Mask; +use serde_json::Value; use x509_parser::nom::{ bytes::complete::{tag, take_while1}, character::complete::{char, multispace0}, @@ -13,9 +15,12 @@ use x509_parser::nom::{ }; use crate::{ - core::errors::{self, RouterResult}, + core::{ + errors::{self, RouterResult}, + payment_methods::vault, + }, routes::SessionState, - types::domain, + types::{domain, payment_methods as pm_types}, }; pub struct ProxyRequestWrapper(pub proxy_api_models::ProxyRequest); @@ -53,46 +58,23 @@ impl ProxyRequestWrapper { ))) } proxy_api_models::TokenType::TokenizationId => { - Err(report!(errors::ApiErrorResponse::NotImplemented { - message: NotImplementedMessage::Reason( - "Proxy flow using tokenization id".to_string(), - ), - })) - } - } - } + let token_id = id_type::GlobalTokenId::from_string(token.clone().as_str()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Error while coneverting from string to GlobalTokenId type", + )?; + let db = state.store.as_ref(); + let key_manager_state = &(state).into(); - pub async fn get_customer_id( - &self, - state: &SessionState, - key_store: &domain::MerchantKeyStore, - storage_scheme: common_enums::enums::MerchantStorageScheme, - ) -> RouterResult { - let token = &self.0.token; - - match self.0.token_type { - proxy_api_models::TokenType::PaymentMethodId => { - let pm_id = PaymentMethodId { - payment_method_id: token.clone(), - }; - let pm_id = - id_type::GlobalPaymentMethodId::generate_from_string(pm_id.payment_method_id) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to generate GlobalPaymentMethodId")?; - - Ok(state - .store - .find_payment_method(&((state).into()), key_store, &pm_id, storage_scheme) + let tokenization_record = db + .get_entity_id_vault_id_by_token_id(&token_id, key_store, key_manager_state) .await - .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)? - .customer_id) - } - proxy_api_models::TokenType::TokenizationId => { - Err(report!(errors::ApiErrorResponse::NotImplemented { - message: NotImplementedMessage::Reason( - "Proxy flow using tokenization id".to_string(), - ), - })) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while fetching tokenization record from vault")?; + + Ok(ProxyRecord::TokenizationRecord(Box::new( + tokenization_record, + ))) } } } @@ -116,7 +98,7 @@ impl ProxyRequestWrapper { } impl ProxyRecord { - pub fn get_vault_id(&self) -> RouterResult { + fn get_vault_id(&self) -> RouterResult { match self { Self::PaymentMethodRecord(payment_method) => payment_method .locker_id @@ -124,22 +106,57 @@ impl ProxyRecord { .get_required_value("vault_id") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Locker id not present in Payment Method Entry"), - Self::TokenizationRecord(_) => Err(report!(errors::ApiErrorResponse::NotImplemented { - message: NotImplementedMessage::Reason( - "Proxy flow using tokenization id".to_string(), - ), - })), + Self::TokenizationRecord(tokenization_record) => Ok( + payment_methods::VaultId::generate(tokenization_record.locker_id.clone()), + ), } } - pub fn get_customer_id(&self) -> RouterResult { + fn get_customer_id(&self) -> id_type::GlobalCustomerId { match self { - Self::PaymentMethodRecord(payment_method) => Ok(payment_method.customer_id.clone()), - Self::TokenizationRecord(_) => Err(report!(errors::ApiErrorResponse::NotImplemented { - message: NotImplementedMessage::Reason( - "Proxy flow using tokenization id".to_string(), - ), - })), + Self::PaymentMethodRecord(payment_method) => payment_method.customer_id.clone(), + Self::TokenizationRecord(tokenization_record) => { + tokenization_record.customer_id.clone() + } + } + } + + pub async fn get_vault_data( + &self, + state: &SessionState, + merchant_context: domain::MerchantContext, + ) -> RouterResult { + match self { + Self::PaymentMethodRecord(_) => { + let vault_resp = vault::retrieve_payment_method_from_vault_internal( + state, + &merchant_context, + &self.get_vault_id()?, + &self.get_customer_id(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while fetching data from vault")?; + + Ok(vault_resp + .data + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to serialize vault data")?) + } + Self::TokenizationRecord(_) => { + let vault_request = pm_types::VaultRetrieveRequest { + entity_id: self.get_customer_id(), + vault_id: self.get_vault_id()?, + }; + + let vault_data = vault::retrieve_value_from_vault(state, vault_request) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to retrieve vault data")?; + + Ok(vault_data.get("data").cloned().unwrap_or(Value::Null)) + } } } } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index dee2aa3594..91f35de891 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -754,7 +754,7 @@ pub struct Proxy; #[cfg(all(feature = "oltp", feature = "v2"))] impl Proxy { pub fn server(state: AppState) -> Scope { - web::scope("/proxy") + web::scope("/v2/proxy") .app_data(web::Data::new(state)) .service(web::resource("").route(web::post().to(proxy::proxy))) }