Skip to content
Open
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c247711
add api changes
prasunna09 Nov 18, 2025
5fac302
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 18, 2025
735626a
code refactoring
prasunna09 Nov 19, 2025
bcf7f9c
Merge branch 'add-guest-checkout-api-changes' of github.com:juspay/hy…
prasunna09 Nov 19, 2025
a9281ce
chore: run formatter
hyperswitch-bot[bot] Nov 19, 2025
165aa8b
resolve pr comments
prasunna09 Nov 20, 2025
9ac717a
Merge branch 'add-guest-checkout-api-changes' of github.com:juspay/hy…
prasunna09 Nov 20, 2025
e48db50
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Nov 20, 2025
c06dc3d
Merge branch 'main' into add-guest-checkout-api-changes
prasunna09 Nov 20, 2025
f43c934
chore: run formatter
hyperswitch-bot[bot] Nov 20, 2025
3128a3a
fix clippy v2
prasunna09 Nov 20, 2025
2ecbdc8
Merge branch 'add-guest-checkout-api-changes' of github.com:juspay/hy…
prasunna09 Nov 20, 2025
4ad6b86
Merge branch 'main' into add-guest-checkout-api-changes
prasunna09 Nov 20, 2025
e334729
chore: run formatter
hyperswitch-bot[bot] Nov 20, 2025
0a579e5
fix clippy v2
prasunna09 Nov 20, 2025
efd5832
Merge branch 'add-guest-checkout-api-changes' of github.com:juspay/hy…
prasunna09 Nov 20, 2025
a58f3ab
empty commit
prasunna09 Nov 20, 2025
ffcd307
chore: run formatter
hyperswitch-bot[bot] Nov 20, 2025
646f912
Merge branch 'main' into add-guest-checkout-api-changes
prasunna09 Nov 27, 2025
7a74e16
add guest checkout core flow
prasunna09 Nov 30, 2025
5fb6557
chore: run formatter
hyperswitch-bot[bot] Nov 30, 2025
7477bce
code refactoring
prasunna09 Dec 1, 2025
dea6a98
Merge branch 'guest-checkout-core-changes' of github.com:juspay/hyper…
prasunna09 Dec 1, 2025
6dc430f
chore: run formatter
hyperswitch-bot[bot] Dec 1, 2025
9519c3e
code refactoring
prasunna09 Dec 2, 2025
4063cc9
Merge branch 'guest-checkout-core-changes' of github.com:juspay/hyper…
prasunna09 Dec 2, 2025
21d7653
fix code
prasunna09 Dec 3, 2025
6ddac76
fix code
prasunna09 Dec 3, 2025
e401640
Merge branch 'main' into guest-checkout-core-changes
prasunna09 Dec 4, 2025
31faffd
chore: run formatter
hyperswitch-bot[bot] Dec 4, 2025
26941cd
fix clippy v1
prasunna09 Dec 4, 2025
b24e69a
Merge branch 'guest-checkout-core-changes' of github.com:juspay/hyper…
prasunna09 Dec 4, 2025
fef11a8
fix clippy
prasunna09 Dec 4, 2025
0067704
add volatile pm id in proxy flow
prasunna09 Dec 9, 2025
f6e608e
add volataile pm id support in proxy
prasunna09 Dec 10, 2025
d9c7fd9
fix clippy
prasunna09 Dec 10, 2025
108eebf
chore: run formatter
hyperswitch-bot[bot] Dec 10, 2025
c2aa16f
fix clippy
prasunna09 Dec 10, 2025
2bc6a77
Merge branch 'temp-pm-id-support-in-proxy' of github.com:juspay/hyper…
prasunna09 Dec 10, 2025
afb4bd0
chore: run formatter
hyperswitch-bot[bot] Dec 10, 2025
64aa19c
resolve pr comments
prasunna09 Dec 15, 2025
f67d1ed
Merge branch 'main' into temp-pm-id-support-in-proxy
prasunna09 Dec 18, 2025
d9593fc
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Dec 18, 2025
b7ae83f
fix code
prasunna09 Dec 23, 2025
16db9a2
update encryption logic in pm core
prasunna09 Dec 28, 2025
84eee04
chore: run formatter
hyperswitch-bot[bot] Dec 28, 2025
b48ff51
fix clippy
prasunna09 Dec 28, 2025
abaebbd
Merge branch 'temp-pm-id-support-in-proxy' of github.com:juspay/hyper…
prasunna09 Dec 28, 2025
73c9501
chore: run formatter
hyperswitch-bot[bot] Dec 28, 2025
b667caf
fix toolchain check
prasunna09 Dec 28, 2025
32b46eb
Merge branch 'temp-pm-id-support-in-proxy' of github.com:juspay/hyper…
prasunna09 Dec 28, 2025
f9c96b2
Merge branch 'main' into temp-pm-id-support-in-proxy
prasunna09 Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api-reference/v2/openapi_spec_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -27169,7 +27169,8 @@
"type": "string",
"enum": [
"tokenization_id",
"payment_method_id"
"payment_method_id",
"volatile_payment_method_id"
]
},
"Tokenization": {
Expand Down
2 changes: 1 addition & 1 deletion crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub struct PaymentMethodCreate {
pub payment_method_type: api_enums::PaymentMethod,

/// This is a sub-category of payment method.
#[schema(value_type = PaymentMethodType,example = "google_pay")]
#[schema(value_type = PaymentMethodType,example = "credit")]
pub payment_method_subtype: api_enums::PaymentMethodType,

/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
Expand Down
1 change: 1 addition & 0 deletions crates/api_models/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct ProxyRequest {
pub enum TokenType {
TokenizationId,
PaymentMethodId,
VolatilePaymentMethodId,
}

#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)]
Expand Down
2 changes: 2 additions & 0 deletions crates/router/src/core/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ use error_stack::{report, ResultExt};
use futures::TryStreamExt;
#[cfg(feature = "v1")]
use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::behaviour::Conversion;
use hyperswitch_domain_models::{
payments::{payment_attempt::PaymentAttempt, PaymentIntent, VaultData},
router_data_v2::flow_common_types::VaultConnectorFlowData,
Expand Down
151 changes: 138 additions & 13 deletions crates/router/src/core/proxy/utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use api_models::{payment_methods::PaymentMethodId, proxy as proxy_api_models};
use common_utils::{
ext_traits::{Encode, OptionExt},
crypto::{DecodeMessage, GcmAes256},
ext_traits::{BytesExt, Encode, OptionExt},
id_type,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::payment_methods;
use masking::Mask;
use hyperswitch_domain_models::{behaviour::Conversion, payment_methods};
use masking::{Mask, PeekInterface};
use serde_json::Value;
use x509_parser::nom::{
bytes::complete::{tag, take_while1},
Expand All @@ -26,6 +27,7 @@ use crate::{
pub struct ProxyRequestWrapper(pub proxy_api_models::ProxyRequest);
pub enum ProxyRecord {
PaymentMethodRecord(Box<domain::PaymentMethod>),
VolatilePaymentMethodRecord(Box<domain::PaymentMethod>),
TokenizationRecord(Box<domain::Tokenization>),
}

Expand Down Expand Up @@ -75,6 +77,60 @@ impl ProxyRequestWrapper {
tokenization_record,
)))
}
proxy_api_models::TokenType::VolatilePaymentMethodId => {
let pm_id = token.as_str();
let encryption_key = key_store.key.get_inner();

let redis_conn = state
.store
.get_redis_conn()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get redis connection")?;

let response = redis_conn.get_key::<bytes::Bytes>(&pm_id.into()).await;

let payment_method_record = match response {
Ok(resp) => {
let decrypted_payload = GcmAes256
.decode_message(
encryption_key.peek().as_ref(),
masking::Secret::new(resp.into()),
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to decode redis temp locker data")?;

let payment_method = bytes::Bytes::from(decrypted_payload)
.parse_struct::<diesel_models::PaymentMethod>("PaymentMethod")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Error getting PaymentMethod from tokenize response",
)?;

let keymanager_state = &state.into();

let domain_payment_method = domain::PaymentMethod::convert_back(
keymanager_state,
payment_method,
key_store.key.get_inner(),
key_store.merchant_id.clone().into(),
)
.await
.change_context(errors::StorageError::EncryptionError)
.change_context(errors::ApiErrorResponse::InternalServerError)?;

Ok(domain_payment_method)
}
Err(err) => {
Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity {
message: "Token is invalid or expired".into(),
})
}
}?;

Ok(ProxyRecord::VolatilePaymentMethodRecord(Box::new(
payment_method_record,
)))
}
}
}

Expand Down Expand Up @@ -108,19 +164,31 @@ impl ProxyRecord {
Self::TokenizationRecord(tokenization_record) => Ok(
payment_methods::VaultId::generate(tokenization_record.locker_id.clone()),
),
Self::VolatilePaymentMethodRecord(payment_method) => payment_method
.locker_id
.clone()
.get_required_value("vault_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Locker id not present in Volatile Payment Method Entry"),
}
}

