Bug: [FEATURE] expose tokenize endpoints



### Feature Description

HS backend to expose endpoints to help with tokenization of customer's cards. HyperSwitch makes use of an external service which helps tokenize the card and makes it usable within HS ecosystem. Once tokenized, these cards can be used in a PG agnostic manner as they're tokenized with the card networks.

### Possible Implementation

Expose two endpoints

- /payment_methods/tokenize-card
- /payment_methods/tokenize-card-batch

The flow looks like -
- Create / update customer
- Tokenize card with the network (through external service)
- Create a payment method entry in HS storing the required references from external service
- Return response

For batch tokenization, above steps will be executed for every card to be tokenized. Any errors encountered during this operation will be aggregated and returned back in the response.


### 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 2e0d57d977e..45ff3e1f567 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -6974,6 +6974,90 @@
           "Maestro"
         ]
       },
+      "CardNetworkTokenizeRequest": {
+        "allOf": [
+          {
+            "$ref": "#/components/schemas/TokenizeDataRequest"
+          },
+          {
+            "type": "object",
+            "required": [
+              "merchant_id",
+              "customer"
+            ],
+            "properties": {
+              "merchant_id": {
+                "type": "string",
+                "description": "Merchant ID associated with the tokenization request",
+                "example": "merchant_1671528864"
+              },
+              "customer": {
+                "$ref": "#/components/schemas/CustomerDetails"
+              },
+              "billing": {
+                "allOf": [
+                  {
+                    "$ref": "#/components/schemas/Address"
+                  }
+                ],
+                "nullable": true
+              },
+              "metadata": {
+                "type": "object",
+                "description": "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.",
+                "nullable": true
+              },
+              "payment_method_issuer": {
+                "type": "string",
+                "description": "The name of the bank/ provider issuing the payment method to the end user",
+                "nullable": true
+              }
+            }
+          }
+        ]
+      },
+      "CardNetworkTokenizeResponse": {
+        "type": "object",
+        "required": [
+          "customer",
+          "card_tokenized"
+        ],
+        "properties": {
+          "payment_method_response": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/PaymentMethodResponse"
+              }
+            ],
+            "nullable": true
+          },
+          "customer": {
+            "$ref": "#/components/schemas/CustomerDetails"
+          },
+          "card_tokenized": {
+            "type": "boolean",
+            "description": "Card network tokenization status"
+          },
+          "error_code": {
+            "type": "string",
+            "description": "Error code",
+            "nullable": true
+          },
+          "error_message": {
+            "type": "string",
+            "description": "Error message",
+            "nullable": true
+          },
+          "tokenization_data": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/TokenizeDataRequest"
+              }
+            ],
+            "nullable": true
+          }
+        }
+      },
       "CardNetworkTypes": {
         "type": "object",
         "required": [
@@ -21707,6 +21791,113 @@
           "multi_use"
         ]
       },
+      "TokenizeCardRequest": {
+        "type": "object",
+        "required": [
+          "raw_card_number",
+          "card_expiry_month",
+          "card_expiry_year"
+        ],
+        "properties": {
+          "raw_card_number": {
+            "type": "string",
+            "description": "Card Number",
+            "example": "4111111145551142"
+          },
+          "card_expiry_month": {
+            "type": "string",
+            "description": "Card Expiry Month",
+            "example": "10"
+          },
+          "card_expiry_year": {
+            "type": "string",
+            "description": "Card Expiry Year",
+            "example": "25"
+          },
+          "card_cvc": {
+            "type": "string",
+            "description": "The CVC number for the card",
+            "example": "242",
+            "nullable": true
+          },
+          "card_holder_name": {
+            "type": "string",
+            "description": "Card Holder Name",
+            "example": "John Doe",
+            "nullable": true
+          },
+          "nick_name": {
+            "type": "string",
+            "description": "Card Holder's Nick Name",
+            "example": "John Doe",
+            "nullable": true
+          },
+          "card_issuing_country": {
+            "type": "string",
+            "description": "Card Issuing Country",
+            "nullable": true
+          },
+          "card_network": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/CardNetwork"
+              }
+            ],
+            "nullable": true
+          },
+          "card_issuer": {
+            "type": "string",
+            "description": "Issuer Bank for Card",
+            "nullable": true
+          },
+          "card_type": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/CardType"
+              }
+            ],
+            "nullable": true
+          }
+        },
+        "additionalProperties": false
+      },
+      "TokenizeDataRequest": {
+        "oneOf": [
+          {
+            "type": "object",
+            "required": [
+              "card"
+            ],
+            "properties": {
+              "card": {
+                "$ref": "#/components/schemas/TokenizeCardRequest"
+              }
+            }
+          },
+          {
+            "type": "object",
+            "required": [
+              "existing_payment_method"
+            ],
+            "properties": {
+              "existing_payment_method": {
+                "$ref": "#/components/schemas/TokenizePaymentMethodRequest"
+              }
+            }
+          }
+        ]
+      },
+      "TokenizePaymentMethodRequest": {
+        "type": "object",
+        "properties": {
+          "card_cvc": {
+            "type": "string",
+            "description": "The CVC number for the card",
+            "example": "242",
+            "nullable": true
+          }
+        }
+      },
       "TouchNGoRedirection": {
         "type": "object"
       },
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index ccc055a7934..4b0a0be0f26 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -556,7 +556,6 @@ pub struct CardDetail {
     pub card_type: Option<String>,
 }
 
-#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
 #[derive(
     Debug,
     serde::Deserialize,
@@ -906,7 +905,7 @@ pub struct ConnectorTokenDetails {
 }
 
 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
-#[derive(Debug, serde::Serialize, ToSchema, Clone)]
+#[derive(Debug, serde::Serialize, serde::Deserialize, ToSchema, Clone)]
 pub struct PaymentMethodResponse {
     /// The unique identifier of the Payment method
     #[schema(value_type = String, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")]
@@ -2544,6 +2543,139 @@ impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerReq
     }
 }
 
