Bug: [FEATURE] Implement a masking strategy for UPI VPAs



### Feature Description

Unified Payments Interface (UPI) is a popular payment technique in India, used to transfer money among individuals, bill payments and merchant payments. In the case of money transfer among individuals, users are identified with a Virtual Payment Address (VPA). A VPA is typically of the following format: `<identifier>@<bank_name>`. (Feel free to read up more about UPI if you aren't already familiar with it.)

Since a VPA can uniquely identify a user, we'd want to mask VPAs in our application. However, we'd want to keep the `<bank_name>` part unmasked, since it could help us build analytics on the banks or payment service providers used, and possibly improve our routing experience.

### Possible Implementation

We'd want you to implement `masking::Strategy` for UPI VPAs (which could be called `UpiVpaMaskingStrategy`) which masks the identifier completely, and keeps the bank name part in clear text. You can refer to the email masking strategy to get an idea of how this can be done. Please include unit tests for the same.

https://github.com/juspay/hyperswitch/blob/2f9c28938f95a58532604817b1ed370ef8285dd8/crates/common_utils/src/pii.rs#L190-L205

Also, update the type of `vpa_id` field in `api_models::payments::UpiData` to be `Option<Secret<String, UpiVpaMaskingStrategy>>` instead.

https://github.com/juspay/hyperswitch/blob/2f9c28938f95a58532604817b1ed370ef8285dd8/crates/api_models/src/payments.rs#L783-L786
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 6e489ac492a..a184382e654 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<String>, example = "successtest@iata")]
-    pub vpa_id: Option<Secret<String>>,
+    pub vpa_id: Option<Secret<String, pii::UpiVpaMaskingStrategy>>,
 }
 
 #[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 fddbdcdab82..c246d204226 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<T> Strategy<T> for UpiVpaMaskingStrategy
+where
+    T: AsRef<str> + 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<String> = Secret::new("+40712345678".to_string());
         assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
     }
+
+    #[test]
+    fn test_valid_upi_vpa_masking() {
+        let secret: Secret<String, UpiVpaMaskingStrategy> = Secret::new("my_name@upi".to_string());
+        assert_eq!("*******@upi", format!("{secret:?}"));
+    }
+
+    #[test]
+    fn test_invalid_upi_vpa_masking() {
+        let secret: Secret<String, UpiVpaMaskingStrategy> = 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 43eeedcfde4..f50725d9f23 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<FromStrategy, ToStrategy> {
+    /// 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<S, FromStrategy, ToStrategy> SwitchStrategy<FromStrategy, ToStrategy>
+    for Secret<S, FromStrategy>
+where
+    FromStrategy: crate::Strategy<S>,
+    ToStrategy: crate::Strategy<S>,
+{
+    type Output = Secret<S, ToStrategy>;
+
+    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 d0452d5c5bc..8c5b03bd4f1 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 cdbb6bffdff..3cd51886508 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<String>,
+    pub vpa_id: masking::Secret<String, UpiVpaMaskingStrategy>,
 }
 
 #[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 ff200a354c0..9ad5d845d4e 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 =

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Here, I have implemented masking::Strategy for UPI VPAs which is called UpiVpaMaskingStrategy which masks the identifier completely, and keeps the bank name part in clear text. I have included tests for the same. 

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
Fixes #1578.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [x] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
