feat(router): adding generic tokenization endpoint (#7905)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Shivansh Mathur
2025-05-27 11:39:36 +05:30
committed by GitHub
parent c4a5e3ac16
commit 49a178ed20
60 changed files with 1450 additions and 38 deletions

View File

@ -32,6 +32,9 @@ pub mod utils;
use common_utils::{errors::CustomResult, types::keymanager::KeyManagerState};
use database::store::PgPool;
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use diesel_models::tokenization::Tokenization;
pub mod tokenization;
#[cfg(not(feature = "payouts"))]
use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface};
pub use mock_db::MockDb;
@ -502,3 +505,14 @@ impl UniqueConstraints for diesel_models::Customer {
impl<T: DatabaseStore> PayoutAttemptInterface for RouterStore<T> {}
#[cfg(not(feature = "payouts"))]
impl<T: DatabaseStore> PayoutsInterface for RouterStore<T> {}
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
impl UniqueConstraints for diesel_models::tokenization::Tokenization {
fn unique_constraints(&self) -> Vec<String> {
vec![format!("id_{}", self.id.get_string_repr())]
}
fn table_name(&self) -> &str {
"tokenization"
}
}

View File

@ -45,6 +45,8 @@ pub struct MockDb {
pub mandates: Arc<Mutex<Vec<store::Mandate>>>,
pub captures: Arc<Mutex<Vec<store::capture::Capture>>>,
pub merchant_key_store: Arc<Mutex<Vec<store::merchant_key_store::MerchantKeyStore>>>,
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
pub tokenizations: Arc<Mutex<Vec<store::tokenization::Tokenization>>>,
pub business_profiles: Arc<Mutex<Vec<store::business_profile::Profile>>>,
pub reverse_lookups: Arc<Mutex<Vec<store::ReverseLookup>>>,
pub payment_link: Arc<Mutex<Vec<store::payment_link::PaymentLink>>>,
@ -92,6 +94,8 @@ impl MockDb {
mandates: Default::default(),
captures: Default::default(),
merchant_key_store: Default::default(),
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
tokenizations: Default::default(),
business_profiles: Default::default(),
reverse_lookups: Default::default(),
payment_link: Default::default(),

View File

@ -0,0 +1,174 @@
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use async_bb8_diesel::AsyncRunQueryDsl;
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use common_utils::{
errors::CustomResult,
ext_traits::OptionExt,
id_type::{CellId, GlobalTokenId, MerchantId},
types::keymanager::KeyManagerState,
};
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use diesel::{ExpressionMethods, Insertable, RunQueryDsl};
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use diesel_models::{
enums::TokenizationFlag as DbTokenizationFlag,
schema_v2::tokenization::dsl as tokenization_dsl, tokenization, PgPooledConn,
};
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use error_stack::{report, Report, ResultExt};
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use hyperswitch_domain_models::tokenization::Tokenization;
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use hyperswitch_domain_models::{
behaviour::{Conversion, ReverseConversion},
merchant_key_store::MerchantKeyStore,
};
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use tokio::time;
use super::MockDb;
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
use crate::{
connection, diesel_error_to_data_error, errors,
kv_router_store::{
FilterResourceParams, FindResourceBy, InsertResourceParams, UpdateResourceParams,
},
redis::kv_store::{Op, PartitionKey},
utils::{pg_connection_read, pg_connection_write},
};
use crate::{kv_router_store::KVRouterStore, DatabaseStore, RouterStore};
#[cfg(not(all(feature = "v2", feature = "tokenization_v2")))]
pub trait TokenizationInterface {}
#[async_trait::async_trait]
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
pub trait TokenizationInterface {
async fn insert_tokenization(
&self,
tokenization: hyperswitch_domain_models::tokenization::Tokenization,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>;
async fn get_entity_id_vault_id_by_token_id(
&self,
token: &common_utils::id_type::GlobalTokenId,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>;
}
#[async_trait::async_trait]
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
impl<T: DatabaseStore> TokenizationInterface for RouterStore<T> {
async fn insert_tokenization(
&self,
tokenization: hyperswitch_domain_models::tokenization::Tokenization,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
let conn = connection::pg_connection_write(self).await?;
tokenization
.construct_new()
.await
.change_context(errors::StorageError::EncryptionError)?
.insert(&conn)
.await
.map_err(|error| report!(errors::StorageError::from(error)))?
.convert(
key_manager_state,
merchant_key_store.key.get_inner(),
merchant_key_store.merchant_id.clone().into(),
)
.await
.change_context(errors::StorageError::DecryptionError)
}
async fn get_entity_id_vault_id_by_token_id(
&self,
token: &common_utils::id_type::GlobalTokenId,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
let conn = connection::pg_connection_read(self).await?;
let tokenization = diesel_models::tokenization::Tokenization::find_by_id(&conn, token)
.await
.map_err(|error| report!(errors::StorageError::from(error)))?;
let domain = tokenization
.convert(
key_manager_state,
merchant_key_store.key.get_inner(),
merchant_key_store.merchant_id.clone().into(),
)
.await
.change_context(errors::StorageError::DecryptionError)?;
Ok(domain)
}
}
#[async_trait::async_trait]
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
impl<T: DatabaseStore> TokenizationInterface for KVRouterStore<T> {
async fn insert_tokenization(
&self,
tokenization: hyperswitch_domain_models::tokenization::Tokenization,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
self.router_store
.insert_tokenization(tokenization, merchant_key_store, key_manager_state)
.await
}
async fn get_entity_id_vault_id_by_token_id(
&self,
token: &common_utils::id_type::GlobalTokenId,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
self.router_store
.get_entity_id_vault_id_by_token_id(token, merchant_key_store, key_manager_state)
.await
}
}
#[async_trait::async_trait]
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
impl TokenizationInterface for MockDb {
async fn insert_tokenization(
&self,
tokenization: hyperswitch_domain_models::tokenization::Tokenization,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
Err(errors::StorageError::MockDbError)?
}
async fn get_entity_id_vault_id_by_token_id(
&self,
token: &common_utils::id_type::GlobalTokenId,
merchant_key_store: &MerchantKeyStore,
key_manager_state: &KeyManagerState,
) -> CustomResult<hyperswitch_domain_models::tokenization::Tokenization, errors::StorageError>
{
Err(errors::StorageError::MockDbError)?
}
}
#[cfg(not(all(feature = "v2", feature = "tokenization_v2")))]
impl TokenizationInterface for MockDb {}
#[cfg(not(all(feature = "v2", feature = "tokenization_v2")))]
impl<T: DatabaseStore> TokenizationInterface for KVRouterStore<T> {}
#[cfg(not(all(feature = "v2", feature = "tokenization_v2")))]
impl<T: DatabaseStore> TokenizationInterface for RouterStore<T> {}