+#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
+pub struct CardNetworkTokenizeRequest {
+    /// Merchant ID associated with the tokenization request
+    #[schema(example = "merchant_1671528864", value_type = String)]
+    pub merchant_id: id_type::MerchantId,
+
+    /// Details of the card or payment method to be tokenized
+    #[serde(flatten)]
+    pub data: TokenizeDataRequest,
+
+    /// Customer details
+    #[schema(value_type = CustomerDetails)]
+    pub customer: payments::CustomerDetails,
+
+    /// The billing details of the payment method
+    #[schema(value_type = Option<Address>)]
+    pub billing: Option<payments::Address>,
+
+    /// 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.
+    #[schema(value_type = Option<Object>, example = json!({ "city": "NY", "unit": "245" }))]
+    pub metadata: Option<pii::SecretSerdeValue>,
+
+    /// The name of the bank/ provider issuing the payment method to the end user
+    pub payment_method_issuer: Option<String>,
+}
+
+impl common_utils::events::ApiEventMetric for CardNetworkTokenizeRequest {}
+
+#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum TokenizeDataRequest {
+    Card(TokenizeCardRequest),
+    ExistingPaymentMethod(TokenizePaymentMethodRequest),
+}
+
+#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
+#[serde(deny_unknown_fields)]
+pub struct TokenizeCardRequest {
+    /// Card Number
+    #[schema(value_type = String, example = "4111111145551142")]
+    pub raw_card_number: CardNumber,
+
+    /// Card Expiry Month
+    #[schema(value_type = String, example = "10")]
+    pub card_expiry_month: masking::Secret<String>,
+
+    /// Card Expiry Year
+    #[schema(value_type = String, example = "25")]
+    pub card_expiry_year: masking::Secret<String>,
+
+    /// The CVC number for the card
+    #[schema(value_type = Option<String>,  example = "242")]
+    pub card_cvc: Option<masking::Secret<String>>,
+
+    /// Card Holder Name
+    #[schema(value_type = Option<String>, example = "John Doe")]
+    pub card_holder_name: Option<masking::Secret<String>>,
+
+    /// Card Holder's Nick Name
+    #[schema(value_type = Option<String>, example = "John Doe")]
+    pub nick_name: Option<masking::Secret<String>>,
+
+    /// Card Issuing Country
+    pub card_issuing_country: Option<String>,
+
+    /// Card's Network
+    #[schema(value_type = Option<CardNetwork>)]
+    pub card_network: Option<api_enums::CardNetwork>,
+
+    /// Issuer Bank for Card
+    pub card_issuer: Option<String>,
+
+    /// Card Type
+    pub card_type: Option<CardType>,
+}
+
+#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
+pub struct TokenizePaymentMethodRequest {
+    /// Payment method's ID
+    #[serde(skip_deserializing)]
+    pub payment_method_id: String,
+
+    /// The CVC number for the card
+    #[schema(value_type = Option<String>,  example = "242")]
+    pub card_cvc: Option<masking::Secret<String>>,
+}
+
+#[derive(Debug, Default, serde::Deserialize, serde::Serialize, ToSchema)]
+pub struct CardNetworkTokenizeResponse {
+    /// Response for payment method entry in DB
+    pub payment_method_response: Option<PaymentMethodResponse>,
+
+    /// Customer details
+    #[schema(value_type = CustomerDetails)]
+    pub customer: Option<payments::CustomerDetails>,
+
+    /// Card network tokenization status
+    pub card_tokenized: bool,
+
+    /// Error code
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub error_code: Option<String>,
+
+    /// Error message
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub error_message: Option<String>,
+
+    /// Details that were sent for tokenization
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub tokenization_data: Option<TokenizeDataRequest>,
+}
+
+impl common_utils::events::ApiEventMetric for CardNetworkTokenizeResponse {}
+
+impl From<&Card> for MigrateCardDetail {
+    fn from(card: &Card) -> Self {
+        Self {
+            card_number: masking::Secret::new(card.card_number.get_card_no()),
+            card_exp_month: card.card_exp_month.clone(),
+            card_exp_year: card.card_exp_year.clone(),
+            card_holder_name: card.name_on_card.clone(),
+            nick_name: card
+                .nick_name
+                .as_ref()
+                .map(|name| masking::Secret::new(name.clone())),
+            card_issuing_country: None,
+            card_network: None,
+            card_issuer: None,
+            card_type: None,
+        }
+    }
+}
+
 #[cfg(feature = "v2")]
 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
 pub struct PaymentMethodSessionRequest {
diff --git a/crates/hyperswitch_domain_models/src/bulk_tokenization.rs b/crates/hyperswitch_domain_models/src/bulk_tokenization.rs
new file mode 100644
index 00000000000..0eff34a1bf5
--- /dev/null
+++ b/crates/hyperswitch_domain_models/src/bulk_tokenization.rs
@@ -0,0 +1,308 @@
+use api_models::{payment_methods as payment_methods_api, payments as payments_api};
+use cards::CardNumber;
+use common_enums as enums;
+use common_utils::{
+    errors,
+    ext_traits::OptionExt,
+    id_type, pii,
+    transformers::{ForeignFrom, ForeignTryFrom},
+};
+use error_stack::report;
+
+use crate::{
+    address::{Address, AddressDetails, PhoneDetails},
+    router_request_types::CustomerDetails,
+};
+
+#[derive(Debug)]
+pub struct CardNetworkTokenizeRequest {
+    pub data: TokenizeDataRequest,
+    pub customer: CustomerDetails,
+    pub billing: Option<Address>,
+    pub metadata: Option<pii::SecretSerdeValue>,
+    pub payment_method_issuer: Option<String>,
+}
+
+#[derive(Debug)]
+pub enum TokenizeDataRequest {
+    Card(TokenizeCardRequest),
+    ExistingPaymentMethod(TokenizePaymentMethodRequest),
+}
+
+#[derive(Clone, Debug)]
+pub struct TokenizeCardRequest {
+    pub raw_card_number: CardNumber,
+    pub card_expiry_month: masking::Secret<String>,
+    pub card_expiry_year: masking::Secret<String>,
+    pub card_cvc: Option<masking::Secret<String>>,
+    pub card_holder_name: Option<masking::Secret<String>>,
+    pub nick_name: Option<masking::Secret<String>>,
+    pub card_issuing_country: Option<String>,
+    pub card_network: Option<enums::CardNetwork>,
+    pub card_issuer: Option<String>,
+    pub card_type: Option<payment_methods_api::CardType>,
+}
+
+#[derive(Clone, Debug)]
+pub struct TokenizePaymentMethodRequest {
+    pub payment_method_id: String,
+    pub card_cvc: Option<masking::Secret<String>>,
+}
+
+#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
+pub struct CardNetworkTokenizeRecord {
+    // Card details
+    pub raw_card_number: Option<CardNumber>,
+    pub card_expiry_month: Option<masking::Secret<String>>,
+    pub card_expiry_year: Option<masking::Secret<String>>,
+    pub card_cvc: Option<masking::Secret<String>>,
+    pub card_holder_name: Option<masking::Secret<String>>,
+    pub nick_name: Option<masking::Secret<String>>,
+    pub card_issuing_country: Option<String>,
+    pub card_network: Option<enums::CardNetwork>,
+    pub card_issuer: Option<String>,
+    pub card_type: Option<payment_methods_api::CardType>,
+
+    // Payment method details
+    pub payment_method_id: Option<String>,
+    pub payment_method_type: Option<payment_methods_api::CardType>,
+    pub payment_method_issuer: Option<String>,
+
+    // Customer details
+    pub customer_id: id_type::CustomerId,
+    #[serde(rename = "name")]
+    pub customer_name: Option<masking::Secret<String>>,
+    #[serde(rename = "email")]
+    pub customer_email: Option<pii::Email>,
+    #[serde(rename = "phone")]
+    pub customer_phone: Option<masking::Secret<String>>,
+    #[serde(rename = "phone_country_code")]
+    pub customer_phone_country_code: Option<String>,
+
+    // Billing details
+    pub billing_address_city: Option<String>,
+    pub billing_address_country: Option<enums::CountryAlpha2>,
+    pub billing_address_line1: Option<masking::Secret<String>>,
+    pub billing_address_line2: Option<masking::Secret<String>>,
+    pub billing_address_line3: Option<masking::Secret<String>>,
+    pub billing_address_zip: Option<masking::Secret<String>>,
+    pub billing_address_state: Option<masking::Secret<String>>,
+    pub billing_address_first_name: Option<masking::Secret<String>>,
+    pub billing_address_last_name: Option<masking::Secret<String>>,
+    pub billing_phone_number: Option<masking::Secret<String>>,
+    pub billing_phone_country_code: Option<String>,
+    pub billing_email: Option<pii::Email>,
+
+    // Other details
+    pub line_number: Option<u64>,
+    pub merchant_id: Option<id_type::MerchantId>,
+}
+
+impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::CustomerDetails {
+    fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
+        Self {
+            id: record.customer_id.clone(),
+            name: record.customer_name.clone(),
+            email: record.customer_email.clone(),
+            phone: record.customer_phone.clone(),
+            phone_country_code: record.customer_phone_country_code.clone(),
+        }
+    }
+}
+
+impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::Address {
+    fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
+        Self {
+            address: Some(payments_api::AddressDetails {
+                first_name: record.billing_address_first_name.clone(),
+                last_name: record.billing_address_last_name.clone(),
+                line1: record.billing_address_line1.clone(),
+                line2: record.billing_address_line2.clone(),
+                line3: record.billing_address_line3.clone(),
+                city: record.billing_address_city.clone(),
+                zip: record.billing_address_zip.clone(),
+                state: record.billing_address_state.clone(),
+                country: record.billing_address_country,
+            }),
+            phone: Some(payments_api::PhoneDetails {
+                number: record.billing_phone_number.clone(),
+                country_code: record.billing_phone_country_code.clone(),
+            }),
+            email: record.billing_email.clone(),
+        }
+    }
+}
+
+impl ForeignTryFrom<CardNetworkTokenizeRecord> for payment_methods_api::CardNetworkTokenizeRequest {
+    type Error = error_stack::Report<errors::ValidationError>;
+    fn foreign_try_from(record: CardNetworkTokenizeRecord) -> Result<Self, Self::Error> {
+        let billing = Some(payments_api::Address::foreign_from(&record));
+        let customer = payments_api::CustomerDetails::foreign_from(&record);
+        let merchant_id = record.merchant_id.get_required_value("merchant_id")?;
+
+        match (
+            record.raw_card_number,
+            record.card_expiry_month,
+            record.card_expiry_year,
+            record.payment_method_id,
+        ) {
+            (Some(raw_card_number), Some(card_expiry_month), Some(card_expiry_year), None) => {
+                Ok(Self {
+                    merchant_id,
+                    data: payment_methods_api::TokenizeDataRequest::Card(
+                        payment_methods_api::TokenizeCardRequest {
+                            raw_card_number,
+                            card_expiry_month,
+                            card_expiry_year,
+                            card_cvc: record.card_cvc,
+                            card_holder_name: record.card_holder_name,
+                            nick_name: record.nick_name,
+                            card_issuing_country: record.card_issuing_country,
+                            card_network: record.card_network,
+                            card_issuer: record.card_issuer,
+                            card_type: record.card_type.clone(),
+                        },
+                    ),
+                    billing,
+                    customer,
+                    metadata: None,
+                    payment_method_issuer: record.payment_method_issuer,
+                })
+            }
+            (None, None, None, Some(payment_method_id)) => Ok(Self {
+                merchant_id,
+                data: payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(
+                    payment_methods_api::TokenizePaymentMethodRequest {
+                        payment_method_id,
+                        card_cvc: record.card_cvc,
+                    },
+                ),
+                billing,
+                customer,
+                metadata: None,
+                payment_method_issuer: record.payment_method_issuer,
+            }),
+            _ => Err(report!(errors::ValidationError::InvalidValue {
+                message: "Invalid record in bulk tokenization - expected one of card details or payment method details".to_string()
+            })),
+        }
+    }
+}
+
+impl ForeignFrom<&TokenizeCardRequest> for payment_methods_api::MigrateCardDetail {
+    fn foreign_from(card: &TokenizeCardRequest) -> Self {
+        Self {
+            card_number: masking::Secret::new(card.raw_card_number.get_card_no()),
+            card_exp_month: card.card_expiry_month.clone(),
+            card_exp_year: card.card_expiry_year.clone(),
+            card_holder_name: card.card_holder_name.clone(),
+            nick_name: card.nick_name.clone(),
+            card_issuing_country: card.card_issuing_country.clone(),
+            card_network: card.card_network.clone(),
+            card_issuer: card.card_issuer.clone(),
+            card_type: card
+                .card_type
+                .as_ref()
+                .map(|card_type| card_type.to_string()),
+        }
+    }
+}
+
+impl ForeignTryFrom<CustomerDetails> for payments_api::CustomerDetails {
+    type Error = error_stack::Report<errors::ValidationError>;
+    fn foreign_try_from(customer: CustomerDetails) -> Result<Self, Self::Error> {
+        Ok(Self {
+            id: customer.customer_id.get_required_value("customer_id")?,
+            name: customer.name,
+            email: customer.email,
+            phone: customer.phone,
+            phone_country_code: customer.phone_country_code,
+        })
+    }
+}
+
+impl ForeignFrom<payment_methods_api::CardNetworkTokenizeRequest> for CardNetworkTokenizeRequest {
+    fn foreign_from(req: payment_methods_api::CardNetworkTokenizeRequest) -> Self {
+        Self {
+            data: TokenizeDataRequest::foreign_from(req.data),
+            customer: CustomerDetails::foreign_from(req.customer),
+            billing: req.billing.map(ForeignFrom::foreign_from),
+            metadata: req.metadata,
+            payment_method_issuer: req.payment_method_issuer,
+        }
+    }
+}
+
+impl ForeignFrom<payment_methods_api::TokenizeDataRequest> for TokenizeDataRequest {
+    fn foreign_from(req: payment_methods_api::TokenizeDataRequest) -> Self {
+        match req {
+            payment_methods_api::TokenizeDataRequest::Card(card) => {
+                Self::Card(TokenizeCardRequest {
+                    raw_card_number: card.raw_card_number,
+                    card_expiry_month: card.card_expiry_month,
+                    card_expiry_year: card.card_expiry_year,
+                    card_cvc: card.card_cvc,
+                    card_holder_name: card.card_holder_name,
+                    nick_name: card.nick_name,
+                    card_issuing_country: card.card_issuing_country,
+                    card_network: card.card_network,
+                    card_issuer: card.card_issuer,
+                    card_type: card.card_type,
+                })
+            }
+            payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(pm) => {
+                Self::ExistingPaymentMethod(TokenizePaymentMethodRequest {
+                    payment_method_id: pm.payment_method_id,
+                    card_cvc: pm.card_cvc,
+                })
+            }
+        }
+    }
+}
+
+impl ForeignFrom<payments_api::CustomerDetails> for CustomerDetails {
+    fn foreign_from(req: payments_api::CustomerDetails) -> Self {
+        Self {
+            customer_id: Some(req.id),
+            name: req.name,
+            email: req.email,
+            phone: req.phone,
+            phone_country_code: req.phone_country_code,
+        }
+    }
+}
+
+impl ForeignFrom<payments_api::Address> for Address {
+    fn foreign_from(req: payments_api::Address) -> Self {
+        Self {
+            address: req.address.map(ForeignFrom::foreign_from),
+            phone: req.phone.map(ForeignFrom::foreign_from),
+            email: req.email,
+        }
+    }
+}
+
+impl ForeignFrom<payments_api::AddressDetails> for AddressDetails {
+    fn foreign_from(req: payments_api::AddressDetails) -> Self {
+        Self {
+            city: req.city,
+            country: req.country,
+            line1: req.line1,
+            line2: req.line2,
+            line3: req.line3,
+            zip: req.zip,
+            state: req.state,
+            first_name: req.first_name,
+            last_name: req.last_name,
+        }
+    }
+}
+
+impl ForeignFrom<payments_api::PhoneDetails> for PhoneDetails {
+    fn foreign_from(req: payments_api::PhoneDetails) -> Self {
+        Self {
+            number: req.number,
+            country_code: req.country_code,
+        }
+    }
+}
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index c97753a6dc0..004a9fd2128 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -1,6 +1,7 @@
 pub mod address;
 pub mod api;
 pub mod behaviour;
+pub mod bulk_tokenization;
 pub mod business_profile;
 pub mod callback_mapper;
 pub mod card_testing_guard_data;
diff --git a/crates/hyperswitch_domain_models/src/network_tokenization.rs b/crates/hyperswitch_domain_models/src/network_tokenization.rs
index bf87c750509..a29792e969e 100644
--- a/crates/hyperswitch_domain_models/src/network_tokenization.rs
+++ b/crates/hyperswitch_domain_models/src/network_tokenization.rs
@@ -22,7 +22,7 @@ pub struct CardData {
     pub card_number: CardNumber,
     pub exp_month: Secret<String>,
     pub exp_year: Secret<String>,
-    pub card_security_code: Secret<String>,
+    pub card_security_code: Option<Secret<String>>,
 }
 
 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs
index 2f8375b47c0..c715ef6610b 100644
--- a/crates/hyperswitch_domain_models/src/payment_method_data.rs
+++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs
@@ -102,6 +102,20 @@ pub struct CardDetailsForNetworkTransactionId {
     pub card_holder_name: Option<Secret<String>>,
 }
 
+#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
+pub struct CardDetail {
+    pub card_number: cards::CardNumber,
+    pub card_exp_month: Secret<String>,
+    pub card_exp_year: Secret<String>,
+    pub card_issuer: Option<String>,
+    pub card_network: Option<api_enums::CardNetwork>,
+    pub card_type: Option<String>,
+    pub card_issuing_country: Option<String>,
+    pub bank_code: Option<String>,
+    pub nick_name: Option<Secret<String>>,
+    pub card_holder_name: Option<Secret<String>>,
+}
+
 impl CardDetailsForNetworkTransactionId {
     pub fn get_nti_and_card_details_for_mit_flow(
         recurring_details: mandates::RecurringDetails,
@@ -129,6 +143,23 @@ impl CardDetailsForNetworkTransactionId {
     }
 }
 
+impl From<&Card> for CardDetail {
+    fn from(item: &Card) -> Self {
+        Self {
+            card_number: item.card_number.to_owned(),
+            card_exp_month: item.card_exp_month.to_owned(),
+            card_exp_year: item.card_exp_year.to_owned(),
+            card_issuer: item.card_issuer.to_owned(),
+            card_network: item.card_network.to_owned(),
+            card_type: item.card_type.to_owned(),
+            card_issuing_country: item.card_issuing_country.to_owned(),
+            bank_code: item.bank_code.to_owned(),
+            nick_name: item.nick_name.to_owned(),
+            card_holder_name: item.card_holder_name.to_owned(),
+        }
+    }
+}
+
 impl From<mandates::NetworkTransactionIdAndCardDetails> for CardDetailsForNetworkTransactionId {
     fn from(card_details_for_nti: mandates::NetworkTransactionIdAndCardDetails) -> Self {
         Self {
diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs
index 8d18d1fe59f..bcdb1c9e208 100644
--- a/crates/openapi/src/openapi.rs
+++ b/crates/openapi/src/openapi.rs
@@ -540,6 +540,12 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payment_methods::SurchargePercentage,
         api_models::payment_methods::PaymentMethodCollectLinkRequest,
         api_models::payment_methods::PaymentMethodCollectLinkResponse,
+        api_models::payment_methods::CardType,
+        api_models::payment_methods::CardNetworkTokenizeRequest,
+        api_models::payment_methods::CardNetworkTokenizeResponse,
+        api_models::payment_methods::TokenizeDataRequest,
+        api_models::payment_methods::TokenizeCardRequest,
+        api_models::payment_methods::TokenizePaymentMethodRequest,
         api_models::refunds::RefundListRequest,
         api_models::refunds::RefundListResponse,
         api_models::relay::RelayRequest,
diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs
index 0c84f67cb41..bc6c245db9e 100644
--- a/crates/openapi/src/openapi_v2.rs
+++ b/crates/openapi/src/openapi_v2.rs
@@ -512,6 +512,9 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payments::SortBy,
         api_models::payments::PaymentMethodListResponseForPayments,
         api_models::payments::ResponsePaymentMethodTypesForPayments,
+        api_models::payment_methods::CardNetworkTokenizeRequest,
+        api_models::payment_methods::CardNetworkTokenizeResponse,
+        api_models::payment_methods::CardType,
         api_models::payment_methods::RequiredFieldInfo,
         api_models::payment_methods::MaskedBankDetails,
         api_models::payment_methods::SurchargeDetailsResponse,
@@ -523,6 +526,9 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payment_methods::PaymentMethodSessionRequest,
         api_models::payment_methods::PaymentMethodSessionResponse,
         api_models::payment_methods::PaymentMethodsSessionUpdateRequest,
+        api_models::payment_methods::TokenizeCardRequest,
+        api_models::payment_methods::TokenizeDataRequest,
+        api_models::payment_methods::TokenizePaymentMethodRequest,
         api_models::refunds::RefundListRequest,
         api_models::refunds::RefundListResponse,
         api_models::payments::AmountFilter,
diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs
index 2a309848a7f..9304afc69d3 100644
--- a/crates/openapi/src/routes/payment_method.rs
+++ b/crates/openapi/src/routes/payment_method.rs
@@ -444,6 +444,45 @@ pub fn payment_method_session_list_payment_methods() {}
 )]
 pub fn payment_method_session_update_saved_payment_method() {}
 
+/// Card network tokenization - Create using raw card data
+///
+/// Create a card network token for a customer and store it as a payment method.
+/// This API expects raw card details for creating a network token with the card networks.
+#[utoipa::path(
+    post,
+    path = "/payment_methods/tokenize-card",
+    request_body = CardNetworkTokenizeRequest,
+    responses(
+        (status = 200, description = "Payment Method Created", body = CardNetworkTokenizeResponse),
+        (status = 404, description = "Customer not found"),
+    ),
+    tag = "Payment Methods",
+    operation_id = "Create card network token",
+    security(("admin_api_key" = []))
+)]
+pub async fn tokenize_card_api() {}
+
+/// Card network tokenization - Create using existing payment method
+///
+/// Create a card network token for a customer for an existing payment method.
+/// This API expects an existing payment method ID for a card.
+#[utoipa::path(
+    post,
+    path = "/payment_methods/{id}/tokenize-card",
+    request_body = CardNetworkTokenizeRequest,
+    params (
+        ("id" = String, Path, description = "The unique identifier for the Payment Method"),
+    ),
+    responses(
+        (status = 200, description = "Payment Method Updated", body = CardNetworkTokenizeResponse),
+        (status = 404, description = "Customer not found"),
+    ),
+    tag = "Payment Methods",
+    operation_id = "Create card network token",
+    security(("admin_api_key" = []))
+)]
+pub async fn tokenize_card_using_pm_api() {}
+
 /// Payment Method Session - Confirm a payment method session
 ///
 /// **Confirms a payment method session object with the payment method data**
diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs
index 215f262ea8b..f56fef1af5f 100644
--- a/crates/router/src/core/errors.rs
+++ b/crates/router/src/core/errors.rs
@@ -448,6 +448,26 @@ pub enum NetworkTokenizationError {
     ApiError,
 }
 
+#[derive(Debug, thiserror::Error)]
+pub enum BulkNetworkTokenizationError {
+    #[error("Failed to validate card details")]
+    CardValidationFailed,
+    #[error("Failed to validate payment method details")]
+    PaymentMethodValidationFailed,
+    #[error("Failed to assign a customer to the card")]
+    CustomerAssignmentFailed,
+    #[error("Failed to perform BIN lookup for the card")]
+    BinLookupFailed,
+    #[error("Failed to tokenize the card details with the network")]
+    NetworkTokenizationFailed,
+    #[error("Failed to store the card details in locker")]
+    VaultSaveFailed,
+    #[error("Failed to create a payment method entry")]
+    PaymentMethodCreationFailed,
+    #[error("Failed to update the payment method")]
+    PaymentMethodUpdationFailed,
+}
+
 #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
 #[derive(Debug, thiserror::Error)]
 pub enum RevenueRecoveryError {
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index 9f0fd705228..6232eeafe2b 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -6,6 +6,11 @@ pub mod cards;
 pub mod migration;
 pub mod network_tokenization;
 pub mod surcharge_decision_configs;
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+pub mod tokenize;
 pub mod transformers;
 pub mod utils;
 mod validator;
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 1f37cc157e4..a1f53f85482 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -68,6 +68,19 @@ use super::surcharge_decision_configs::{
     perform_surcharge_decision_management_for_payment_method_list,
     perform_surcharge_decision_management_for_saved_cards,
 };
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+use super::tokenize::NetworkTokenizationProcess;
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+use crate::core::payment_methods::{
+    add_payment_method_status_update_task, tokenize,
+    utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache},
+};
 #[cfg(all(
     any(feature = "v2", feature = "v1"),
     not(feature = "payment_methods_v2"),
@@ -98,7 +111,7 @@ use crate::{
         api::{self, routing as routing_types, PaymentMethodCreateExt},
         domain::{self, Profile},
         storage::{self, enums, PaymentMethodListContext, PaymentTokenData},
-        transformers::ForeignTryFrom,
+        transformers::{ForeignFrom, ForeignTryFrom},
     },
     utils,
     utils::OptionExt,
@@ -108,17 +121,6 @@ use crate::{
     consts as router_consts, core::payment_methods as pm_core, headers,
     types::payment_methods as pm_types, utils::ConnectorResponseExt,
 };
-#[cfg(all(
-    any(feature = "v1", feature = "v2"),
-    not(feature = "payment_methods_v2")
-))]
-use crate::{
-    core::payment_methods::{
-        add_payment_method_status_update_task,
-        utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache},
-    },
-    types::transformers::ForeignFrom,
-};
 
 #[cfg(all(
     any(feature = "v1", feature = "v2"),
@@ -6021,3 +6023,181 @@ pub async fn list_countries_currencies_for_connector_payment_method_util(
             .collect(),
     }
 }
+
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+pub async fn tokenize_card_flow(
+    state: &routes::SessionState,
+    req: domain::CardNetworkTokenizeRequest,
+    merchant_account: &domain::MerchantAccount,
+    key_store: &domain::MerchantKeyStore,
+) -> errors::RouterResult<api::CardNetworkTokenizeResponse> {
+    match req.data {
+        domain::TokenizeDataRequest::Card(ref card_req) => {
+            let executor = tokenize::CardNetworkTokenizeExecutor::new(
+                state,
+                key_store,
+                merchant_account,
+                card_req,
+                &req.customer,
+            );
+            let builder =
+                tokenize::NetworkTokenizationBuilder::<tokenize::TokenizeWithCard>::default();
+            execute_card_tokenization(executor, builder, card_req).await
+        }
+        domain::TokenizeDataRequest::ExistingPaymentMethod(ref payment_method) => {
+            let executor = tokenize::CardNetworkTokenizeExecutor::new(
+                state,
+                key_store,
+                merchant_account,
+                payment_method,
+                &req.customer,
+            );
+            let builder =
+                tokenize::NetworkTokenizationBuilder::<tokenize::TokenizeWithPmId>::default();
+            execute_payment_method_tokenization(executor, builder, payment_method).await
+        }
+    }
+}
+
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+pub async fn execute_card_tokenization(
+    executor: tokenize::CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest>,
+    builder: tokenize::NetworkTokenizationBuilder<'_, tokenize::TokenizeWithCard>,
+    req: &domain::TokenizeCardRequest,
+) -> errors::RouterResult<api::CardNetworkTokenizeResponse> {
+    // Validate request and get optional customer
+    let optional_customer = executor
+        .validate_request_and_fetch_optional_customer()
+        .await?;
+    let builder = builder.set_validate_result();
+
+    // Perform BIN lookup and validate card network
+    let optional_card_info = executor
+        .fetch_bin_details_and_validate_card_network(
+            req.raw_card_number.clone(),
+            req.card_issuer.as_ref(),
+            req.card_network.as_ref(),
+            req.card_type.as_ref(),
+            req.card_issuing_country.as_ref(),
+        )
+        .await?;
+    let builder = builder.set_card_details(req, optional_card_info);
+
+    // Create customer if not present
+    let customer = match optional_customer {
+        Some(customer) => customer,
+        None => executor.create_customer().await?,
+    };
+    let builder = builder.set_customer(&customer);
+
+    // Tokenize card
+    let (optional_card, optional_cvc) = builder.get_optional_card_and_cvc();
+    let domain_card = optional_card
+        .get_required_value("card")
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
+    let network_token_details = executor
+        .tokenize_card(&customer.id, &domain_card, optional_cvc)
+        .await?;
+    let builder = builder.set_token_details(&network_token_details);
+
+    // Store card and token in locker
+    let store_card_and_token_resp = executor
+        .store_card_and_token_in_locker(&network_token_details, &domain_card, &customer.id)
+        .await?;
+    let builder = builder.set_stored_card_response(&store_card_and_token_resp);
+    let builder = builder.set_stored_token_response(&store_card_and_token_resp);
+
+    // Create payment method
+    let payment_method = executor
+        .create_payment_method(
+            &store_card_and_token_resp,
+            &network_token_details,
+            &domain_card,
+            &customer.id,
+        )
+        .await?;
+    let builder = builder.set_payment_method_response(&payment_method);
+
+    Ok(builder.build())
+}
+
+#[cfg(all(
+    any(feature = "v1", feature = "v2"),
+    not(feature = "payment_methods_v2")
+))]
+pub async fn execute_payment_method_tokenization(
+    executor: tokenize::CardNetworkTokenizeExecutor<'_, domain::TokenizePaymentMethodRequest>,
+    builder: tokenize::NetworkTokenizationBuilder<'_, tokenize::TokenizeWithPmId>,
+    req: &domain::TokenizePaymentMethodRequest,
+) -> errors::RouterResult<api::CardNetworkTokenizeResponse> {
+    // Fetch payment method
+    let payment_method = executor
+        .fetch_payment_method(&req.payment_method_id)
+        .await?;
+    let builder = builder.set_payment_method(&payment_method);
+
+    // Validate payment method and customer
+    let (locker_id, customer) = executor
+        .validate_request_and_locker_reference_and_customer(&payment_method)
+        .await?;
+    let builder = builder.set_validate_result(&customer);
+
+    // Fetch card from locker
+    let card_details = get_card_from_locker(
+        executor.state,
+        &customer.id,
+        executor.merchant_account.get_id(),
+        &locker_id,
+    )
+    .await?;
+
+    // Perform BIN lookup and validate card network
+    let optional_card_info = executor
+        .fetch_bin_details_and_validate_card_network(
+            card_details.card_number.clone(),
+            None,
+            None,
+            None,
+            None,
+        )
+        .await?;
+    let builder = builder.set_card_details(&card_details, optional_card_info, req.card_cvc.clone());
+
+    // Tokenize card
+    let (optional_card, optional_cvc) = builder.get_optional_card_and_cvc();
+    let domain_card = optional_card.get_required_value("card")?;
+    let network_token_details = executor
+        .tokenize_card(&customer.id, &domain_card, optional_cvc)
+        .await?;
+    let builder = builder.set_token_details(&network_token_details);
+
+    // Store token in locker
+    let store_token_resp = executor
+        .store_network_token_in_locker(
+            &network_token_details,
+            &customer.id,
+            card_details.name_on_card.clone(),
+            card_details.nick_name.clone().map(Secret::new),
+        )
+        .await?;
+    let builder = builder.set_stored_token_response(&store_token_resp);
+
+    // Update payment method
+    let updated_payment_method = executor
+        .update_payment_method(
+            &store_token_resp,
+            payment_method,
+            &network_token_details,
+            &domain_card,
+        )
+        .await?;
+    let builder = builder.set_payment_method(&updated_payment_method);
+
+    Ok(builder.build())
+}
diff --git a/crates/router/src/core/payment_methods/network_tokenization.rs b/crates/router/src/core/payment_methods/network_tokenization.rs
index 6799a1cf5ba..41a94402baf 100644
--- a/crates/router/src/core/payment_methods/network_tokenization.rs
+++ b/crates/router/src/core/payment_methods/network_tokenization.rs
@@ -267,7 +267,8 @@ pub async fn generate_network_token(
 ))]
 pub async fn make_card_network_tokenization_request(
     state: &routes::SessionState,
-    card: &domain::Card,
+    card: &domain::CardDetail,
+    optional_cvc: Option<Secret<String>>,
     customer_id: &id_type::CustomerId,
 ) -> CustomResult<
     (domain::CardNetworkTokenResponsePayload, Option<String>),
@@ -277,7 +278,7 @@ pub async fn make_card_network_tokenization_request(
         card_number: card.card_number.clone(),
         exp_month: card.card_exp_month.clone(),
         exp_year: card.card_exp_year.clone(),
-        card_security_code: card.card_cvc.clone(),
+        card_security_code: optional_cvc,
     };
 
     let payload = card_data