fn get_customer_id(&self) -> RouterResult<id_type::GlobalCustomerId> {
fn get_customer_id(&self) -> RouterResult<Option<id_type::GlobalCustomerId>> {
match self {
Self::PaymentMethodRecord(payment_method) => payment_method
.customer_id
.clone()
.get_required_value("customer_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Customer id not present in Payment Method Entry"),
Self::PaymentMethodRecord(payment_method) => {
let customer_id = payment_method
.customer_id
.clone()
.get_required_value("customer_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Customer id not present in Payment Method Entry")?;
Ok(Some(customer_id))
}
Self::TokenizationRecord(tokenization_record) => {
Ok(tokenization_record.customer_id.clone())
Ok(Some(tokenization_record.customer_id.clone()))
}
Self::VolatilePaymentMethodRecord(payment_method) => {
Ok(payment_method.customer_id.clone())
}
}
}
Expand All @@ -132,11 +200,16 @@ impl ProxyRecord {
) -> RouterResult<Value> {
match self {
Self::PaymentMethodRecord(_) => {
let customer_id = self
.get_customer_id()?
.get_required_value("customer_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Locker id not present in Payment Method Entry")?;
let vault_resp = vault::retrieve_payment_method_from_vault_internal(
state,
&platform,
&self.get_vault_id()?,
&self.get_customer_id()?,
&customer_id,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
Expand All @@ -149,8 +222,13 @@ impl ProxyRecord {
.attach_printable("Failed to serialize vault data")?)
}
Self::TokenizationRecord(_) => {
let customer_id = self
.get_customer_id()?
.get_required_value("customer_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Locker id not present in Tokenization Record")?;
let vault_request = pm_types::VaultRetrieveRequest {
entity_id: self.get_customer_id()?,
entity_id: customer_id,
vault_id: self.get_vault_id()?,
};

Expand All @@ -161,6 +239,53 @@ impl ProxyRecord {

Ok(vault_data.get("data").cloned().unwrap_or(Value::Null))
}
Self::VolatilePaymentMethodRecord(_) => {
//retrieve from redis
let vault_id = self.get_vault_id()?;
let key_store = platform.get_processor().get_key_store();
let encryption_key = key_store.key.get_inner();

let redis_conn = state
.store
.get_redis_conn()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get redis connection")?;

let response = redis_conn
.get_key::<bytes::Bytes>(&vault_id.get_string_repr().into())
.await;

match response {
Ok(resp) => {
let decrypted_payload = GcmAes256
.decode_message(
encryption_key.peek().as_ref(),
masking::Secret::new(resp.into()),
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to decode redis temp locker data")?;

let vault_data = bytes::Bytes::from(decrypted_payload)
.parse_struct::<domain::PaymentMethodVaultingData>(
"PaymentMethodVaultingData",
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Error getting PaymentMethodVaultingData from tokenize response",
)?;

Ok(vault_data
.encode_to_value()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize vault data")?)
}
Err(err) => {
Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity {
message: "Token is invalid or expired".into(),
})
}
}
}
}
}
}
Expand Down
Loading