Bug: [FEATURE] Single-Use PSP tokenization for making a payment



### Feature Description

Have a single use PSP tokenisation feature which creates a token at PSP end for the payment methods service and return it to the user for using the same for using it to make payments

### Possible Implementation

Add it to the confirm call of the payment method session call where we decided to create a Payment Method for the request given

### Have you spent some time checking if this feature request has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index a9de9611e56..9f641ddff62 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -19487,7 +19487,8 @@
         "type": "object",
         "description": "The Payment Service Provider Configuration for payment methods that are created using the payment method session",
         "required": [
-          "tokenization_type"
+          "tokenization_type",
+          "connector_id"
         ],
         "properties": {
           "tokenization_type": {
@@ -19495,8 +19496,7 @@
           },
           "connector_id": {
             "type": "string",
-            "description": "The merchant connector id to be used for tokenization",
-            "nullable": true
+            "description": "The merchant connector id to be used for tokenization"
           }
         }
       },
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 56bffc02640..1dfa9ce9d42 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -455,6 +455,15 @@ impl PaymentMethodCreate {
             _ => false,
         }
     }
+    pub fn get_tokenize_connector_id(
+        &self,
+    ) -> Result<id_type::MerchantConnectorAccountId, error_stack::Report<errors::ValidationError>>
+    {
+        self.psp_tokenization
+            .clone()
+            .get_required_value("psp_tokenization")
+            .map(|psp| psp.connector_id)
+    }
 }
 
 #[cfg(all(
diff --git a/crates/common_types/src/payment_methods.rs b/crates/common_types/src/payment_methods.rs
index 79cf34e1103..c6b492a1030 100644
--- a/crates/common_types/src/payment_methods.rs
+++ b/crates/common_types/src/payment_methods.rs
@@ -162,6 +162,6 @@ pub struct PspTokenization {
     pub tokenization_type: common_enums::TokenizationType,
 
     /// The merchant connector id to be used for tokenization
-    #[schema(value_type = Option<String>)]
-    pub connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
+    #[schema(value_type = String)]
+    pub connector_id: common_utils::id_type::MerchantConnectorAccountId,
 }
diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs
index 1f99e5cbfb5..f69b9dc6855 100644
--- a/crates/hyperswitch_domain_models/src/payment_method_data.rs
+++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs
@@ -6,6 +6,8 @@ use api_models::{
     },
 };
 use common_enums::enums as api_enums;
