diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 6e489ac492..a184382e65 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -922,7 +922,7 @@ pub struct CryptoData { #[serde(rename_all = "snake_case")] pub struct UpiData { #[schema(value_type = Option, example = "successtest@iata")] - pub vpa_id: Option>, + pub vpa_id: Option>, } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/common_utils/src/pii.rs b/crates/common_utils/src/pii.rs index fddbdcdab8..c246d20422 100644 --- a/crates/common_utils/src/pii.rs +++ b/crates/common_utils/src/pii.rs @@ -329,13 +329,33 @@ where } } +/// Strategy for masking UPI VPA's + +#[derive(Debug)] +pub struct UpiVpaMaskingStrategy; + +impl Strategy for UpiVpaMaskingStrategy +where + T: AsRef + std::fmt::Debug, +{ + fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let vpa_str: &str = val.as_ref(); + if let Some((user_identifier, bank_or_psp)) = vpa_str.split_once('@') { + let masked_user_identifier = "*".repeat(user_identifier.len()); + write!(f, "{masked_user_identifier}@{bank_or_psp}") + } else { + WithType::fmt(val, f) + } + } +} + #[cfg(test)] mod pii_masking_strategy_tests { use std::str::FromStr; use masking::{ExposeInterface, Secret}; - use super::{ClientSecret, Email, IpAddress}; + use super::{ClientSecret, Email, IpAddress, UpiVpaMaskingStrategy}; use crate::pii::{EmailStrategy, REDACTED}; /* @@ -435,4 +455,16 @@ mod pii_masking_strategy_tests { let secret: Secret = Secret::new("+40712345678".to_string()); assert_eq!("*** alloc::string::String ***", format!("{secret:?}")); } + + #[test] + fn test_valid_upi_vpa_masking() { + let secret: Secret = Secret::new("my_name@upi".to_string()); + assert_eq!("*******@upi", format!("{secret:?}")); + } + + #[test] + fn test_invalid_upi_vpa_masking() { + let secret: Secret = Secret::new("my_name_upi".to_string()); + assert_eq!("*** alloc::string::String ***", format!("{secret:?}")); + } } diff --git a/crates/masking/src/abs.rs b/crates/masking/src/abs.rs index 43eeedcfde..f50725d9f2 100644 --- a/crates/masking/src/abs.rs +++ b/crates/masking/src/abs.rs @@ -40,3 +40,25 @@ where self.inner_secret } } + +/// Interface that consumes a secret and converts it to a secret with a different masking strategy. +pub trait SwitchStrategy { + /// The type returned by `switch_strategy()`. + type Output; + + /// Consumes the secret and converts it to a secret with a different masking strategy. + fn switch_strategy(self) -> Self::Output; +} + +impl SwitchStrategy + for Secret +where + FromStrategy: crate::Strategy, + ToStrategy: crate::Strategy, +{ + type Output = Secret; + + fn switch_strategy(self) -> Self::Output { + Secret::new(self.inner_secret) + } +} diff --git a/crates/masking/src/lib.rs b/crates/masking/src/lib.rs index d0452d5c5b..8c5b03bd4f 100644 --- a/crates/masking/src/lib.rs +++ b/crates/masking/src/lib.rs @@ -16,7 +16,7 @@ mod strategy; pub use strategy::{Strategy, WithType, WithoutType}; mod abs; -pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface}; +pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface, SwitchStrategy}; mod secret; mod strong_secret; diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index cdbb6bffdf..3cd5188650 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -5,7 +5,7 @@ use common_utils::{ crypto::Encryptable, date_time, ext_traits::StringExt, - pii::{IpAddress, SecretSerdeValue}, + pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy}, }; use error_stack::{IntoReport, ResultExt}; use serde::{Deserialize, Serialize}; @@ -63,7 +63,7 @@ pub enum StripeWallet { #[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)] pub struct StripeUpi { - pub vpa_id: masking::Secret, + pub vpa_id: masking::Secret, } #[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)] diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index ff200a354c..9ad5d845d4 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use api_models::enums::PaymentMethod; -use masking::Secret; +use masking::{Secret, SwitchStrategy}; use serde::{Deserialize, Serialize}; use crate::{ @@ -90,9 +90,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest { }; let return_url = item.get_return_url()?; let payer_info = match item.request.payment_method_data.clone() { - api::PaymentMethodData::Upi(upi_data) => { - upi_data.vpa_id.map(|id| PayerInfo { token_id: id }) - } + api::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo { + token_id: id.switch_strategy(), + }), _ => None, }; let amount =