mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	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>
This commit is contained in:
		| @ -1,3 +1,9 @@ | |||||||
|  | use std::borrow::Cow; | ||||||
|  |  | ||||||
|  | use error_stack::ResultExt; | ||||||
|  |  | ||||||
|  | use crate::errors::{CustomResult, ValidationError}; | ||||||
|  |  | ||||||
| crate::global_id_type!( | crate::global_id_type!( | ||||||
|     GlobalTokenId, |     GlobalTokenId, | ||||||
|     "A global id that can be used to identify a token. |     "A global id that can be used to identify a token. | ||||||
| @ -17,6 +23,15 @@ impl GlobalTokenId { | |||||||
|         self.0.get_string_repr() |         self.0.get_string_repr() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     ///Get GlobalTokenId from a string | ||||||
|  |     pub fn from_string(token_string: &str) -> CustomResult<Self, ValidationError> { | ||||||
|  |         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 |     /// Generate a new GlobalTokenId from a cell id | ||||||
|     pub fn generate(cell_id: &crate::id_type::CellId) -> Self { |     pub fn generate(cell_id: &crate::id_type::CellId) -> Self { | ||||||
|         let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Token); |         let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Token); | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ use crate::{logger, routes::SessionState, services, types::domain}; | |||||||
| pub mod utils; | pub mod utils; | ||||||
| use api_models::proxy as proxy_api_models; | use api_models::proxy as proxy_api_models; | ||||||
| use common_utils::{ | use common_utils::{ | ||||||
|     ext_traits::{BytesExt, Encode}, |     ext_traits::BytesExt, | ||||||
|     request::{self, RequestBuilder}, |     request::{self, RequestBuilder}, | ||||||
| }; | }; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| @ -24,25 +24,9 @@ pub async fn proxy_core( | |||||||
|         ) |         ) | ||||||
|         .await?; |         .await?; | ||||||
|  |  | ||||||
|     let vault_id = proxy_record.get_vault_id()?; |     let vault_data = proxy_record | ||||||
|     let customer_id = proxy_record.get_customer_id()?; |         .get_vault_data(&state, merchant_context) | ||||||
|  |         .await?; | ||||||
|     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 processed_body = |     let processed_body = | ||||||
|         interpolate_token_references_with_vault_data(req.request_body.clone(), &vault_data)?; |         interpolate_token_references_with_vault_data(req.request_body.clone(), &vault_data)?; | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| use api_models::{payment_methods::PaymentMethodId, proxy as proxy_api_models}; | use api_models::{payment_methods::PaymentMethodId, proxy as proxy_api_models}; | ||||||
| use common_utils::{ext_traits::OptionExt, id_type}; | use common_utils::{ | ||||||
| use error_stack::{report, ResultExt}; |     ext_traits::{Encode, OptionExt}, | ||||||
| use hyperswitch_domain_models::{ |     id_type, | ||||||
|     errors::api_error_response::NotImplementedMessage, payment_methods, |  | ||||||
| }; | }; | ||||||
|  | use error_stack::ResultExt; | ||||||
|  | use hyperswitch_domain_models::payment_methods; | ||||||
| use masking::Mask; | use masking::Mask; | ||||||
|  | use serde_json::Value; | ||||||
| use x509_parser::nom::{ | use x509_parser::nom::{ | ||||||
|     bytes::complete::{tag, take_while1}, |     bytes::complete::{tag, take_while1}, | ||||||
|     character::complete::{char, multispace0}, |     character::complete::{char, multispace0}, | ||||||
| @ -13,9 +15,12 @@ use x509_parser::nom::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::errors::{self, RouterResult}, |     core::{ | ||||||
|  |         errors::{self, RouterResult}, | ||||||
|  |         payment_methods::vault, | ||||||
|  |     }, | ||||||
|     routes::SessionState, |     routes::SessionState, | ||||||
|     types::domain, |     types::{domain, payment_methods as pm_types}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| pub struct ProxyRequestWrapper(pub proxy_api_models::ProxyRequest); | pub struct ProxyRequestWrapper(pub proxy_api_models::ProxyRequest); | ||||||
| @ -53,46 +58,23 @@ impl ProxyRequestWrapper { | |||||||
|                 ))) |                 ))) | ||||||
|             } |             } | ||||||
|             proxy_api_models::TokenType::TokenizationId => { |             proxy_api_models::TokenType::TokenizationId => { | ||||||
|                 Err(report!(errors::ApiErrorResponse::NotImplemented { |                 let token_id = id_type::GlobalTokenId::from_string(token.clone().as_str()) | ||||||
|                     message: NotImplementedMessage::Reason( |  | ||||||
|                         "Proxy flow using tokenization id".to_string(), |  | ||||||
|                     ), |  | ||||||
|                 })) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub async fn get_customer_id( |  | ||||||
|         &self, |  | ||||||
|         state: &SessionState, |  | ||||||
|         key_store: &domain::MerchantKeyStore, |  | ||||||
|         storage_scheme: common_enums::enums::MerchantStorageScheme, |  | ||||||
|     ) -> RouterResult<id_type::GlobalCustomerId> { |  | ||||||
|         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) |                     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|                         .attach_printable("Unable to generate GlobalPaymentMethodId")?; |                     .attach_printable( | ||||||
|  |                         "Error while coneverting from string to GlobalTokenId type", | ||||||
|  |                     )?; | ||||||
|  |                 let db = state.store.as_ref(); | ||||||
|  |                 let key_manager_state = &(state).into(); | ||||||
|  |  | ||||||
|                 Ok(state |                 let tokenization_record = db | ||||||
|                     .store |                     .get_entity_id_vault_id_by_token_id(&token_id, key_store, key_manager_state) | ||||||
|                     .find_payment_method(&((state).into()), key_store, &pm_id, storage_scheme) |  | ||||||
|                     .await |                     .await | ||||||
|                     .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)? |                     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|                     .customer_id) |                     .attach_printable("Error while fetching tokenization record from vault")?; | ||||||
|             } |  | ||||||
|             proxy_api_models::TokenType::TokenizationId => { |                 Ok(ProxyRecord::TokenizationRecord(Box::new( | ||||||
|                 Err(report!(errors::ApiErrorResponse::NotImplemented { |                     tokenization_record, | ||||||
|                     message: NotImplementedMessage::Reason( |                 ))) | ||||||
|                         "Proxy flow using tokenization id".to_string(), |  | ||||||
|                     ), |  | ||||||
|                 })) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -116,7 +98,7 @@ impl ProxyRequestWrapper { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl ProxyRecord { | impl ProxyRecord { | ||||||
|     pub fn get_vault_id(&self) -> RouterResult<payment_methods::VaultId> { |     fn get_vault_id(&self) -> RouterResult<payment_methods::VaultId> { | ||||||
|         match self { |         match self { | ||||||
|             Self::PaymentMethodRecord(payment_method) => payment_method |             Self::PaymentMethodRecord(payment_method) => payment_method | ||||||
|                 .locker_id |                 .locker_id | ||||||
| @ -124,22 +106,57 @@ impl ProxyRecord { | |||||||
|                 .get_required_value("vault_id") |                 .get_required_value("vault_id") | ||||||
|                 .change_context(errors::ApiErrorResponse::InternalServerError) |                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|                 .attach_printable("Locker id not present in Payment Method Entry"), |                 .attach_printable("Locker id not present in Payment Method Entry"), | ||||||
|             Self::TokenizationRecord(_) => Err(report!(errors::ApiErrorResponse::NotImplemented { |             Self::TokenizationRecord(tokenization_record) => Ok( | ||||||
|                 message: NotImplementedMessage::Reason( |                 payment_methods::VaultId::generate(tokenization_record.locker_id.clone()), | ||||||
|                     "Proxy flow using tokenization id".to_string(), |  | ||||||
|             ), |             ), | ||||||
|             })), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_customer_id(&self) -> RouterResult<id_type::GlobalCustomerId> { |     fn get_customer_id(&self) -> id_type::GlobalCustomerId { | ||||||
|         match self { |         match self { | ||||||
|             Self::PaymentMethodRecord(payment_method) => Ok(payment_method.customer_id.clone()), |             Self::PaymentMethodRecord(payment_method) => payment_method.customer_id.clone(), | ||||||
|             Self::TokenizationRecord(_) => Err(report!(errors::ApiErrorResponse::NotImplemented { |             Self::TokenizationRecord(tokenization_record) => { | ||||||
|                 message: NotImplementedMessage::Reason( |                 tokenization_record.customer_id.clone() | ||||||
|                     "Proxy flow using tokenization id".to_string(), |             } | ||||||
|                 ), |         } | ||||||
|             })), |     } | ||||||
|  |  | ||||||
|  |     pub async fn get_vault_data( | ||||||
|  |         &self, | ||||||
|  |         state: &SessionState, | ||||||
|  |         merchant_context: domain::MerchantContext, | ||||||
|  |     ) -> RouterResult<Value> { | ||||||
|  |         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)) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -754,7 +754,7 @@ pub struct Proxy; | |||||||
| #[cfg(all(feature = "oltp", feature = "v2"))] | #[cfg(all(feature = "oltp", feature = "v2"))] | ||||||
| impl Proxy { | impl Proxy { | ||||||
|     pub fn server(state: AppState) -> Scope { |     pub fn server(state: AppState) -> Scope { | ||||||
|         web::scope("/proxy") |         web::scope("/v2/proxy") | ||||||
|             .app_data(web::Data::new(state)) |             .app_data(web::Data::new(state)) | ||||||
|             .service(web::resource("").route(web::post().to(proxy::proxy))) |             .service(web::resource("").route(web::post().to(proxy::proxy))) | ||||||
|     } |     } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Prasunna Soppa
					Prasunna Soppa