+#[cfg(feature = "v2")]
+use common_utils::ext_traits::OptionExt;
 use common_utils::{
     id_type,
     new_type::{
@@ -16,7 +18,6 @@ use common_utils::{
 use masking::{PeekInterface, Secret};
 use serde::{Deserialize, Serialize};
 use time::Date;
-
 // We need to derive Serialize and Deserialize because some parts of payment method data are being
 // stored in the database as serde_json::Value
 #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
@@ -688,6 +689,40 @@ pub enum MobilePaymentData {
     },
 }
 
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+impl TryFrom<payment_methods::PaymentMethodCreateData> for PaymentMethodData {
+    type Error = error_stack::Report<common_utils::errors::ValidationError>;
+
+    fn try_from(value: payment_methods::PaymentMethodCreateData) -> Result<Self, Self::Error> {
+        match value {
+            payment_methods::PaymentMethodCreateData::Card(payment_methods::CardDetail {
+                card_number,
+                card_exp_month,
+                card_exp_year,
+                card_cvc,
+                card_issuer,
+                card_network,
+                card_type,
+                card_issuing_country,
+                nick_name,
+                card_holder_name,
+            }) => Ok(Self::Card(Card {
+                card_number,
+                card_exp_month,
+                card_exp_year,
+                card_cvc: card_cvc.get_required_value("card_cvc")?,
+                card_issuer,
+                card_network,
+                card_type: card_type.map(|card_type| card_type.to_string()),
+                card_issuing_country: card_issuing_country.map(|country| country.to_string()),
+                bank_code: None,
+                nick_name,
+                card_holder_name,
+            })),
+        }
+    }
+}
+
 impl From<api_models::payments::PaymentMethodData> for PaymentMethodData {
     fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self {
         match api_model_payment_method_data {
@@ -1891,6 +1926,26 @@ impl From<NetworkTokenDetails> for NetworkTokenDetailsPaymentMethod {
     }
 }
 
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+pub struct SingleUsePaymentMethodToken {
+    pub token: Secret<String>,
+    pub merchant_connector_id: id_type::MerchantConnectorAccountId,
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+impl SingleUsePaymentMethodToken {
+    pub fn get_single_use_token_from_payment_method_token(
+        token: Secret<String>,
+        mca_id: id_type::MerchantConnectorAccountId,
+    ) -> Self {
+        Self {
+            token,
+            merchant_connector_id: mca_id,
+        }
+    }
+}
+
 impl From<NetworkTokenDetailsPaymentMethod> for payment_methods::NetworkTokenDetailsPaymentMethod {
     fn from(item: NetworkTokenDetailsPaymentMethod) -> Self {
         Self {
@@ -1908,3 +1963,19 @@ impl From<NetworkTokenDetailsPaymentMethod> for payment_methods::NetworkTokenDet
         }
     }
 }
+
+#[cfg(feature = "v2")]
+#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
+pub struct SingleUseTokenKey(String);
+
+#[cfg(feature = "v2")]
+impl SingleUseTokenKey {
+    pub fn store_key(payment_method_id: &id_type::GlobalPaymentMethodId) -> Self {
+        let new_token = format!("single_use_token_{}", payment_method_id.get_string_repr());
+        Self(new_token)
+    }
+
+    pub fn get_store_key(&self) -> &str {
+        &self.0
+    }
+}
diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs
index b6911f1f562..f06343ff9b4 100644
--- a/crates/router/src/connector/stripe/transformers.rs
+++ b/crates/router/src/connector/stripe/transformers.rs
@@ -464,6 +464,7 @@ pub struct MultibancoCreditTransferSourceRequest {
 #[derive(Debug, Eq, PartialEq, Serialize)]
 #[serde(untagged)]
 pub enum StripePaymentMethodData {
+    CardToken(StripeCardToken),
     Card(StripeCardData),
     PayLater(StripePayLaterData),
     Wallet(StripeWallet),
@@ -472,6 +473,19 @@ pub enum StripePaymentMethodData {
     BankTransfer(StripeBankTransferData),
 }
 
+// Struct to call the Stripe tokens API to create a PSP token for the card details provided
+#[derive(Debug, Eq, PartialEq, Serialize)]
+pub struct StripeCardToken {
+    #[serde(rename = "card[number]")]
+    pub token_card_number: cards::CardNumber,
+    #[serde(rename = "card[exp_month]")]
+    pub token_card_exp_month: Secret<String>,
+    #[serde(rename = "card[exp_year]")]
+    pub token_card_exp_year: Secret<String>,
+    #[serde(rename = "card[cvc]")]
+    pub token_card_cvc: Secret<String>,
+}
+
 #[derive(Debug, Eq, PartialEq, Serialize)]
 #[serde(tag = "payment_method_data[type]")]
 pub enum BankDebitData {
@@ -2094,15 +2108,30 @@ impl TryFrom<&types::SetupMandateRouterData> for SetupIntentRequest {
 impl TryFrom<&types::TokenizationRouterData> for TokenRequest {
     type Error = error_stack::Report<errors::ConnectorError>;
     fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
-        let payment_data = create_stripe_payment_method(
-            &item.request.payment_method_data,
-            item.auth_type,
-            item.payment_method_token.clone(),
-            None,
-            StripeBillingAddress::default(),
-        )?;
+        // Card flow for tokenization is handled seperately because of API contact difference
+        let request_payment_data = match &item.request.payment_method_data {
+            domain::PaymentMethodData::Card(card_details) => {
+                StripePaymentMethodData::CardToken(StripeCardToken {
+                    token_card_number: card_details.card_number.clone(),
+                    token_card_exp_month: card_details.card_exp_month.clone(),
+                    token_card_exp_year: card_details.card_exp_year.clone(),
+                    token_card_cvc: card_details.card_cvc.clone(),
+                })
+            }
+            _ => {
+                create_stripe_payment_method(
+                    &item.request.payment_method_data,
+                    item.auth_type,
+                    item.payment_method_token.clone(),
+                    None,
+                    StripeBillingAddress::default(),
+                )?
+                .0
+            }
+        };
+
         Ok(Self {
-            token_data: payment_data.0,
+            token_data: request_payment_data,
         })
     }
 }
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index e368a94b488..43041ef01b6 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -235,3 +235,12 @@ pub(crate) const SENDER_ID: &[u8] = b"Google";
 
 /// Default value for the number of attempts to retry fetching forex rates
 pub const DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS: u64 = 3;
+
+/// Default payment intent id
+pub const IRRELEVANT_PAYMENT_INTENT_ID: &str = "irrelevant_payment_intent_id";
+
+/// Default payment attempt id
+pub const IRRELEVANT_PAYMENT_ATTEMPT_ID: &str = "irrelevant_payment_attempt_id";
+
+// Default payment method storing TTL in redis in seconds
+pub const DEFAULT_PAYMENT_METHOD_STORE_TTL: i64 = 86400; // 1 day
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index 27fb3959bfe..100b10012bc 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -15,7 +15,6 @@ pub mod transformers;
 pub mod utils;
 mod validator;
 pub mod vault;
-
 use std::borrow::Cow;
 #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
 use std::collections::HashSet;
@@ -34,6 +33,7 @@ use common_utils::{consts::DEFAULT_LOCALE, id_type};
 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
 use common_utils::{
     crypto::Encryptable,
+    errors::CustomResult,
     ext_traits::{AsyncExt, Encode, ValueExt},
     fp_utils::when,
     generate_id, types as util_types,
@@ -59,6 +59,8 @@ use masking::{PeekInterface, Secret};
 use router_env::{instrument, tracing};
 use time::Duration;
 
+#[cfg(feature = "v2")]
+use super::payments::tokenization;
 use super::{
     errors::{RouterResponse, StorageErrorExt},
     pm_auth,
@@ -67,24 +69,28 @@ use super::{
 use crate::{
     configs::settings,
     core::{payment_methods::transformers as pm_transforms, payments as payments_core},
+    db::errors::ConnectorErrorExt,
     headers, logger,
     routes::{self, payment_methods as pm_routes},
     services::encryption,
     types::{
+        self,
         api::{self, payment_methods::PaymentMethodCreateExt},
         domain::types as domain_types,
         payment_methods as pm_types,
         storage::{ephemeral_key, PaymentMethodListContext},
         transformers::{ForeignFrom, ForeignTryFrom},
+        Tokenizable,
     },
     utils::ext_traits::OptionExt,
 };
 use crate::{
     consts,
     core::{
-        errors::{self, RouterResult},
+        errors::{ProcessTrackerError, RouterResult},
         payments::helpers as payment_helpers,
     },
+    errors,
     routes::{app::StorageInterface, SessionState},
     services,
     types::{
@@ -206,7 +212,7 @@ pub async fn initiate_pm_collect_link(
         )
         .await?;
 
-    // Create DB entries
+    // Create DB entrie
     let pm_collect_link = create_pm_collect_db_entry(
         &state,
         &merchant_account,
@@ -466,7 +472,7 @@ pub async fn add_payment_method_status_update_task(
     prev_status: enums::PaymentMethodStatus,
     curr_status: enums::PaymentMethodStatus,
     merchant_id: &id_type::MerchantId,
-) -> Result<(), errors::ProcessTrackerError> {
+) -> Result<(), ProcessTrackerError> {
     let created_at = payment_method.created_at;
     let schedule_time =
         created_at.saturating_add(Duration::seconds(consts::DEFAULT_SESSION_EXPIRY));
@@ -988,7 +994,7 @@ pub async fn create_payment_method_core(
                 .change_context(errors::ApiErrorResponse::InternalServerError)
                 .attach_printable("Failed to update payment method in db")?;
 
-            let resp = pm_transforms::generate_payment_method_response(&payment_method)?;
+            let resp = pm_transforms::generate_payment_method_response(&payment_method, &None)?;
 
             Ok((resp, payment_method))
         }
@@ -1033,7 +1039,7 @@ pub async fn network_tokenize_and_vault_the_pmd(
     network_tokenization_enabled_for_profile: bool,
     customer_id: &id_type::GlobalCustomerId,
 ) -> Option<NetworkTokenPaymentMethodDetails> {
-    let network_token_pm_details_result: errors::CustomResult<
+    let network_token_pm_details_result: CustomResult<
         NetworkTokenPaymentMethodDetails,
         errors::NetworkTokenizationError,
     > = async {
@@ -1272,7 +1278,7 @@ pub async fn payment_method_intent_create(
     .await
     .attach_printable("Failed to add Payment method to DB")?;
 
-    let resp = pm_transforms::generate_payment_method_response(&payment_method)?;
+    let resp = pm_transforms::generate_payment_method_response(&payment_method, &None)?;
 
     Ok(services::ApplicationResponse::Json(resp))
 }
@@ -1523,7 +1529,7 @@ pub async fn create_payment_method_for_intent(
     payment_method_billing_address: Option<
         Encryptable<hyperswitch_domain_models::address::Address>,
     >,
-) -> errors::CustomResult<domain::PaymentMethod, errors::ApiErrorResponse> {
+) -> CustomResult<domain::PaymentMethod, errors::ApiErrorResponse> {
     let db = &*state.store;
 
     let current_time = common_utils::date_time::now();
@@ -1758,7 +1764,7 @@ fn get_pm_list_context(
         }
         Some(payment_methods::PaymentMethodsData::BankDetails(bank_details)) => {
             let get_bank_account_token_data =
-                || -> errors::CustomResult<payment_methods::BankAccountTokenData, errors::ApiErrorResponse> {
+                || -> CustomResult<payment_methods::BankAccountTokenData,errors::ApiErrorResponse> {
                     let connector_details = bank_details
                         .connector_details
                         .first()
@@ -1885,7 +1891,14 @@ pub async fn retrieve_payment_method(
         .await
         .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
 
-    transformers::generate_payment_method_response(&payment_method)
+    let single_use_token_in_cache = get_single_use_token_from_store(
+        &state.clone(),
+        payment_method_data::SingleUseTokenKey::store_key(&pm_id.clone()),
+    )
+    .await
+    .unwrap_or_default();
+
+    transformers::generate_payment_method_response(&payment_method, &single_use_token_in_cache)
         .map(services::ApplicationResponse::Json)
 }
 
@@ -2006,7 +2019,7 @@ pub async fn update_payment_method_core(
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Failed to update payment method in db")?;
 
-    let response = pm_transforms::generate_payment_method_response(&payment_method)?;
+    let response = pm_transforms::generate_payment_method_response(&payment_method, &None)?;
 
     // Add a PT task to handle payment_method delete from vault
 
@@ -2545,7 +2558,7 @@ pub async fn payment_methods_session_confirm(
     let payment_method = create_payment_method_core(
         &state,
         &req_state,
-        create_payment_method_request,
+        create_payment_method_request.clone(),
         &merchant_account,
         &key_store,
         &profile,
@@ -2578,7 +2591,18 @@ pub async fn payment_methods_session_confirm(
             tokenization_type: common_enums::TokenizationType::SingleUse,
             ..
         }) => {
-            todo!("single use tokenization are not implemented")
+            Box::pin(create_single_use_tokenization_flow(
+                state.clone(),
+                req_state.clone(),
+                merchant_account.clone(),
+                profile.clone(),
+                key_store.clone(),
+                &create_payment_method_request.clone(),
+                &payment_method,
+                &payment_method_session,
+            ))
+            .await?;
+            None
         }
         None => None,
     };
@@ -2650,3 +2674,194 @@ impl pm_types::SavedPMLPaymentsInfo {
         Ok(())
     }
 }
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+#[allow(clippy::too_many_arguments)]
+async fn create_single_use_tokenization_flow(
+    state: SessionState,
+    req_state: routes::app::ReqState,
+    merchant_account: domain::MerchantAccount,
+    profile: domain::Profile,
+    key_store: domain::MerchantKeyStore,
+    payment_method_create_request: &payment_methods::PaymentMethodCreate,
+    payment_method: &api::PaymentMethodResponse,
+    payment_method_session: &domain::payment_methods::PaymentMethodSession,
+) -> RouterResult<()> {
+    let customer_id = payment_method_create_request.customer_id.to_owned();
+    let connector_id = payment_method_create_request
+        .get_tokenize_connector_id()
+        .change_context(errors::ApiErrorResponse::MissingRequiredField {
+            field_name: "psp_tokenization.connector_id",
+        })
+        .attach_printable("Failed to get tokenize connector id")?;
+
+    let db = &state.store;
+
+    let merchant_connector_account_details = db
+        .find_merchant_connector_account_by_id(&(&state).into(), &connector_id, &key_store)
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
+            id: connector_id.get_string_repr().to_owned(),
+        })
+        .attach_printable("error while fetching merchant_connector_account from connector_id")?;
+    let auth_type = merchant_connector_account_details
+        .get_connector_account_details()
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed while parsing value for ConnectorAuthType")?;
+
+    let payment_method_data_request = types::PaymentMethodTokenizationData {
+        payment_method_data: payment_method_data::PaymentMethodData::try_from(
+            payment_method_create_request.payment_method_data.clone(),
+        )
+        .change_context(errors::ApiErrorResponse::MissingRequiredField {
+            field_name: "card_cvc",
+        })
+        .attach_printable(
+            "Failed to convert type from Payment Method Create Data to Payment Method Data",
+        )?,
+        browser_info: None,
+        currency: api_models::enums::Currency::default(),
+        amount: None,
+    };
+
+    let payment_method_session_address = types::PaymentAddress::new(
+        None,
+        payment_method_session
+            .billing
+            .clone()
+            .map(|address| address.into_inner()),
+        None,
+        None,
+    );
+
+    let mut router_data =
+        types::RouterData::<api::PaymentMethodToken, _, types::PaymentsResponseData> {
+            flow: std::marker::PhantomData,
+            merchant_id: merchant_account.get_id().clone(),
+            customer_id: None,
+            connector_customer: None,
+            connector: merchant_connector_account_details
+                .connector_name
+                .to_string(),
+            payment_id: consts::IRRELEVANT_PAYMENT_INTENT_ID.to_string(), //Static
+            attempt_id: consts::IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(), //Static
+            tenant_id: state.tenant.tenant_id.clone(),
+            status: common_enums::enums::AttemptStatus::default(),
+            payment_method: common_enums::enums::PaymentMethod::Card,
+            connector_auth_type: auth_type,
+            description: None,
+            address: payment_method_session_address,
+            auth_type: common_enums::enums::AuthenticationType::default(),
+            connector_meta_data: None,
+            connector_wallets_details: None,
+            amount_captured: None,
+            access_token: None,
+            session_token: None,
+            reference_id: None,
+            payment_method_token: None,
+            recurring_mandate_payment_data: None,
+            preprocessing_id: None,
+            payment_method_balance: None,
+            connector_api_version: None,
+            request: payment_method_data_request.clone(),
+            response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()),
+            connector_request_reference_id: payment_method_session.id.get_string_repr().to_string(),
+            #[cfg(feature = "payouts")]
+            payout_method_data: None,
+            #[cfg(feature = "payouts")]
+            quote_id: None,
+            test_mode: None,
+            connector_http_status_code: None,
+            external_latency: None,
+            apple_pay_flow: None,
+            frm_metadata: None,
+            dispute_id: None,
+            refund_id: None,
+            connector_response: None,
+            payment_method_status: None,
+            minor_amount_captured: None,
+            integrity_check: Ok(()),
+            additional_merchant_data: None,
+            header_payload: None,
+            connector_mandate_request_reference_id: None,
+            authentication_id: None,
+            psd2_sca_exemption_type: None,
+        };
+
+    let payment_method_token_response = tokenization::add_token_for_payment_method(
+        &mut router_data,
+        payment_method_data_request.clone(),
+        state.clone(),
+        &merchant_connector_account_details.clone(),
+    )
+    .await?;
+
+    let token_response = payment_method_token_response.token.map_err(|err| {
+        errors::ApiErrorResponse::ExternalConnectorError {
+            code: err.code,
+            message: err.message,
+            connector: (merchant_connector_account_details.clone())
+                .connector_name
+                .to_string(),
+            status_code: err.status_code,
+            reason: err.reason,
+        }
+    })?;
+
+    let value = payment_method_data::SingleUsePaymentMethodToken::get_single_use_token_from_payment_method_token(
+                                                       token_response.clone().into(),
+                                                connector_id.clone()
+                                            );
+
+    let key = payment_method_data::SingleUseTokenKey::store_key(&payment_method.id);
+
+    add_single_use_token_to_store(&state, key, value)
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to store single use token")?;
+
+    Ok(())
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+async fn add_single_use_token_to_store(
+    state: &SessionState,
+    key: payment_method_data::SingleUseTokenKey,
+    value: payment_method_data::SingleUsePaymentMethodToken,
+) -> CustomResult<(), errors::StorageError> {
+    let redis_connection = state
+        .store
+        .get_redis_conn()
+        .map_err(Into::<errors::StorageError>::into)?;
+
+    redis_connection
+        .serialize_and_set_key_with_expiry(
+            &payment_method_data::SingleUseTokenKey::get_store_key(&key).into(),
+            value,
+            consts::DEFAULT_PAYMENT_METHOD_STORE_TTL,
+        )
+        .await
+        .change_context(errors::StorageError::KVError)
+        .attach_printable("Failed to insert payment method token to redis")?;
+    Ok(())
+}
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+async fn get_single_use_token_from_store(
+    state: &SessionState,
+    key: payment_method_data::SingleUseTokenKey,
+) -> CustomResult<Option<payment_method_data::SingleUsePaymentMethodToken>, errors::StorageError> {
+    let redis_connection = state
+        .store
+        .get_redis_conn()
+        .map_err(Into::<errors::StorageError>::into)?;
+
+    redis_connection
+        .get_and_deserialize_key::<Option<payment_method_data::SingleUsePaymentMethodToken>>(
+            &payment_method_data::SingleUseTokenKey::get_store_key(&key).into(),
+            "SingleUsePaymentMethodToken",
+        )
+        .await
+        .change_context(errors::StorageError::KVError)
+        .attach_printable("Failed to get payment method token from redis")
+}
diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs
index a7817329607..56a31732859 100644
--- a/crates/router/src/core/payment_methods/transformers.rs
+++ b/crates/router/src/core/payment_methods/transformers.rs
@@ -6,6 +6,8 @@ use common_utils::{
     request::RequestContent,
 };
 use error_stack::ResultExt;
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+use hyperswitch_domain_models::payment_method_data;
 use josekit::jwe;
 use router_env::tracing_actix_web::RequestId;
 use serde::{Deserialize, Serialize};
@@ -548,6 +550,7 @@ pub fn generate_pm_vaulting_req_from_update_request(
 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
 pub fn generate_payment_method_response(
     payment_method: &domain::PaymentMethod,
+    single_use_token: &Option<payment_method_data::SingleUsePaymentMethodToken>,
 ) -> errors::RouterResult<api::PaymentMethodResponse> {
     let pmd = payment_method
         .payment_method_data
@@ -559,8 +562,7 @@ pub fn generate_payment_method_response(
             }
             _ => None,
         });
-
-    let connector_tokens = payment_method
+    let mut connector_tokens = payment_method
         .connector_mandate_details
         .as_ref()
         .and_then(|connector_token_details| connector_token_details.payments.clone())
@@ -570,7 +572,19 @@ pub fn generate_payment_method_response(
                 .into_iter()
                 .map(transformers::ForeignFrom::foreign_from)
                 .collect::<Vec<_>>()
-        });
+        })
+        .unwrap_or_default();
+
+    if let Some(token) = single_use_token {
+        let connector_token_single_use = transformers::ForeignFrom::foreign_from(token);
+        connector_tokens.push(connector_token_single_use);
+    }
+    let connector_tokens = if connector_tokens.is_empty() {
+        None
+    } else {
+        Some(connector_tokens)
+    };
+
     let network_token_pmd = payment_method
         .network_token_payment_method_data
         .clone()
@@ -1113,3 +1127,21 @@ impl
         }
     }
 }
+
+#[cfg(feature = "v2")]
+impl transformers::ForeignFrom<&payment_method_data::SingleUsePaymentMethodToken>
+    for api_models::payment_methods::ConnectorTokenDetails
+{
+    fn foreign_from(token: &payment_method_data::SingleUsePaymentMethodToken) -> Self {
+        Self {
+            connector_id: token.clone().merchant_connector_id,
+            token_type: common_enums::TokenizationType::SingleUse,
+            status: api_enums::ConnectorTokenStatus::Active,
+            connector_token_request_reference_id: None,
+            original_payment_authorized_amount: None,
+            original_payment_authorized_currency: None,
+            metadata: None,
+            token: token.clone().token,
+        }
+    }
+}
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 43d190edc74..1bc0152f4dd 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -1343,3 +1343,68 @@ pub fn update_connector_mandate_details_status(
         payouts: None,
     }))
 }
+
+#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
+pub async fn add_token_for_payment_method(
+    router_data: &mut types::RouterData<
+        api::PaymentMethodToken,
+        types::PaymentMethodTokenizationData,
+        types::PaymentsResponseData,
+    >,
+    payment_method_data_request: types::PaymentMethodTokenizationData,
+    state: SessionState,
+    merchant_connector_account_details: &hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount,
+) -> RouterResult<types::PspTokenResult> {
+    let connector_id = merchant_connector_account_details.id.clone();
+    let connector_data = api::ConnectorData::get_connector_by_name(
+        &(state.conf.connectors),
+        &merchant_connector_account_details
+            .connector_name
+            .to_string(),
+        api::GetToken::Connector,
+        Some(connector_id.clone()),
+    )?;
+
+    let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
+        api::PaymentMethodToken,
+        types::PaymentMethodTokenizationData,
+        types::PaymentsResponseData,
+    > = connector_data.connector.get_connector_integration();
+
+    let payment_method_token_response_data_type: Result<
+        types::PaymentsResponseData,
+        types::ErrorResponse,
+    > = Err(types::ErrorResponse::default());
+
+    let payment_method_token_router_data =
+        helpers::router_data_type_conversion::<_, api::PaymentMethodToken, _, _, _, _>(
+            router_data.clone(),
+            payment_method_data_request.clone(),
+            payment_method_token_response_data_type,
+        );
+
+    let connector_integration_response = services::execute_connector_processing_step(
+        &state,
+        connector_integration,
+        &payment_method_token_router_data,
+        payments::CallConnectorAction::Trigger,
+        None,
+    )
+    .await
+    .to_payment_failed_response()?;
+    let payment_token_response = connector_integration_response.response.map(|res| {
+        if let types::PaymentsResponseData::TokenizationResponse { token } = res {
+            Ok(token)
+        } else {
+            Err(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to get token from connector")
+        }
+    });
+
+    match payment_token_response {
+        Ok(token) => Ok(types::PspTokenResult { token: Ok(token?) }),
+        Err(error_response) => Ok(types::PspTokenResult {
+            token: Err(error_response),
+        }),
+    }
+}
diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs
index ff9eef080a6..1e1509f8dbc 100644
--- a/crates/router/src/types.rs
+++ b/crates/router/src/types.rs
@@ -513,6 +513,10 @@ pub struct PaymentMethodTokenResult {
     pub is_payment_method_tokenization_performed: bool,
 }
 
+pub struct PspTokenResult {
+    pub token: Result<String, ErrorResponse>,
+}
+
 #[derive(Debug, Clone, Copy)]
 pub enum Redirection {
     Redirect,

## 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 -->
Add single use token implementation for Payment Method Service

The feature would help to provide a single use PSP token for creating payment 

### 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 it is an obvious bug or documentation fix
that will have little conversation).
-->


## 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)?
-->
Create Payment Method Session

Request
```bash
curl --location 'http://localhost:8080/v2/payment-methods-session' \
--header 'x-profile-id: pro_2A1cEgnP0mOT4rqUqLtt' \
--header 'Authorization: api-key=dev_KVJIPqLBqYEGWUSse6Bggow5xL2PkaWLMYcpgTGcvr334sCVwagyUNbrBOTRnESf' \
--header 'Content-Type: application/json' \
--data-raw '{
    "customer_id": "12345_cus_0195ab1d3f807a80bd6611b1b71cd1d0",
     "billing": {
        "address": {
            "first_name": "John",
            "last_name": "Dough"
        },
        "email": "example@example.com"
    },"psp_tokenization": {
        "tokenization_type": "single_use",
        "connector_id": "mca_lWefajNQ9HaKiJUhvAZo"
    }
}'
```
Response
```json
{
    "id": "12345_pms_0195ab1d677970a3bf12c0c019d42f6b",
    "customer_id": "12345_cus_0195ab1d3f807a80bd6611b1b71cd1d0",
    "billing": {
        "address": {
            "city": null,
            "country": null,
            "line1": null,
            "line2": null,
            "line3": null,
            "zip": null,
            "state": null,
            "first_name": "John",
            "last_name": "Dough"
        },
        "phone": null,
        "email": "example@example.com"
    },
    "psp_tokenization": {
        "tokenization_type": "single_use",
        "connector_id": "mca_lWefajNQ9HaKiJUhvAZo"
    },
    "network_tokenization": null,
    "expires_at": "2025-03-18T21:31:25.850Z",
    "client_secret": "cs_0195ab1d677a73c2a9b18b77354ad4ae",
    "return_url": null,
    "next_action": null,
    "authentication_details": null,
    "associated_payment_methods": null
}
```


Confirm call for the Payment method session with `tokenization_type` as `single_use` under  `psp_tokenization` in the request

Request
```bash
curl --location 'http://localhost:8080/v2/payment-methods-session/12345_pms_0195ab1d677970a3bf12c0c019d42f6b/confirm' \
--header 'x-profile-id: pro_2A1cEgnP0mOT4rqUqLtt' \
--header 'Authorization: publishable-key=pk_dev_427ac8f9e07d403a9dc21d71be6725cb,client-secret=cs_0195ab1d677a73c2a9b18b77354ad4ae' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_KVJIPqLBqYEGWUSse6Bggow5xL2PkaWLMYcpgTGcvr334sCVwagyUNbrBOTRnESf' \
--data '{
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "04",
            "card_exp_year": "40",
            "card_holder_name": "John Doe",
            "card_cvc": "100"
        }
    },
    "payment_method_type": "card",
    "payment_method_subtype": "credit",
    "psp_tokenization": {
        "tokenization_type": "single_use",
        "connector_id": "mca_wmwOLCiB3NoMv8YwyxcE"
    }
}'
```

Response
```json
{
    "id": "12345_pms_0195ab1d677970a3bf12c0c019d42f6b",
    "customer_id": "12345_cus_0195ab1d3f807a80bd6611b1b71cd1d0",
    "billing": {
        "address": {
            "city": null,
            "country": null,
            "line1": null,
            "line2": null,
            "line3": null,
            "zip": null,
            "state": null,
            "first_name": "John",
            "last_name": "Dough"
        },
        "phone": null,
        "email": "example@example.com"
    },
    "psp_tokenization": {
        "tokenization_type": "single_use",
        "connector_id": "mca_lWefajNQ9HaKiJUhvAZo"
    },
    "network_tokenization": null,
    "expires_at": "2025-03-18T21:31:25.850Z",
    "client_secret": "CLIENT_SECRET_REDACTED",
    "return_url": null,
    "next_action": null,
    "authentication_details": null,
    "associated_payment_methods": null
}
```

The way the same is stored in the Storage ( Redis )

```bash
127.0.0.1:6379> get single_use_token_12345_pm_0195d26b9d2b71f094ba39e9fd131ca8
"{\"token\":\"tok_1R6t2xD5R7gDAGffQLz0D4fj\",\"merchant_connector_id\":\"mca_lWefajNQ9HaKiJUhvAZo\"}"
```


Retrieve API for the payment method created

Request

```bash
curl --location 'http://localhost:8080/v2/payment-methods/12345_pm_0195d26b9d2b71f094ba39e9fd131ca8' \
--header 'Content-Type: application/json' \
--header 'x-profile-id: pro_2A1cEgnP0mOT4rqUqLtt' \
--header 'api-key: dev_KVJIPqLBqYEGWUSse6Bggow5xL2PkaWLMYcpgTGcvr334sCVwagyUNbrBOTRnESf' \
--header 'Authorization: publishable-key=pk_dev_427ac8f9e07d403a9dc21d71be6725cb, api-key=dev_KVJIPqLBqYEGWUSse6Bggow5xL2PkaWLMYcpgTGcvr334sCVwagyUNbrBOTRnESf'
```

Response
```bash
{
  "id": "12345_pm_0195d26b9d2b71f094ba39e9fd131ca8",
  "merchant_id": "apka_apna_test_gvHKZJ8EatRCipXedizy",
  "customer_id": "12345_cus_0195ab1d3f807a80bd6611b1b71cd1d0",
  "payment_method_type": "card",
  "payment_method_subtype": "credit",
  "recurring_enabled": false,
  "created": "2025-03-26T12:27:02.827Z",
  "last_used_at": "2025-03-26T12:27:02.827Z",
  "payment_method_data": {
    "card": {
      "issuer_country": null,
      "last4_digits": "4242",
      "expiry_month": "04",
      "expiry_year": "28",
      "card_holder_name": "John Doe",
      "card_fingerprint": null,
      "nick_name": null,
      "card_network": null,
      "card_isin": null,
      "card_issuer": null,
      "card_type": null,
      "saved_to_locker": true
    }
  },
  "connector_tokens": [
    {
      "connector_id": "mca_lWefajNQ9HaKiJUhvAZo",
      "token_type": "single_use",
      "status": "active",
      "connector_token_request_reference_id": null,
      "original_payment_authorized_amount": null,
      "original_payment_authorized_currency": null,
      "metadata": null,
      "token": "tok_1R6t2xD5R7gDAGffQLz0D4fj"
    }
  ],
  "network_token": null
}
```

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

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