diff --git a/crates/router/src/core/payment_methods/tokenize.rs b/crates/router/src/core/payment_methods/tokenize.rs
new file mode 100644
index 00000000000..853d3c0a2bd
--- /dev/null
+++ b/crates/router/src/core/payment_methods/tokenize.rs
@@ -0,0 +1,428 @@
+use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm};
+use api_models::{enums as api_enums, payment_methods as payment_methods_api};
+use cards::CardNumber;
+use common_utils::{
+    crypto::Encryptable,
+    id_type,
+    transformers::{ForeignFrom, ForeignTryFrom},
+};
+use error_stack::{report, ResultExt};
+use hyperswitch_domain_models::{
+    network_tokenization as nt_domain_types, router_request_types as domain_request_types,
+};
+use masking::{ExposeInterface, Secret};
+use router_env::logger;
+
+use super::migration;
+use crate::{
+    core::payment_methods::{
+        cards::{add_card_to_hs_locker, create_encrypted_data, tokenize_card_flow},
+        network_tokenization, transformers as pm_transformers,
+    },
+    errors::{self, RouterResult},
+    services,
+    types::{api, domain},
+    SessionState,
+};
+
+pub mod card_executor;
+pub mod payment_method_executor;
+
+pub use card_executor::*;
+pub use payment_method_executor::*;
+use rdkafka::message::ToBytes;
+
+#[derive(Debug, MultipartForm)]
+pub struct CardNetworkTokenizeForm {
+    #[multipart(limit = "1MB")]
+    pub file: Bytes,
+    pub merchant_id: Text<id_type::MerchantId>,
+}
+
+pub fn parse_csv(
+    merchant_id: &id_type::MerchantId,
+    data: &[u8],
+) -> csv::Result<Vec<payment_methods_api::CardNetworkTokenizeRequest>> {
+    let mut csv_reader = csv::ReaderBuilder::new()
+        .has_headers(true)
+        .from_reader(data);
+    let mut records = Vec::new();
+    let mut id_counter = 0;
+    for (i, result) in csv_reader
+        .deserialize::<domain::CardNetworkTokenizeRecord>()
+        .enumerate()
+    {
+        match result {
+            Ok(mut record) => {
+                logger::info!("Parsed Record (line {}): {:?}", i + 1, record);
+                id_counter += 1;
+                record.line_number = Some(id_counter);
+                record.merchant_id = Some(merchant_id.clone());
+                match payment_methods_api::CardNetworkTokenizeRequest::foreign_try_from(record) {
+                    Ok(record) => {
+                        records.push(record);
+                    }
+                    Err(err) => {
+                        logger::error!("Error parsing line {}: {}", i + 1, err.to_string());
+                    }
+                }
+            }
+            Err(e) => logger::error!("Error parsing line {}: {}", i + 1, e),
+        }
+    }
+    Ok(records)
+}
+
+pub fn get_tokenize_card_form_records(
+    form: CardNetworkTokenizeForm,
+) -> Result<
+    (
+        id_type::MerchantId,
+        Vec<payment_methods_api::CardNetworkTokenizeRequest>,
+    ),
+    errors::ApiErrorResponse,
+> {
+    match parse_csv(&form.merchant_id, form.file.data.to_bytes()) {
+        Ok(records) => {
+            logger::info!("Parsed a total of {} records", records.len());
+            Ok((form.merchant_id.0, records))
+        }
+        Err(e) => {
+            logger::error!("Failed to parse CSV: {:?}", e);
+            Err(errors::ApiErrorResponse::PreconditionFailed {
+                message: e.to_string(),
+            })
+        }
+    }
+}
+
+pub async fn tokenize_cards(
+    state: &SessionState,
+    records: Vec<payment_methods_api::CardNetworkTokenizeRequest>,
+    merchant_account: &domain::MerchantAccount,
+    key_store: &domain::MerchantKeyStore,
+) -> errors::RouterResponse<Vec<payment_methods_api::CardNetworkTokenizeResponse>> {
+    use futures::stream::StreamExt;
+
+    // Process all records in parallel
+    let responses = futures::stream::iter(records.into_iter())
+        .map(|record| async move {
+            let tokenize_request = record.data.clone();
+            let customer = record.customer.clone();
+            Box::pin(tokenize_card_flow(
+                state,
+                domain::CardNetworkTokenizeRequest::foreign_from(record),
+                merchant_account,
+                key_store,
+            ))
+            .await
+            .unwrap_or_else(|e| {
+                let err = e.current_context();
+                payment_methods_api::CardNetworkTokenizeResponse {
+                    tokenization_data: Some(tokenize_request),
+                    error_code: Some(err.error_code()),
+                    error_message: Some(err.error_message()),
+                    card_tokenized: false,
+                    payment_method_response: None,
+                    customer: Some(customer),
+                }
+            })
+        })
+        .buffer_unordered(10)
+        .collect()
+        .await;
+
+    // Return the final response
+    Ok(services::ApplicationResponse::Json(responses))
+}
+
+// Data types
+type NetworkTokenizationResponse = (
+    nt_domain_types::CardNetworkTokenResponsePayload,
+    Option<String>,
+);
+
+pub struct StoreLockerResponse {
+    pub store_card_resp: pm_transformers::StoreCardRespPayload,
+    pub store_token_resp: pm_transformers::StoreCardRespPayload,
+}
+
+// Builder
+pub struct NetworkTokenizationBuilder<'a, S: State> {
+    /// Current state
+    state: std::marker::PhantomData<S>,
+
+    /// Customer details
+    pub customer: Option<&'a api::CustomerDetails>,
+
+    /// Card details
+    pub card: Option<domain::CardDetail>,
+
+    /// CVC
+    pub card_cvc: Option<Secret<String>>,
+
+    /// Network token details
+    pub network_token: Option<&'a nt_domain_types::CardNetworkTokenResponsePayload>,
+
+    /// Stored card details
+    pub stored_card: Option<&'a pm_transformers::StoreCardRespPayload>,
+
+    /// Stored token details
+    pub stored_token: Option<&'a pm_transformers::StoreCardRespPayload>,
+
+    /// Payment method response
+    pub payment_method_response: Option<api::PaymentMethodResponse>,
+
+    /// Card network tokenization status
+    pub card_tokenized: bool,
+
+    /// Error code
+    pub error_code: Option<&'a String>,
+
+    /// Error message
+    pub error_message: Option<&'a String>,
+}
+
+// Async executor
+pub struct CardNetworkTokenizeExecutor<'a, D> {
+    pub state: &'a SessionState,
+    pub merchant_account: &'a domain::MerchantAccount,
+    key_store: &'a domain::MerchantKeyStore,
+    data: &'a D,
+    customer: &'a domain_request_types::CustomerDetails,
+}
+
+// State machine
+pub trait State {}
+pub trait TransitionTo<S: State> {}
+
+// Trait for network tokenization
+#[async_trait::async_trait]
+pub trait NetworkTokenizationProcess<'a, D> {
+    fn new(
+        state: &'a SessionState,
+        key_store: &'a domain::MerchantKeyStore,
+        merchant_account: &'a domain::MerchantAccount,
+        data: &'a D,
+        customer: &'a domain_request_types::CustomerDetails,
+    ) -> Self;
+    async fn encrypt_card(
+        &self,
+        card_details: &domain::CardDetail,
+        saved_to_locker: bool,
+    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>>;
+    async fn encrypt_network_token(
+        &self,
+        network_token_details: &NetworkTokenizationResponse,
+        card_details: &domain::CardDetail,
+        saved_to_locker: bool,
+    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>>;
+    async fn fetch_bin_details_and_validate_card_network(
+        &self,
+        card_number: CardNumber,
+        card_issuer: Option<&String>,
+        card_network: Option<&api_enums::CardNetwork>,
+        card_type: Option<&api_models::payment_methods::CardType>,
+        card_issuing_country: Option<&String>,
+    ) -> RouterResult<Option<diesel_models::CardInfo>>;
+    fn validate_card_network(
+        &self,
+        optional_card_network: Option<&api_enums::CardNetwork>,
+    ) -> RouterResult<()>;
+    async fn tokenize_card(
+        &self,
+        customer_id: &id_type::CustomerId,
+        card: &domain::CardDetail,
+        optional_cvc: Option<Secret<String>>,
+    ) -> RouterResult<NetworkTokenizationResponse>;
+    async fn store_network_token_in_locker(
+        &self,
+        network_token: &NetworkTokenizationResponse,
+        customer_id: &id_type::CustomerId,
+        card_holder_name: Option<Secret<String>>,
+        nick_name: Option<Secret<String>>,
+    ) -> RouterResult<pm_transformers::StoreCardRespPayload>;
+}
+
+// Generic implementation
+#[async_trait::async_trait]
+impl<'a, D> NetworkTokenizationProcess<'a, D> for CardNetworkTokenizeExecutor<'a, D>
+where
+    D: Send + Sync + 'static,
+{
+    fn new(
+        state: &'a SessionState,
+        key_store: &'a domain::MerchantKeyStore,
+        merchant_account: &'a domain::MerchantAccount,
+        data: &'a D,
+        customer: &'a domain_request_types::CustomerDetails,
+    ) -> Self {
+        Self {
+            data,
+            customer,
+            state,
+            merchant_account,
+            key_store,
+        }
+    }
+    async fn encrypt_card(
+        &self,
+        card_details: &domain::CardDetail,
+        saved_to_locker: bool,
+    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>> {
+        let pm_data = api::PaymentMethodsData::Card(api::CardDetailsPaymentMethod {
+            last4_digits: Some(card_details.card_number.get_last4()),
+            expiry_month: Some(card_details.card_exp_month.clone()),
+            expiry_year: Some(card_details.card_exp_year.clone()),
+            card_isin: Some(card_details.card_number.get_card_isin()),
+            nick_name: card_details.nick_name.clone(),
+            card_holder_name: card_details.card_holder_name.clone(),
+            issuer_country: card_details.card_issuing_country.clone(),
+            card_issuer: card_details.card_issuer.clone(),
+            card_network: card_details.card_network.clone(),
+            card_type: card_details.card_type.clone(),
+            saved_to_locker,
+        });
+        create_encrypted_data(&self.state.into(), self.key_store, pm_data)
+            .await
+            .inspect_err(|err| logger::info!("Error encrypting payment method data: {:?}", err))
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+    }
+    async fn encrypt_network_token(
+        &self,
+        network_token_details: &NetworkTokenizationResponse,
+        card_details: &domain::CardDetail,
+        saved_to_locker: bool,
+    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>> {
+        let network_token = &network_token_details.0;
+        let token_data = api::PaymentMethodsData::Card(api::CardDetailsPaymentMethod {
+            last4_digits: Some(network_token.token_last_four.clone()),
+            expiry_month: Some(network_token.token_expiry_month.clone()),
+            expiry_year: Some(network_token.token_expiry_year.clone()),
+            card_isin: Some(network_token.token_isin.clone()),
+            nick_name: card_details.nick_name.clone(),
+            card_holder_name: card_details.card_holder_name.clone(),
+            issuer_country: card_details.card_issuing_country.clone(),
+            card_issuer: card_details.card_issuer.clone(),
+            card_network: card_details.card_network.clone(),
+            card_type: card_details.card_type.clone(),
+            saved_to_locker,
+        });
+        create_encrypted_data(&self.state.into(), self.key_store, token_data)
+            .await
+            .inspect_err(|err| logger::info!("Error encrypting network token data: {:?}", err))
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+    }
+    async fn fetch_bin_details_and_validate_card_network(
+        &self,
+        card_number: CardNumber,
+        card_issuer: Option<&String>,
+        card_network: Option<&api_enums::CardNetwork>,
+        card_type: Option<&api_models::payment_methods::CardType>,
+        card_issuing_country: Option<&String>,
+    ) -> RouterResult<Option<diesel_models::CardInfo>> {
+        let db = &*self.state.store;
+        if card_issuer.is_some()
+            && card_network.is_some()
+            && card_type.is_some()
+            && card_issuing_country.is_some()
+        {
+            self.validate_card_network(card_network)?;
+            return Ok(None);
+        }
+
+        db.get_card_info(&card_number.get_card_isin())
+            .await
+            .attach_printable("Failed to perform BIN lookup")
+            .change_context(errors::ApiErrorResponse::InternalServerError)?
+            .map(|card_info| {
+                self.validate_card_network(card_info.card_network.as_ref())?;
+                Ok(card_info)
+            })
+            .transpose()
+    }
+    async fn tokenize_card(
+        &self,
+        customer_id: &id_type::CustomerId,
+        card: &domain::CardDetail,
+        optional_cvc: Option<Secret<String>>,
+    ) -> RouterResult<NetworkTokenizationResponse> {
+        network_tokenization::make_card_network_tokenization_request(
+            self.state,
+            card,
+            optional_cvc,
+            customer_id,
+        )
+        .await
+        .map_err(|err| {
+            logger::error!("Failed to tokenize card with the network: {:?}", err);
+            report!(errors::ApiErrorResponse::InternalServerError)
+        })
+    }
+    fn validate_card_network(
+        &self,
+        optional_card_network: Option<&api_enums::CardNetwork>,
+    ) -> RouterResult<()> {
+        optional_card_network.map_or(
+            Err(report!(errors::ApiErrorResponse::NotSupported {
+                message: "Unknown card network".to_string()
+            })),
+            |card_network| {
+                if self
+                    .state
+                    .conf
+                    .network_tokenization_supported_card_networks
+                    .card_networks
+                    .contains(card_network)
+                {
+                    Ok(())
+                } else {
+                    Err(report!(errors::ApiErrorResponse::NotSupported {
+                        message: format!(
+                            "Network tokenization for {} is not supported",
+                            card_network
+                        )
+                    }))
+                }
+            },
+        )
+    }
+    async fn store_network_token_in_locker(
+        &self,
+        network_token: &NetworkTokenizationResponse,
+        customer_id: &id_type::CustomerId,
+        card_holder_name: Option<Secret<String>>,
+        nick_name: Option<Secret<String>>,
+    ) -> RouterResult<pm_transformers::StoreCardRespPayload> {
+        let network_token = &network_token.0;
+        let merchant_id = self.merchant_account.get_id();
+        let locker_req =
+            pm_transformers::StoreLockerReq::LockerCard(pm_transformers::StoreCardReq {
+                merchant_id: merchant_id.clone(),
+                merchant_customer_id: customer_id.clone(),
+                card: payment_methods_api::Card {
+                    card_number: network_token.token.clone(),
+                    card_exp_month: network_token.token_expiry_month.clone(),
+                    card_exp_year: network_token.token_expiry_year.clone(),
+                    card_brand: Some(network_token.card_brand.to_string()),
+                    card_isin: Some(network_token.token_isin.clone()),
+                    name_on_card: card_holder_name,
+                    nick_name: nick_name.map(|nick_name| nick_name.expose()),
+                },
+                requestor_card_reference: None,
+                ttl: self.state.conf.locker.ttl_for_storage_in_secs,
+            });
+
+        let stored_resp = add_card_to_hs_locker(
+            self.state,
+            &locker_req,
+            customer_id,
+            api_enums::LockerChoice::HyperswitchCardVault,
+        )
+        .await
+        .inspect_err(|err| logger::info!("Error adding card in locker: {:?}", err))
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
+
+        Ok(stored_resp)
+    }
+}
diff --git a/crates/router/src/core/payment_methods/tokenize/card_executor.rs b/crates/router/src/core/payment_methods/tokenize/card_executor.rs
new file mode 100644
index 00000000000..9b7f89c5199
--- /dev/null
+++ b/crates/router/src/core/payment_methods/tokenize/card_executor.rs
@@ -0,0 +1,589 @@
+use std::str::FromStr;
+
+use api_models::{enums as api_enums, payment_methods as payment_methods_api};
+use common_utils::{
+    consts,
+    ext_traits::OptionExt,
+    generate_customer_id_of_default_length, id_type,
+    pii::Email,
+    type_name,
+    types::keymanager::{Identifier, KeyManagerState, ToEncryptable},
+};
+use error_stack::{report, ResultExt};
+use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
+use masking::{ExposeInterface, PeekInterface, SwitchStrategy};
+use router_env::logger;
+
+use super::{
+    migration, CardNetworkTokenizeExecutor, NetworkTokenizationBuilder, NetworkTokenizationProcess,
+    NetworkTokenizationResponse, State, StoreLockerResponse, TransitionTo,
+};
+use crate::{
+    core::payment_methods::{
+        cards::{add_card_to_hs_locker, create_payment_method},
+        transformers as pm_transformers,
+    },
+    errors::{self, RouterResult},
+    types::{api, domain},
+    utils,
+};
+
+// Available states for card tokenization
+pub struct TokenizeWithCard;
+pub struct CardRequestValidated;
+pub struct CardDetailsAssigned;
+pub struct CustomerAssigned;
+pub struct CardTokenized;
+pub struct CardStored;
+pub struct CardTokenStored;
+pub struct PaymentMethodCreated;
+
+impl State for TokenizeWithCard {}
+impl State for CustomerAssigned {}
+impl State for CardRequestValidated {}
+impl State for CardDetailsAssigned {}
+impl State for CardTokenized {}
+impl State for CardStored {}
+impl State for CardTokenStored {}
+impl State for PaymentMethodCreated {}
+
+// State transitions for card tokenization
+impl TransitionTo<CardRequestValidated> for TokenizeWithCard {}
+impl TransitionTo<CardDetailsAssigned> for CardRequestValidated {}
+impl TransitionTo<CustomerAssigned> for CardDetailsAssigned {}
+impl TransitionTo<CardTokenized> for CustomerAssigned {}
+impl TransitionTo<CardTokenStored> for CardTokenized {}
+impl TransitionTo<PaymentMethodCreated> for CardTokenStored {}
+
+impl Default for NetworkTokenizationBuilder<'_, TokenizeWithCard> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, TokenizeWithCard> {
+    pub fn new() -> Self {
+        Self {
+            state: std::marker::PhantomData,
+            customer: None,
+            card: None,
+            card_cvc: None,
+            network_token: None,
+            stored_card: None,
+            stored_token: None,
+            payment_method_response: None,
+            card_tokenized: false,
+            error_code: None,
+            error_message: None,
+        }
+    }
+    pub fn set_validate_result(self) -> NetworkTokenizationBuilder<'a, CardRequestValidated> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CardRequestValidated> {
+    pub fn set_card_details(
+        self,
+        card_req: &'a domain::TokenizeCardRequest,
+        optional_card_info: Option<diesel_models::CardInfo>,
+    ) -> NetworkTokenizationBuilder<'a, CardDetailsAssigned> {
+        let card = domain::CardDetail {
+            card_number: card_req.raw_card_number.clone(),
+            card_exp_month: card_req.card_expiry_month.clone(),
+            card_exp_year: card_req.card_expiry_year.clone(),
+            bank_code: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.bank_code.clone()),
+            nick_name: card_req.nick_name.clone(),
+            card_holder_name: card_req.card_holder_name.clone(),
+            card_issuer: optional_card_info
+                .as_ref()
+                .map_or(card_req.card_issuer.clone(), |card_info| {
+                    card_info.card_issuer.clone()
+                }),
+            card_network: optional_card_info
+                .as_ref()
+                .map_or(card_req.card_network.clone(), |card_info| {
+                    card_info.card_network.clone()
+                }),
+            card_type: optional_card_info.as_ref().map_or(
+                card_req
+                    .card_type
+                    .as_ref()
+                    .map(|card_type| card_type.to_string()),
+                |card_info| card_info.card_type.clone(),
+            ),
+            card_issuing_country: optional_card_info
+                .as_ref()
+                .map_or(card_req.card_issuing_country.clone(), |card_info| {
+                    card_info.card_issuing_country.clone()
+                }),
+        };
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            card: Some(card),
+            card_cvc: card_req.card_cvc.clone(),
+            customer: self.customer,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CardDetailsAssigned> {
+    pub fn set_customer(
+        self,
+        customer: &'a api::CustomerDetails,
+    ) -> NetworkTokenizationBuilder<'a, CustomerAssigned> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            customer: Some(customer),
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CustomerAssigned> {
+    pub fn get_optional_card_and_cvc(
+        &self,
+    ) -> (Option<domain::CardDetail>, Option<masking::Secret<String>>) {
+        (self.card.clone(), self.card_cvc.clone())
+    }
+    pub fn set_token_details(
+        self,
+        network_token: &'a NetworkTokenizationResponse,
+    ) -> NetworkTokenizationBuilder<'a, CardTokenized> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            network_token: Some(&network_token.0),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CardTokenized> {
+    pub fn set_stored_card_response(
+        self,
+        store_card_response: &'a StoreLockerResponse,
+    ) -> NetworkTokenizationBuilder<'a, CardStored> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            stored_card: Some(&store_card_response.store_card_resp),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CardStored> {
+    pub fn set_stored_token_response(
+        self,
+        store_token_response: &'a StoreLockerResponse,
+    ) -> NetworkTokenizationBuilder<'a, CardTokenStored> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            card_tokenized: true,
+            stored_token: Some(&store_token_response.store_token_resp),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            payment_method_response: self.payment_method_response,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, CardTokenStored> {
+    pub fn set_payment_method_response(
+        self,
+        payment_method: &'a domain::PaymentMethod,
+    ) -> NetworkTokenizationBuilder<'a, PaymentMethodCreated> {
+        let card_detail_from_locker = self.card.as_ref().map(|card| api::CardDetailFromLocker {
+            scheme: None,
+            issuer_country: card.card_issuing_country.clone(),
+            last4_digits: Some(card.card_number.clone().get_last4()),
+            card_number: None,
+            expiry_month: Some(card.card_exp_month.clone().clone()),
+            expiry_year: Some(card.card_exp_year.clone().clone()),
+            card_token: None,
+            card_holder_name: card.card_holder_name.clone(),
+            card_fingerprint: None,
+            nick_name: card.nick_name.clone(),
+            card_network: card.card_network.clone(),
+            card_isin: Some(card.card_number.clone().get_card_isin()),
+            card_issuer: card.card_issuer.clone(),
+            card_type: card.card_type.clone(),
+            saved_to_locker: true,
+        });
+        let payment_method_response = api::PaymentMethodResponse {
+            merchant_id: payment_method.merchant_id.clone(),
+            customer_id: Some(payment_method.customer_id.clone()),
+            payment_method_id: payment_method.payment_method_id.clone(),
+            payment_method: payment_method.payment_method,
+            payment_method_type: payment_method.payment_method_type,
+            card: card_detail_from_locker,
+            recurring_enabled: true,
+            installment_payment_enabled: false,
+            metadata: payment_method.metadata.clone(),
+            created: Some(payment_method.created_at),
+            last_used_at: Some(payment_method.last_used_at),
+            client_secret: payment_method.client_secret.clone(),
+            bank_transfer: None,
+            payment_experience: None,
+        };
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            payment_method_response: Some(payment_method_response),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl NetworkTokenizationBuilder<'_, PaymentMethodCreated> {
+    pub fn build(self) -> api::CardNetworkTokenizeResponse {
+        api::CardNetworkTokenizeResponse {
+            payment_method_response: self.payment_method_response,
+            customer: self.customer.cloned(),
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code.cloned(),
+            error_message: self.error_message.cloned(),
+            // Below field is mutated by caller functions for batched API operations
+            tokenization_data: None,
+        }
+    }
+}
+
+// Specific executor for card tokenization
+impl CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest> {
+    pub async fn validate_request_and_fetch_optional_customer(
+        &self,
+    ) -> RouterResult<Option<api::CustomerDetails>> {
+        // Validate card's expiry
+        migration::validate_card_expiry(&self.data.card_expiry_month, &self.data.card_expiry_year)?;
+
+        // Validate customer ID
+        let customer_id = self
+            .customer
+            .customer_id
+            .as_ref()
+            .get_required_value("customer_id")
+            .change_context(errors::ApiErrorResponse::MissingRequiredField {
+                field_name: "customer.customer_id",
+            })?;
+
+        // Fetch customer details if present
+        let db = &*self.state.store;
+        let key_manager_state: &KeyManagerState = &self.state.into();
+        db.find_customer_optional_by_customer_id_merchant_id(
+            key_manager_state,
+            customer_id,
+            self.merchant_account.get_id(),
+            self.key_store,
+            self.merchant_account.storage_scheme,
+        )
+        .await
+        .inspect_err(|err| logger::info!("Error fetching customer: {:?}", err))
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .map_or(
+            // Validate if customer creation is feasible
+            if self.customer.name.is_some()
+                || self.customer.email.is_some()
+                || self.customer.phone.is_some()
+            {
+                Ok(None)
+            } else {
+                Err(report!(errors::ApiErrorResponse::MissingRequiredFields {
+                    field_names: vec!["customer.name", "customer.email", "customer.phone"],
+                }))
+            },
+            // If found, send back CustomerDetails from DB
+            |optional_customer| {
+                Ok(optional_customer.map(|customer| api::CustomerDetails {
+                    id: customer.customer_id.clone(),
+                    name: customer.name.clone().map(|name| name.into_inner()),
+                    email: customer.email.clone().map(Email::from),
+                    phone: customer.phone.clone().map(|phone| phone.into_inner()),
+                    phone_country_code: customer.phone_country_code.clone(),
+                }))
+            },
+        )
+    }
+
+    pub async fn create_customer(&self) -> RouterResult<api::CustomerDetails> {
+        let db = &*self.state.store;
+        let customer_id = self
+            .customer
+            .customer_id
+            .as_ref()
+            .get_required_value("customer_id")
+            .change_context(errors::ApiErrorResponse::MissingRequiredField {
+                field_name: "customer_id",
+            })?;
+        let key_manager_state: &KeyManagerState = &self.state.into();
+
+        let encrypted_data = crypto_operation(
+            key_manager_state,
+            type_name!(domain::Customer),
+            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableCustomer::to_encryptable(
+                domain::FromRequestEncryptableCustomer {
+                    name: self.customer.name.clone(),
+                    email: self
+                        .customer
+                        .email
+                        .clone()
+                        .map(|email| email.expose().switch_strategy()),
+                    phone: self.customer.phone.clone(),
+                },
+            )),
+            Identifier::Merchant(self.merchant_account.get_id().clone()),
+            self.key_store.key.get_inner().peek(),
+        )
+        .await
+        .inspect_err(|err| logger::info!("Error encrypting customer: {:?}", err))
+        .and_then(|val| val.try_into_batchoperation())
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to encrypt customer")?;
+
+        let encryptable_customer =
+            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to form EncryptableCustomer")?;
+
+        let new_customer_id = generate_customer_id_of_default_length();
+        let domain_customer = domain::Customer {
+            customer_id: new_customer_id.clone(),
+            merchant_id: self.merchant_account.get_id().clone(),
+            name: encryptable_customer.name,
+            email: encryptable_customer.email.map(|email| {
+                utils::Encryptable::new(
+                    email.clone().into_inner().switch_strategy(),
+                    email.into_encrypted(),
+                )
+            }),
+            phone: encryptable_customer.phone,
+            description: None,
+            phone_country_code: self.customer.phone_country_code.to_owned(),
+            metadata: None,
+            connector_customer: None,
+            created_at: common_utils::date_time::now(),
+            modified_at: common_utils::date_time::now(),
+            address_id: None,
+            default_payment_method_id: None,
+            updated_by: None,
+            version: hyperswitch_domain_models::consts::API_VERSION,
+        };
+
+        db.insert_customer(
+            domain_customer,
+            key_manager_state,
+            self.key_store,
+            self.merchant_account.storage_scheme,
+        )
+        .await
+        .inspect_err(|err| logger::info!("Error creating a customer: {:?}", err))
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable_lazy(|| {
+            format!(
+                "Failed to insert customer [id - {:?}] for merchant [id - {:?}]",
+                customer_id,
+                self.merchant_account.get_id()
+            )
+        })?;
+
+        Ok(api::CustomerDetails {
+            id: new_customer_id,
+            name: self.customer.name.clone(),
+            email: self.customer.email.clone(),
+            phone: self.customer.phone.clone(),
+            phone_country_code: self.customer.phone_country_code.clone(),
+        })
+    }
+
+    pub async fn store_card_and_token_in_locker(
+        &self,
+        network_token: &NetworkTokenizationResponse,
+        card: &domain::CardDetail,
+        customer_id: &id_type::CustomerId,
+    ) -> RouterResult<StoreLockerResponse> {
+        let stored_card_resp = self.store_card_in_locker(card, customer_id).await?;
+        let stored_token_resp = self
+            .store_network_token_in_locker(
+                network_token,
+                customer_id,
+                card.card_holder_name.clone(),
+                card.nick_name.clone(),
+            )
+            .await?;
+        let store_locker_response = StoreLockerResponse {
+            store_card_resp: stored_card_resp,
+            store_token_resp: stored_token_resp,
+        };
+        Ok(store_locker_response)
+    }
+
+    pub async fn store_card_in_locker(
+        &self,
+        card: &domain::CardDetail,
+        customer_id: &id_type::CustomerId,
+    ) -> RouterResult<pm_transformers::StoreCardRespPayload> {
+        let merchant_id = self.merchant_account.get_id();
+        let locker_req =
+            pm_transformers::StoreLockerReq::LockerCard(pm_transformers::StoreCardReq {
+                merchant_id: merchant_id.clone(),
+                merchant_customer_id: customer_id.clone(),
+                card: payment_methods_api::Card {
+                    card_number: card.card_number.clone(),
+                    card_exp_month: card.card_exp_month.clone(),
+                    card_exp_year: card.card_exp_year.clone(),
+                    card_isin: Some(card.card_number.get_card_isin().clone()),
+                    name_on_card: card.card_holder_name.clone(),
+                    nick_name: card
+                        .nick_name
+                        .as_ref()
+                        .map(|nick_name| nick_name.clone().expose()),
+                    card_brand: None,
+                },
+                requestor_card_reference: None,
+                ttl: self.state.conf.locker.ttl_for_storage_in_secs,
+            });
+
+        let stored_resp = add_card_to_hs_locker(
+            self.state,
+            &locker_req,
+            customer_id,
+            api_enums::LockerChoice::HyperswitchCardVault,
+        )
+        .await
+        .inspect_err(|err| logger::info!("Error adding card in locker: {:?}", err))
+        .change_context(errors::ApiErrorResponse::InternalServerError)?;
+
+        Ok(stored_resp)
+    }
+
+    pub async fn create_payment_method(
+        &self,
+        stored_locker_resp: &StoreLockerResponse,
+        network_token_details: &NetworkTokenizationResponse,
+        card_details: &domain::CardDetail,
+        customer_id: &id_type::CustomerId,
+    ) -> RouterResult<domain::PaymentMethod> {
+        let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm");
+
+        // Form encrypted PM data (original card)
+        let enc_pm_data = self.encrypt_card(card_details, true).await?;
+
+        // Form encrypted network token data
+        let enc_token_data = self
+            .encrypt_network_token(network_token_details, card_details, true)
+            .await?;
+
+        // Form PM create entry
+        let payment_method_create = api::PaymentMethodCreate {
+            payment_method: Some(api_enums::PaymentMethod::Card),
+            payment_method_type: card_details
+                .card_type
+                .as_ref()
+                .and_then(|card_type| api_enums::PaymentMethodType::from_str(card_type).ok()),
+            payment_method_issuer: card_details.card_issuer.clone(),
+            payment_method_issuer_code: None,
+            card: Some(api::CardDetail {
+                card_number: card_details.card_number.clone(),
+                card_exp_month: card_details.card_exp_month.clone(),
+                card_exp_year: card_details.card_exp_year.clone(),
+                card_holder_name: card_details.card_holder_name.clone(),
+                nick_name: card_details.nick_name.clone(),
+                card_issuing_country: card_details.card_issuing_country.clone(),
+                card_network: card_details.card_network.clone(),
+                card_issuer: card_details.card_issuer.clone(),
+                card_type: card_details.card_type.clone(),
+            }),
+            metadata: None,
+            customer_id: Some(customer_id.clone()),
+            card_network: card_details
+                .card_network
+                .as_ref()
+                .map(|network| network.to_string()),
+            bank_transfer: None,
+            wallet: None,
+            client_secret: None,
+            payment_method_data: None,
+            billing: None,
+            connector_mandate_details: None,
+            network_transaction_id: None,
+        };
+        create_payment_method(
+            self.state,
+            &payment_method_create,
+            customer_id,
+            &payment_method_id,
+            Some(stored_locker_resp.store_card_resp.card_reference.clone()),
+            self.merchant_account.get_id(),
+            None,
+            None,
+            Some(enc_pm_data),
+            self.key_store,
+            None,
+            None,
+            None,
+            self.merchant_account.storage_scheme,
+            None,
+            None,
+            network_token_details.1.clone(),
+            Some(stored_locker_resp.store_token_resp.card_reference.clone()),
+            Some(enc_token_data),
+        )
+        .await
+    }
+}
diff --git a/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs b/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs
new file mode 100644
index 00000000000..967dc77f7fd
--- /dev/null
+++ b/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs
@@ -0,0 +1,411 @@
+use api_models::enums as api_enums;
+use common_utils::{
+    ext_traits::OptionExt, fp_utils::when, pii::Email, types::keymanager::KeyManagerState,
+};
+use error_stack::{report, ResultExt};
+use masking::Secret;
+use router_env::logger;
+
+use super::{
+    CardNetworkTokenizeExecutor, NetworkTokenizationBuilder, NetworkTokenizationProcess,
+    NetworkTokenizationResponse, State, TransitionTo,
+};
+use crate::{
+    core::payment_methods::transformers as pm_transformers,
+    errors::{self, RouterResult},
+    types::{api, domain},
+};
+
+// Available states for payment method tokenization
+pub struct TokenizeWithPmId;
+pub struct PmValidated;
+pub struct PmFetched;
+pub struct PmAssigned;
+pub struct PmTokenized;
+pub struct PmTokenStored;
+pub struct PmTokenUpdated;
+
+impl State for TokenizeWithPmId {}
+impl State for PmValidated {}
+impl State for PmFetched {}
+impl State for PmAssigned {}
+impl State for PmTokenized {}
+impl State for PmTokenStored {}
+impl State for PmTokenUpdated {}
+
+// State transitions for payment method tokenization
+impl TransitionTo<PmFetched> for TokenizeWithPmId {}
+impl TransitionTo<PmValidated> for PmFetched {}
+impl TransitionTo<PmAssigned> for PmValidated {}
+impl TransitionTo<PmTokenized> for PmAssigned {}
+impl TransitionTo<PmTokenStored> for PmTokenized {}
+impl TransitionTo<PmTokenUpdated> for PmTokenStored {}
+
+impl Default for NetworkTokenizationBuilder<'_, TokenizeWithPmId> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, TokenizeWithPmId> {
+    pub fn new() -> Self {
+        Self {
+            state: std::marker::PhantomData,
+            customer: None,
+            card: None,
+            card_cvc: None,
+            network_token: None,
+            stored_card: None,
+            stored_token: None,
+            payment_method_response: None,
+            card_tokenized: false,
+            error_code: None,
+            error_message: None,
+        }
+    }
+    pub fn set_payment_method(
+        self,
+        payment_method: &domain::PaymentMethod,
+    ) -> NetworkTokenizationBuilder<'a, PmFetched> {
+        let payment_method_response = api::PaymentMethodResponse {
+            merchant_id: payment_method.merchant_id.clone(),
+            customer_id: Some(payment_method.customer_id.clone()),
+            payment_method_id: payment_method.payment_method_id.clone(),
+            payment_method: payment_method.payment_method,
+            payment_method_type: payment_method.payment_method_type,
+            recurring_enabled: true,
+            installment_payment_enabled: false,
+            metadata: payment_method.metadata.clone(),
+            created: Some(payment_method.created_at),
+            last_used_at: Some(payment_method.last_used_at),
+            client_secret: payment_method.client_secret.clone(),
+            card: None,
+            bank_transfer: None,
+            payment_experience: None,
+        };
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            payment_method_response: Some(payment_method_response),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, PmFetched> {
+    pub fn set_validate_result(
+        self,
+        customer: &'a api::CustomerDetails,
+    ) -> NetworkTokenizationBuilder<'a, PmValidated> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            customer: Some(customer),
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, PmValidated> {
+    pub fn set_card_details(
+        self,
+        card_from_locker: &'a api_models::payment_methods::Card,
+        optional_card_info: Option<diesel_models::CardInfo>,
+        card_cvc: Option<Secret<String>>,
+    ) -> NetworkTokenizationBuilder<'a, PmAssigned> {
+        let card = domain::CardDetail {
+            card_number: card_from_locker.card_number.clone(),
+            card_exp_month: card_from_locker.card_exp_month.clone(),
+            card_exp_year: card_from_locker.card_exp_year.clone(),
+            bank_code: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.bank_code.clone()),
+            nick_name: card_from_locker
+                .nick_name
+                .as_ref()
+                .map(|nick_name| Secret::new(nick_name.clone())),
+            card_holder_name: card_from_locker.name_on_card.clone(),
+            card_issuer: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.card_issuer.clone()),
+            card_network: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.card_network.clone()),
+            card_type: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.card_type.clone()),
+            card_issuing_country: optional_card_info
+                .as_ref()
+                .and_then(|card_info| card_info.card_issuing_country.clone()),
+        };
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            card: Some(card),
+            card_cvc,
+            customer: self.customer,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, PmAssigned> {
+    pub fn get_optional_card_and_cvc(
+        &self,
+    ) -> (Option<domain::CardDetail>, Option<Secret<String>>) {
+        (self.card.clone(), self.card_cvc.clone())
+    }
+    pub fn set_token_details(
+        self,
+        network_token: &'a NetworkTokenizationResponse,
+    ) -> NetworkTokenizationBuilder<'a, PmTokenized> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            network_token: Some(&network_token.0),
+            card_tokenized: true,
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            stored_card: self.stored_card,
+            stored_token: self.stored_token,
+            payment_method_response: self.payment_method_response,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, PmTokenized> {
+    pub fn set_stored_token_response(
+        self,
+        store_token_response: &'a pm_transformers::StoreCardRespPayload,
+    ) -> NetworkTokenizationBuilder<'a, PmTokenStored> {
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            stored_token: Some(store_token_response),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            payment_method_response: self.payment_method_response,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl<'a> NetworkTokenizationBuilder<'a, PmTokenStored> {
+    pub fn set_payment_method(
+        self,
+        payment_method: &'a domain::PaymentMethod,
+    ) -> NetworkTokenizationBuilder<'a, PmTokenUpdated> {
+        let payment_method_response = api::PaymentMethodResponse {
+            merchant_id: payment_method.merchant_id.clone(),
+            customer_id: Some(payment_method.customer_id.clone()),
+            payment_method_id: payment_method.payment_method_id.clone(),
+            payment_method: payment_method.payment_method,
+            payment_method_type: payment_method.payment_method_type,
+            recurring_enabled: true,
+            installment_payment_enabled: false,
+            metadata: payment_method.metadata.clone(),
+            created: Some(payment_method.created_at),
+            last_used_at: Some(payment_method.last_used_at),
+            client_secret: payment_method.client_secret.clone(),
+            card: None,
+            bank_transfer: None,
+            payment_experience: None,
+        };
+        NetworkTokenizationBuilder {
+            state: std::marker::PhantomData,
+            payment_method_response: Some(payment_method_response),
+            customer: self.customer,
+            card: self.card,
+            card_cvc: self.card_cvc,
+            stored_token: self.stored_token,
+            network_token: self.network_token,
+            stored_card: self.stored_card,
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code,
+            error_message: self.error_message,
+        }
+    }
+}
+
+impl NetworkTokenizationBuilder<'_, PmTokenUpdated> {
+    pub fn build(self) -> api::CardNetworkTokenizeResponse {
+        api::CardNetworkTokenizeResponse {
+            payment_method_response: self.payment_method_response,
+            customer: self.customer.cloned(),
+            card_tokenized: self.card_tokenized,
+            error_code: self.error_code.cloned(),
+            error_message: self.error_message.cloned(),
+            // Below field is mutated by caller functions for batched API operations
+            tokenization_data: None,
+        }
+    }
+}
+
+// Specific executor for payment method tokenization
+impl CardNetworkTokenizeExecutor<'_, domain::TokenizePaymentMethodRequest> {
+    pub async fn fetch_payment_method(
+        &self,
+        payment_method_id: &str,
+    ) -> RouterResult<domain::PaymentMethod> {
+        self.state
+            .store
+            .find_payment_method(
+                &self.state.into(),
+                self.key_store,
+                payment_method_id,
+                self.merchant_account.storage_scheme,
+            )
+            .await
+            .map_err(|err| match err.current_context() {
+                storage_impl::errors::StorageError::DatabaseError(err)
+                    if matches!(
+                        err.current_context(),
+                        diesel_models::errors::DatabaseError::NotFound
+                    ) =>
+                {
+                    report!(errors::ApiErrorResponse::InvalidRequestData {
+                        message: "Invalid payment_method_id".into(),
+                    })
+                }
+                storage_impl::errors::StorageError::ValueNotFound(_) => {
+                    report!(errors::ApiErrorResponse::InvalidRequestData {
+                        message: "Invalid payment_method_id".to_string(),
+                    })
+                }
+                err => {
+                    logger::info!("Error fetching payment_method: {:?}", err);
+                    report!(errors::ApiErrorResponse::InternalServerError)
+                }
+            })
+    }
+    pub async fn validate_request_and_locker_reference_and_customer(
+        &self,
+        payment_method: &domain::PaymentMethod,
+    ) -> RouterResult<(String, api::CustomerDetails)> {
+        // Ensure customer ID matches
+        let customer_id_in_req = self
+            .customer
+            .customer_id
+            .clone()
+            .get_required_value("customer_id")
+            .change_context(errors::ApiErrorResponse::MissingRequiredField {
+                field_name: "customer",
+            })?;
+        when(payment_method.customer_id != customer_id_in_req, || {
+            Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                message: "Payment method does not belong to the customer".to_string()
+            }))
+        })?;
+
+        // Ensure payment method is card
+        match payment_method.payment_method {
+            Some(api_enums::PaymentMethod::Card) => Ok(()),
+            Some(_) => Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                message: "Payment method is not card".to_string()
+            })),
+            None => Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                message: "Payment method is empty".to_string()
+            })),
+        }?;
+
+        // Ensure card is not tokenized already
+        when(
+            payment_method
+                .network_token_requestor_reference_id
+                .is_some(),
+            || {
+                Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                    message: "Card is already tokenized".to_string()
+                }))
+            },
+        )?;
+
+        // Ensure locker reference is present
+        let locker_id = payment_method.locker_id.clone().ok_or(report!(
+            errors::ApiErrorResponse::InvalidRequestData {
+                message: "locker_id not found for given payment_method_id".to_string()
+            }
+        ))?;
+
+        // Fetch customer
+        let db = &*self.state.store;
+        let key_manager_state: &KeyManagerState = &self.state.into();
+        let customer = db
+            .find_customer_by_customer_id_merchant_id(
+                key_manager_state,
+                &payment_method.customer_id,
+                self.merchant_account.get_id(),
+                self.key_store,
+                self.merchant_account.storage_scheme,
+            )
+            .await
+            .inspect_err(|err| logger::info!("Error fetching customer: {:?}", err))
+            .change_context(errors::ApiErrorResponse::InternalServerError)?;
+
+        let customer_details = api::CustomerDetails {
+            id: customer.customer_id.clone(),
+            name: customer.name.clone().map(|name| name.into_inner()),
+            email: customer.email.clone().map(Email::from),
+            phone: customer.phone.clone().map(|phone| phone.into_inner()),
+            phone_country_code: customer.phone_country_code.clone(),
+        };
+
+        Ok((locker_id, customer_details))
+    }
+    pub async fn update_payment_method(
+        &self,
+        store_token_response: &pm_transformers::StoreCardRespPayload,
+        payment_method: domain::PaymentMethod,
+        network_token_details: &NetworkTokenizationResponse,
+        card_details: &domain::CardDetail,
+    ) -> RouterResult<domain::PaymentMethod> {
+        // Form encrypted network token data
+        let enc_token_data = self
+            .encrypt_network_token(network_token_details, card_details, true)
+            .await?;
+
+        // Update payment method
+        let payment_method_update = diesel_models::PaymentMethodUpdate::NetworkTokenDataUpdate {
+            network_token_requestor_reference_id: network_token_details.1.clone(),
+            network_token_locker_id: Some(store_token_response.card_reference.clone()),
+            network_token_payment_method_data: Some(enc_token_data.into()),
+        };
+        self.state
+            .store
+            .update_payment_method(
+                &self.state.into(),
+                self.key_store,
+                payment_method,
+                payment_method_update,
+                self.merchant_account.storage_scheme,
+            )
+            .await
+            .inspect_err(|err| logger::info!("Error updating payment method: {:?}", err))
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+    }
+}
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 3baf72901e4..0bb232f85a0 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -1030,9 +1030,11 @@ pub async fn save_network_token_in_locker(
         .filter(|cn| network_tokenization_supported_card_networks.contains(cn))
         .is_some()
     {
+        let optional_card_cvc = Some(card_data.card_cvc.clone());
         match network_tokenization::make_card_network_tokenization_request(
             state,
-            card_data,
+            &domain::CardDetail::from(card_data),
+            optional_card_cvc,
             &customer_id,
         )
         .await
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index df4b9977b9c..cc143163674 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1254,6 +1254,14 @@ impl PaymentMethods {
                     web::resource("/migrate-batch")
                         .route(web::post().to(payment_methods::migrate_payment_methods)),
                 )
+                .service(
+                    web::resource("/tokenize-card")
+                        .route(web::post().to(payment_methods::tokenize_card_api)),
+                )
+                .service(
+                    web::resource("/tokenize-card-batch")
+                        .route(web::post().to(payment_methods::tokenize_card_batch_api)),
+                )
                 .service(
                     web::resource("/collect")
                         .route(web::post().to(payment_methods::initiate_pm_collect_link_flow)),
@@ -1267,6 +1275,10 @@ impl PaymentMethods {
                         .route(web::get().to(payment_methods::payment_method_retrieve_api))
                         .route(web::delete().to(payment_methods::payment_method_delete_api)),
                 )
+                .service(
+                    web::resource("/{payment_method_id}/tokenize-card")
+                        .route(web::post().to(payment_methods::tokenize_card_using_pm_api)),
+                )
                 .service(
                     web::resource("/{payment_method_id}/update")
                         .route(web::post().to(payment_methods::payment_method_update_api)),
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index fd490c6fe4e..4c814f6a67b 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -39,6 +39,7 @@ pub enum ApiIdentifier {
     ApplePayCertificatesMigration,
     Relay,
     Documentation,
+    CardNetworkTokenization,
     Hypersense,
     PaymentMethodSession,
 }
@@ -313,6 +314,10 @@ impl From<Flow> for ApiIdentifier {
 
             Flow::FeatureMatrix => Self::Documentation,
 
+            Flow::TokenizeCard
+            | Flow::TokenizeCardUsingPaymentMethodId
+            | Flow::TokenizeCardBatch => Self::CardNetworkTokenization,
+
             Flow::HypersenseTokenRequest
             | Flow::HypersenseVerifyToken
             | Flow::HypersenseSignoutToken => Self::Hypersense,
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index 8d17a416645..a32a0268714 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -1,13 +1,15 @@
 #[cfg(all(
     any(feature = "v1", feature = "v2", feature = "olap", feature = "oltp"),
-    not(feature = "customer_v2")
+    all(not(feature = "customer_v2"), not(feature = "payment_methods_v2"))
 ))]
 use actix_multipart::form::MultipartForm;
 use actix_web::{web, HttpRequest, HttpResponse};
-use common_utils::{errors::CustomResult, id_type};
+use common_utils::{errors::CustomResult, id_type, transformers::ForeignFrom};
 use diesel_models::enums::IntentStatus;
 use error_stack::ResultExt;
-use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore;
+use hyperswitch_domain_models::{
+    bulk_tokenization::CardNetworkTokenizeRequest, merchant_key_store::MerchantKeyStore,
+};
 use router_env::{instrument, logger, tracing, Flow};
 
 use super::app::{AppState, SessionState};
@@ -17,7 +19,7 @@ use crate::{
         errors::{self, utils::StorageErrorExt},
         payment_methods::{self as payment_methods_routes, cards},
     },
-    services::{api, authentication as auth, authorization::permissions::Permission},
+    services::{self, api, authentication as auth, authorization::permissions::Permission},
     types::{
         api::payment_methods::{self, PaymentMethodId},
         domain,
@@ -29,7 +31,10 @@ use crate::{
     not(feature = "customer_v2")
 ))]
 use crate::{
-    core::{customers, payment_methods::migration},
+    core::{
+        customers,
+        payment_methods::{migration, tokenize},
+    },
     types::api::customers::CustomerRequest,
 };
 
@@ -927,6 +932,129 @@ impl ParentPaymentMethodToken {
     }
 }
 
+#[cfg(all(
+    any(feature = "v1", feature = "v2", feature = "olap", feature = "oltp"),
+    not(feature = "payment_methods_v2")
+))]
+#[instrument(skip_all, fields(flow = ?Flow::TokenizeCard))]
+pub async fn tokenize_card_api(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<payment_methods::CardNetworkTokenizeRequest>,
+) -> HttpResponse {
+    let flow = Flow::TokenizeCard;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.into_inner(),
+        |state, _, req, _| async move {
+            let merchant_id = req.merchant_id.clone();
+            let (key_store, merchant_account) = get_merchant_account(&state, &merchant_id).await?;
+            let res = Box::pin(cards::tokenize_card_flow(
+                &state,
+                CardNetworkTokenizeRequest::foreign_from(req),
+                &merchant_account,
+                &key_store,
+            ))
+            .await?;
+            Ok(services::ApplicationResponse::Json(res))
+        },
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+#[cfg(all(
+    any(feature = "v1", feature = "v2", feature = "olap", feature = "oltp"),
+    not(feature = "payment_methods_v2")
+))]
+#[instrument(skip_all, fields(flow = ?Flow::TokenizeCardUsingPaymentMethodId))]
+pub async fn tokenize_card_using_pm_api(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    path: web::Path<String>,
+    json_payload: web::Json<payment_methods::CardNetworkTokenizeRequest>,
+) -> HttpResponse {
+    let flow = Flow::TokenizeCardUsingPaymentMethodId;
+    let pm_id = path.into_inner();
+    let mut payload = json_payload.into_inner();
+    if let payment_methods::TokenizeDataRequest::ExistingPaymentMethod(ref mut pm_data) =
+        payload.data
+    {
+        pm_data.payment_method_id = pm_id;
+    } else {
+        return api::log_and_return_error_response(error_stack::report!(
+            errors::ApiErrorResponse::InvalidDataValue { field_name: "card" }
+        ));
+    }
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        payload,
+        |state, _, req, _| async move {
+            let merchant_id = req.merchant_id.clone();
+            let (key_store, merchant_account) = get_merchant_account(&state, &merchant_id).await?;
+            let res = Box::pin(cards::tokenize_card_flow(
+                &state,
+                CardNetworkTokenizeRequest::foreign_from(req),
+                &merchant_account,
+                &key_store,
+            ))
+            .await?;
+            Ok(services::ApplicationResponse::Json(res))
+        },
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+#[cfg(all(
+    any(feature = "v1", feature = "v2", feature = "olap", feature = "oltp"),
+    not(feature = "payment_methods_v2")
+))]
+#[instrument(skip_all, fields(flow = ?Flow::TokenizeCardBatch))]
+pub async fn tokenize_card_batch_api(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    MultipartForm(form): MultipartForm<tokenize::CardNetworkTokenizeForm>,
+) -> HttpResponse {
+    let flow = Flow::TokenizeCardBatch;
+    let (merchant_id, records) = match tokenize::get_tokenize_card_form_records(form) {
+        Ok(res) => res,
+        Err(e) => return api::log_and_return_error_response(e.into()),
+    };
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        records,
+        |state, _, req, _| {
+            let merchant_id = merchant_id.clone();
+            async move {
+                let (key_store, merchant_account) =
+                    get_merchant_account(&state, &merchant_id).await?;
+                Box::pin(tokenize::tokenize_cards(
+                    &state,
+                    req,
+                    &merchant_account,
+                    &key_store,
+                ))
+                .await
+            }
+        },
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 #[cfg(feature = "v2")]
 #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSessionCreate))]
 pub async fn payment_methods_session_create(
diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs
index 47ad15727ee..dc2d7816ffc 100644
--- a/crates/router/src/types/api/payment_methods.rs
+++ b/crates/router/src/types/api/payment_methods.rs
@@ -1,8 +1,9 @@
 #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
 pub use api_models::payment_methods::{
-    CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardType, CustomerPaymentMethod,
+    CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
+    CardNetworkTokenizeResponse, CardType, CustomerPaymentMethod,
     CustomerPaymentMethodsListResponse, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest,
-    GetTokenizePayloadResponse, ListCountriesCurrenciesRequest,
+    GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail,
     PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate,
     PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId,
     PaymentMethodIntentConfirm, PaymentMethodIntentCreate, PaymentMethodListData,
@@ -17,15 +18,17 @@ pub use api_models::payment_methods::{
     not(feature = "payment_methods_v2")
 ))]
 pub use api_models::payment_methods::{
-    CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod,
-    CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest,
-    GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest,
+    CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardNetworkTokenizeRequest,
+    CardNetworkTokenizeResponse, CustomerPaymentMethod, CustomerPaymentMethodsListResponse,
+    DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest,
+    GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, MigrateCardDetail,
     PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate,
     PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId,
     PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate,
     PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData,
-    TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2,
-    TokenizedWalletValue1, TokenizedWalletValue2,
+    TokenizeCardRequest, TokenizeDataRequest, TokenizePayloadEncrypted, TokenizePayloadRequest,
+    TokenizePaymentMethodRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1,
+    TokenizedWalletValue2,
 };
 use error_stack::report;
 
diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs
index 74329844a76..2835acb4729 100644
--- a/crates/router/src/types/api/payments.rs
+++ b/crates/router/src/types/api/payments.rs
@@ -13,11 +13,12 @@ pub use api_models::{
     },
     payments::{
         AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card,
-        CryptoData, CustomerAcceptance, CustomerDetailsResponse, MandateAmountData, MandateData,
-        MandateTransactionType, MandateType, MandateValidationFields, NextActionType,
-        OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType,
-        PaymentListConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentMethodData,
-        PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody,
+        CryptoData, CustomerAcceptance, CustomerDetails, CustomerDetailsResponse,
+        MandateAmountData, MandateData, MandateTransactionType, MandateType,
+        MandateValidationFields, NextActionType, OnlineMandate, OpenBankingSessionToken,
+        PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilters,
+        PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest,
+        PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody,
         PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest,
         PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest,
         PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse,
diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs
index 270ed3ca64d..51f07fd6d2e 100644
--- a/crates/router/src/types/domain.rs
+++ b/crates/router/src/types/domain.rs
@@ -33,6 +33,7 @@ mod merchant_connector_account;
 mod merchant_key_store {
     pub use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore;
 }
+pub use hyperswitch_domain_models::bulk_tokenization::*;
 pub mod payment_methods {
     pub use hyperswitch_domain_models::payment_methods::*;
 }
diff --git a/crates/router/src/types/domain/payments.rs b/crates/router/src/types/domain/payments.rs
index 53bc138c649..3f7849ae0fa 100644
--- a/crates/router/src/types/domain/payments.rs
+++ b/crates/router/src/types/domain/payments.rs
@@ -1,8 +1,8 @@
 pub use hyperswitch_domain_models::payment_method_data::{
     AliPayQr, ApplePayFlow, ApplePayThirdPartySdkData, ApplePayWalletData, ApplepayPaymentMethod,
-    BankDebitData, BankRedirectData, BankTransferData, BoletoVoucherData, Card, CardRedirectData,
-    CardToken, CashappQr, CryptoData, GcashRedirection, GiftCardData, GiftCardDetails,
-    GoPayRedirection, GooglePayPaymentMethodInfo, GooglePayRedirectData,
+    BankDebitData, BankRedirectData, BankTransferData, BoletoVoucherData, Card, CardDetail,
+    CardRedirectData, CardToken, CashappQr, CryptoData, GcashRedirection, GiftCardData,
+    GiftCardDetails, GoPayRedirection, GooglePayPaymentMethodInfo, GooglePayRedirectData,
     GooglePayThirdPartySdkData, GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData,
     KakaoPayRedirection, MbWayRedirection, MifinityData, NetworkTokenData, OpenBankingData,
     PayLaterData, PaymentMethodData, RealTimePaymentData, SamsungPayWalletData,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index b4655b6b774..aca819e3413 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -543,6 +543,12 @@ pub enum Flow {
     Relay,
     /// Relay retrieve flow
     RelayRetrieve,
+    /// Card tokenization flow
+    TokenizeCard,
+    /// Card tokenization using payment method flow
+    TokenizeCardUsingPaymentMethodId,
+    /// Cards batch tokenization flow
+    TokenizeCardBatch,
     /// Incoming Relay Webhook Receive
     IncomingRelayWebhookReceive,
     /// Generate Hypersense Token

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

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

## Description
This PR introduces below changes

- Exposes two endpoints for
    - Single card tokenization
        - Tokenizing raw card details with the network
        - Tokenizing an existing payment method in HS with the network (card only)
    - Bulk tokenization
        - Tokenizing using raw card details or an existing payment method

More details around the flow is present in https://github.com/juspay/hyperswitch/issues/6971


### 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
This helps merchants to tokenize their customer's payment methods with the card networks. A new payment method entry is created for every card which can be used to perform txns using the network tokens.

## How did you test it?
<details>
    <summary>1. Tokenize using raw card details</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card' \
        --header 'Content-Type: application/json' \
        --header 'api-key: test_admin' \
        --data '{
            "card": {
                "raw_card_number": "4761360080000093",
                "card_expiry_month": "01",
                "card_expiry_year": "26",
                "card_cvc": "947"
            },
            "merchant_id": "merchant_1737627877",
            "card_type": "debit",
            "customer": {
                "id": "cus_IoFeOlGhEeD54AbBdvxI"
            }
        }'

Response

    {"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_IoFeOlGhEeD54AbBdvxI","payment_method_id":"pm_ai4pU6o32LUgRy2kl5hd","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0093","expiry_month":"01","expiry_year":"26","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":null,"card_network":null,"card_isin":"476136","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:41:54.753Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_IoFeOlGhEeD54AbBdvxI","name":"John Nether","email":"guest@example.com","phone":"6168205362","phone_country_code":"+1"},"card_tokenized":true,"req":null}

</details>

<details>
    <summary>2. Tokenize using existing payment method entry</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card/pm_HHZV9XC6C10jjKGiK8m1' \
        --header 'Content-Type: application/json' \
        --header 'api-key: test_admin' \
        --data '{
            "existing_payment_method": {
                "card_cvc": "947"
            },
            "merchant_id": "merchant_1737627877",
            "card_type": "debit",
            "customer": {
                "id": "cus_IoFeOlGhEeD54AbBdvxI"
            }
        }'

Response

    {"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_IoFeOlGhEeD54AbBdvxI","payment_method_id":"pm_HHZV9XC6C10jjKGiK8m1","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"03","expiry_year":"2030","card_token":null,"card_holder_name":"John US","card_fingerprint":null,"nick_name":"JD","card_network":null,"card_isin":"491761","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:43:02.151Z","last_used_at":"2025-01-23T10:43:02.151Z","client_secret":"pm_HHZV9XC6C10jjKGiK8m1_secret_SJC4AUiR4HmYzSDOKwPi"},"customer":{"id":"cus_IoFeOlGhEeD54AbBdvxI","name":null,"email":null,"phone":null,"phone_country_code":null},"card_tokenized":true,"req":null}

</details>

<details>
    <summary>3. Bulk tokenize</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card-batch' \
        --header 'api-key: test_admin' \
        --form 'merchant_id="merchant_1737627877"' \
        --form 'file=@"/Users/Downloads/tokenization sample - Sheet 1.csv"'

Response

    [{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_Hn03n9kbTqUxKZ0nZRVC","payment_method_id":"pm_eBONxbqiaf8T7KkGSlaq","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_Hn03n9kbTqUxKZ0nZRVC","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_OWDUMC1QBmsTeImT9xCs","payment_method_id":"pm_nRtoCkil21RaGNJulP2L","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_OWDUMC1QBmsTeImT9xCs","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_mBx8CYwaUldk2HbX9qBV","payment_method_id":"pm_nhtWX126LHU1BmoN48TB","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_mBx8CYwaUldk2HbX9qBV","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_tpr9V8N4mPijFOZGbqjH","payment_method_id":"pm_eoyzCgzbJjbfREVKe7c0","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.001Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_tpr9V8N4mPijFOZGbqjH","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_fdbpnmVFS3hhYZZfYquM","payment_method_id":"pm_ShvWRpaZyX5kucjVImmw","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.001Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_fdbpnmVFS3hhYZZfYquM","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_HeGLoqXGsKYbpCTvvNCX","payment_method_id":"pm_w6QqSyug2w4rJAIxQY4a","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.009Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_HeGLoqXGsKYbpCTvvNCX","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_3LgTOBzFOcMHEz1glZzF","payment_method_id":"pm_1G3NGbJG0LyJKvbIZvdJ","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.009Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_3LgTOBzFOcMHEz1glZzF","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_bYIVR0NQF4T4bv5KjVNd","payment_method_id":"pm_31KM9k8uPbwUAUaq03N5","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_bYIVR0NQF4T4bv5KjVNd","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_jxMzHRc3Ndfr9Mt2pHKg","payment_method_id":"pm_dAHGSnuIHQC4rHWEGAwF","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_jxMzHRc3Ndfr9Mt2pHKg","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_KyauqQZeqKbZCEdRCrPY","payment_method_id":"pm_im8kjgFyc5ydhrHES9z5","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"22","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_KyauqQZeqKbZCEdRCrPY","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_NuZ4Ntb7hWDXxFt7ro3D","payment_method_id":"pm_gfgZ1gOZ4kIdeBU7Cyqz","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_NuZ4Ntb7hWDXxFt7ro3D","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_UDoT3SdLhK62XG7xFo65","payment_method_id":"pm_cPsrZ1S3xV12NnF5uAH8","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_UDoT3SdLhK62XG7xFo65","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_cxhCY1RgcvRPlIX891kU","payment_method_id":"pm_iHXORES9dupsE5ffAG0C","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2022","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_cxhCY1RgcvRPlIX891kU","name":"Max Mustermann","email":"noreply+20240930-220107@gmail.com","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null}]

</details>

<details>
    <summary>4. Network tokenization in payments flow</summary>

1. Enable `is_network_tokenization_enabled` in profile

cURL

    curl --location --request POST 'http://localhost:8080/account/merchant_1739089569/business_profile/pro_y2hZRUddjwLlTtKLMPEM' \
        --header 'Content-Type: application/json' \
        --header 'api-key: test_admin' \
        --data '{
            "is_network_tokenization_enabled": true
        }'

2. Create a card txn using `no_three_ds` and `off_session`

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_E6uP3pz26jvUfMLK2UkVOax00881TBQscs85jNVqCBrFvcO5h2EsLtZ5HqXe93HF' \
        --data-raw '{"amount":100,"currency":"USD","confirm":true,"capture_method":"automatic","capture_on":"2022-09-10T10:11:12Z","customer_id":"cus_3Nn60DzqkFOwJ9T2y4rG","email":"guest@example.com","name":"John Doe","phone":"999999999","profile_id":"pro_y2hZRUddjwLlTtKLMPEM","phone_country_code":"+65","description":"Its my first payment request","authentication_type":"no_three_ds","return_url":"https://hyperswitch.io","setup_future_usage":"off_session","customer_acceptance":{"acceptance_type":"online","accepted_at":"1963-05-03T04:07:52.723Z","online":{"ip_address":"127.0.0.1","user_agent":"amet irure esse"}},"connector":["cybersource"],"payment_method":"card","payment_method_data":{"card":{"card_number":"4761360080000093","card_exp_month":"01","card_exp_year":"2026","card_cvc":"947","nick_name":"JD"},"billing":{"email":"guest@example.com","address":{"first_name":"John","last_name":"US"}}},"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"California","zip":"94122","country":"US","first_name":"John","last_name":"US"}},"statement_descriptor_name":"joseph","statement_descriptor_suffix":"JS","metadata":{"udf1":"value1","new_customer":"true","login_date":"2019-09-10T10:11:12Z"},"browser_info":{"ip_address":"129.0.0.1","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","accept_header":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8","language":"en-US","color_depth":32,"screen_height":1117,"screen_width":1728,"time_zone":-330,"java_enabled":true,"java_script_enabled":true}}'

Response

    {"merchant_id":"merchant_1739089569","profile_id":"pro_y2hZRUddjwLlTtKLMPEM","profile_name":"IN_default","return_url":"https://google.com/success","enable_payment_response_hash":true,"payment_response_hash_key":"onDz0wKFp8QXWiBCWX5vIvBLpcwnA6ajqvfcNmFwM66O9p59iK1uPYWVIS7pArqd","redirect_to_merchant_with_http_post":false,"webhook_details":{"webhook_version":"1.0.1","webhook_username":"random","webhook_password":"pass","webhook_url":"https://webhook.site/0728473e-e0aa-4bb6-9eea-85435cf54380","payment_created_enabled":true,"payment_succeeded_enabled":true,"payment_failed_enabled":true},"metadata":null,"routing_algorithm":null,"intent_fulfillment_time":900,"frm_routing_algorithm":null,"payout_routing_algorithm":null,"applepay_verified_domains":null,"session_expiry":900,"payment_link_config":{"domain_name":"gdpp-dev.zurich.com","theme":"#1A1A1A","logo":"https://hyperswitch.io/favicon.ico","seller_name":null,"sdk_layout":null,"display_sdk_only":null,"enabled_saved_payment_method":true,"hide_card_nickname_field":null,"show_card_form_by_default":null,"transaction_details":null,"background_image":null,"details_layout":null,"payment_button_text":"Proceed to Payment!","business_specific_configs":null,"allowed_domains":["localhost:5500"],"branding_visibility":null},"authentication_connector_details":null,"use_billing_as_payment_method_billing":true,"extended_card_info_config":null,"collect_shipping_details_from_wallet_connector":false,"collect_billing_details_from_wallet_connector":false,"always_collect_shipping_details_from_wallet_connector":false,"always_collect_billing_details_from_wallet_connector":false,"is_connector_agnostic_mit_enabled":true,"payout_link_config":null,"outgoing_webhook_custom_http_headers":null,"tax_connector_id":null,"is_tax_connector_enabled":false,"is_network_tokenization_enabled":true,"is_auto_retries_enabled":false,"max_auto_retries_enabled":null,"is_click_to_pay_enabled":false,"authentication_product_ids":null}

Verify if relevant network token locker IDs are stored in payment methods table

<img width="1675" alt="Screenshot 2025-02-09 at 2 33 01 PM" src="https://github.com/user-attachments/assets/68114287-9e88-420a-8a1f-dc8474ab3bf0" />


</details>

## 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 the submitted code
- [ ] I added unit tests for my changes where possible
