Issue:
Bug: Locker ID as a soft link for Payment Methods



Decoupling the Payment Method ID and Locker ID to allow more flexibility in PM ID usage for other features/components

- Add Card operation to save the Locker ID in the `locker_id` column of the PM table and generate and add a separate PM ID
- During retrieve, locker fetch to happen based on Locker ID with Payment Method ID as fallback (for ensuring continuity)

Pull request description:
## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
Currently we are mapping `locker_id` from locker to `payment_method_id` in Hyperswitch. This refactor involves changes to store the locker id in the `locker_id` column as opposed to the `payment_method_id` column, and store a unique ID in place of the `payment_method_id`. While reading the locker id, read from the `locker_id` column with `payment_method_id` as fallback.


### Additional Changes

- [ ] This PR modifies the API contract
- [x] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Sandbox testing - 
* Should be tested similar to https://github.com/juspay/hyperswitch/pull/3146
* Test it for both old card already saved before this PR went in along with new card
* Both `/payment_methods` and `/payment` card flow has to be tested with metadata changes too. Everything should work fine

Test cases (`payment_methods` table) - 

(Card 1): Db entry before this code changes :- `locker_id` should be null

![image](https://github.com/juspay/hyperswitch/assets/70657455/f6677536-a3ea-4db3-af1d-2493e30cbbae)

(Card 1): MetaData updation :- Both `payment_method_id` and `locker_id` will remain same but with updated `payment_method_data`

![image](https://github.com/juspay/hyperswitch/assets/70657455/45cebebf-f96c-4101-94bf-d8604d57c5eb)


(Card 2): Db entry when new card is added :- `locker_id` will now be the id from locker, `payment_method_id` will be nano_id

![image](https://github.com/juspay/hyperswitch/assets/70657455/210a0adb-2050-4fc2-9752-ca314284e4ec)

(Card 2): MetaData updation :- Both `payment_method_id` and `locker_id` will remain same but with updated `payment_method_data`

![image](https://github.com/juspay/hyperswitch/assets/70657455/0aba88f8-72b1-4269-9d2c-35d20f075929)

Redis entry (Local testing) - 

Old card (both locker_id and payment_method_id will be same):

![image](https://github.com/juspay/hyperswitch/assets/70657455/6ad1e019-69e6-444d-8b56-dde2543dc6b7)


New card (payment_method_id will be nano_id and locker_id will be the id returned from locker):

![image](https://github.com/juspay/hyperswitch/assets/70657455/400f09b9-da64-4497-bfe5-25429feab4d9)



## 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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable


Testing:

Sandbox testing - 
* Should be tested similar to https://github.com/juspay/hyperswitch/pull/3146
* Test it for both old card already saved before this PR went in along with new card
* Both `/payment_methods` and `/payment` card flow has to be tested with metadata changes too. Everything should work fine

Test cases (`payment_methods` table) - 

(Card 1): Db entry before this code changes :- `locker_id` should be null

![image](https://github.com/juspay/hyperswitch/assets/70657455/f6677536-a3ea-4db3-af1d-2493e30cbbae)

(Card 1): MetaData updation :- Both `payment_method_id` and `locker_id` will remain same but with updated `payment_method_data`

![image](https://github.com/juspay/hyperswitch/assets/70657455/45cebebf-f96c-4101-94bf-d8604d57c5eb)


(Card 2): Db entry when new card is added :- `locker_id` will now be the id from locker, `payment_method_id` will be nano_id

![image](https://github.com/juspay/hyperswitch/assets/70657455/210a0adb-2050-4fc2-9752-ca314284e4ec)

(Card 2): MetaData updation :- Both `payment_method_id` and `locker_id` will remain same but with updated `payment_method_data`

![image](https://github.com/juspay/hyperswitch/assets/70657455/0aba88f8-72b1-4269-9d2c-35d20f075929)

Redis entry (Local testing) - 

Old card (both locker_id and payment_method_id will be same):

![image](https://github.com/juspay/hyperswitch/assets/70657455/6ad1e019-69e6-444d-8b56-dde2543dc6b7)


New card (payment_method_id will be nano_id and locker_id will be the id returned from locker):

![image](https://github.com/juspay/hyperswitch/assets/70657455/400f09b9-da64-4497-bfe5-25429feab4d9)

Patch:
diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs
index d8b46c1932e..f9767e2939b 100644
--- a/crates/diesel_models/src/payment_method.rs
+++ b/crates/diesel_models/src/payment_method.rs
@@ -33,6 +33,7 @@ pub struct PaymentMethod {
     pub payment_method_issuer_code: Option<storage_enums::PaymentMethodIssuerCode>,
     pub metadata: Option<pii::SecretSerdeValue>,
     pub payment_method_data: Option<Encryption>,
+    pub locker_id: Option<String>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Insertable, Queryable, router_derive::DebugAsDisplay)]
@@ -59,6 +60,7 @@ pub struct PaymentMethodNew {
     pub last_modified: PrimitiveDateTime,
     pub metadata: Option<pii::SecretSerdeValue>,
     pub payment_method_data: Option<Encryption>,
+    pub locker_id: Option<String>,
 }
 
 impl Default for PaymentMethodNew {
@@ -69,6 +71,7 @@ impl Default for PaymentMethodNew {
             customer_id: String::default(),
             merchant_id: String::default(),
             payment_method_id: String::default(),
+            locker_id: Option::default(),
             payment_method: storage_enums::PaymentMethod::default(),
             payment_method_type: Option::default(),
             payment_method_issuer: Option::default(),
diff --git a/crates/diesel_models/src/query/payment_method.rs b/crates/diesel_models/src/query/payment_method.rs
index b1ea3ee388a..298db2a234b 100644
--- a/crates/diesel_models/src/query/payment_method.rs
+++ b/crates/diesel_models/src/query/payment_method.rs
@@ -44,6 +44,15 @@ impl PaymentMethod {
         .await
     }
 
+    #[instrument(skip(conn))]
+    pub async fn find_by_locker_id(conn: &PgPooledConn, locker_id: &str) -> StorageResult<Self> {
+        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
+            conn,
+            dsl::locker_id.eq(locker_id.to_owned()),
+        )
+        .await
+    }
+
     #[instrument(skip(conn))]
     pub async fn find_by_payment_method_id(
         conn: &PgPooledConn,
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index 5093f0df7d0..3364e560277 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -828,6 +828,8 @@ diesel::table! {
         payment_method_issuer_code -> Nullable<PaymentMethodIssuerCode>,
         metadata -> Nullable<Json>,
         payment_method_data -> Nullable<Bytea>,
+        #[max_length = 64]
+        locker_id -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs
index 9442bf2ff34..be306112873 100644
--- a/crates/router/src/core/customers.rs
+++ b/crates/router/src/core/customers.rs
@@ -218,7 +218,7 @@ pub async fn delete_customer(
                         &state,
                         &req.customer_id,
                         &merchant_account.merchant_id,
-                        &pm.payment_method_id,
+                        pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
                     )
                     .await
                     .switch()?;
diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs
index 2ebec569b38..1347bb8ffd0 100644
--- a/crates/router/src/core/locker_migration.rs
+++ b/crates/router/src/core/locker_migration.rs
@@ -83,9 +83,13 @@ pub async fn call_to_locker(
         .into_iter()
         .filter(|pm| matches!(pm.payment_method, storage_enums::PaymentMethod::Card))
     {
-        let card =
-            cards::get_card_from_locker(state, customer_id, merchant_id, &pm.payment_method_id)
-                .await;
+        let card = cards::get_card_from_locker(
+            state,
+            customer_id,
+            merchant_id,
+            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
+        )
+        .await;
 
         let card = match card {
             Ok(card) => card,
@@ -127,7 +131,7 @@ pub async fn call_to_locker(
                 customer_id.to_string(),
                 merchant_account,
                 api_enums::LockerChoice::HyperswitchCardVault,
-                Some(&pm.payment_method_id),
+                Some(pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id)),
             )
             .await
             .change_context(errors::ApiErrorResponse::InternalServerError)
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index c1617fa77c9..bdbd9b02d9e 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -155,7 +155,7 @@ impl PaymentMethodRetrieve for Oss {
             storage::PaymentTokenData::Permanent(card_token) => {
                 helpers::retrieve_card_with_permanent_token(
                     state,
-                    &card_token.token,
+                    card_token.locker_id.as_ref().unwrap_or(&card_token.token),
                     payment_intent,
                     card_token_data,
                 )
@@ -166,7 +166,7 @@ impl PaymentMethodRetrieve for Oss {
             storage::PaymentTokenData::PermanentCard(card_token) => {
                 helpers::retrieve_card_with_permanent_token(
                     state,
-                    &card_token.token,
+                    card_token.locker_id.as_ref().unwrap_or(&card_token.token),
                     payment_intent,
                     card_token_data,
                 )
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index dbd8efcd1cf..7bf956be9ff 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -76,6 +76,7 @@ pub async fn create_payment_method(
     req: &api::PaymentMethodCreate,
     customer_id: &str,
     payment_method_id: &str,
+    locker_id: Option<String>,
     merchant_id: &str,
     pm_metadata: Option<serde_json::Value>,
     payment_method_data: Option<Encryption>,
@@ -90,6 +91,7 @@ pub async fn create_payment_method(
             customer_id: customer_id.to_string(),
             merchant_id: merchant_id.to_string(),
             payment_method_id: payment_method_id.to_string(),
+            locker_id,
             payment_method: req.payment_method,
             payment_method_type: req.payment_method_type,
             payment_method_issuer: req.payment_method_issuer.clone(),
@@ -131,6 +133,65 @@ pub fn store_default_payment_method(
     (payment_method_response, None)
 }
 
+#[instrument(skip_all)]
+pub async fn get_or_insert_payment_method(
+    db: &dyn db::StorageInterface,
+    req: api::PaymentMethodCreate,
+    resp: &mut api::PaymentMethodResponse,
+    merchant_account: &domain::MerchantAccount,
+    customer_id: &str,
+    key_store: &domain::MerchantKeyStore,
+) -> errors::RouterResult<diesel_models::PaymentMethod> {
+    let mut payment_method_id = resp.payment_method_id.clone();
+    let mut locker_id = None;
+    let payment_method = {
+        let existing_pm_by_pmid = db.find_payment_method(&payment_method_id).await;
+
+        if let Err(err) = existing_pm_by_pmid {
+            if err.current_context().is_db_not_found() {
+                locker_id = Some(payment_method_id.clone());
+                let existing_pm_by_locker_id = db
+                    .find_payment_method_by_locker_id(&payment_method_id)
+                    .await;
+
+                match &existing_pm_by_locker_id {
+                    Ok(pm) => payment_method_id = pm.payment_method_id.clone(),
+                    Err(_) => payment_method_id = generate_id(consts::ID_LENGTH, "pm"),
+                };
+                existing_pm_by_locker_id
+            } else {
+                Err(err)
+            }
+        } else {
+            existing_pm_by_pmid
+        }
+    };
+    resp.payment_method_id = payment_method_id.to_owned();
+
+    match payment_method {
+        Ok(pm) => Ok(pm),
+        Err(err) => {
+            if err.current_context().is_db_not_found() {
+                insert_payment_method(
+                    db,
+                    resp,
+                    req,
+                    key_store,
+                    &merchant_account.merchant_id,
+                    customer_id,
+                    resp.metadata.clone().map(|val| val.expose()),
+                    locker_id,
+                )
+                .await
+            } else {
+                Err(err)
+                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                    .attach_printable("Error while finding payment method")
+            }
+        }
+    }
+}
+
 #[instrument(skip_all)]
 pub async fn add_payment_method(
     state: routes::AppState,
@@ -182,40 +243,41 @@ pub async fn add_payment_method(
         )),
     };
 
-    let (resp, duplication_check) = response?;
+    let (mut resp, duplication_check) = response?;
 
     match duplication_check {
         Some(duplication_check) => match duplication_check {
             payment_methods::DataDuplicationCheck::Duplicated => {
-                let existing_pm = db.find_payment_method(&resp.payment_method_id).await;
-
-                if let Err(err) = existing_pm {
-                    if err.current_context().is_db_not_found() {
-                        insert_payment_method(
-                            db,
-                            &resp,
-                            req,
-                            key_store,
-                            merchant_id,
-                            &customer_id,
-                            None,
-                        )
-                        .await
-                    } else {
-                        Err(err)
-                            .change_context(errors::ApiErrorResponse::InternalServerError)
-                            .attach_printable("Error while finding payment method")
-                    }?
-                };
+                get_or_insert_payment_method(
+                    db,
+                    req.clone(),
+                    &mut resp,
+                    merchant_account,
+                    &customer_id,
+                    key_store,
+                )
+                .await?;
             }
-
             payment_methods::DataDuplicationCheck::MetaDataChanged => {
                 if let Some(card) = req.card.clone() {
+                    let existing_pm = get_or_insert_payment_method(
+                        db,
+                        req.clone(),
+                        &mut resp,
+                        merchant_account,
+                        &customer_id,
+                        key_store,
+                    )
+                    .await?;
+
                     delete_card_from_locker(
                         &state,
                         &customer_id,
                         merchant_id,
-                        &resp.payment_method_id,
+                        existing_pm
+                            .locker_id
+                            .as_ref()
+                            .unwrap_or(&existing_pm.payment_method_id),
                     )
                     .await?;
 
@@ -226,7 +288,12 @@ pub async fn add_payment_method(
                         customer_id.clone(),
                         merchant_account,
                         api::enums::LockerChoice::HyperswitchCardVault,
-                        Some(&resp.payment_method_id),
+                        Some(
+                            existing_pm
+                                .locker_id
+                                .as_ref()
+                                .unwrap_or(&existing_pm.payment_method_id),
+                        ),
                     )
                     .await;
 
@@ -243,69 +310,46 @@ pub async fn add_payment_method(
                             .attach_printable("Failed while updating card metadata changes"))?
                     };
 
-                    let existing_pm = db.find_payment_method(&resp.payment_method_id).await;
-                    match existing_pm {
-                        Ok(pm) => {
-                            let updated_card = Some(api::CardDetailFromLocker {
-                                scheme: None,
-                                last4_digits: Some(card.card_number.clone().get_last4()),
-                                issuer_country: None,
-                                card_number: Some(card.card_number),
-                                expiry_month: Some(card.card_exp_month),
-                                expiry_year: Some(card.card_exp_year),
-                                card_token: None,
-                                card_fingerprint: None,
-                                card_holder_name: card.card_holder_name,
-                                nick_name: card.nick_name,
-                                card_network: None,
-                                card_isin: None,
-                                card_issuer: None,
-                                card_type: None,
-                                saved_to_locker: true,
-                            });
-
-                            let updated_pmd = updated_card.as_ref().map(|card| {
-                                PaymentMethodsData::Card(CardDetailsPaymentMethod::from(
-                                    card.clone(),
-                                ))
-                            });
-                            let pm_data_encrypted =
-                                create_encrypted_payment_method_data(key_store, updated_pmd).await;
-
-                            let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
-                                payment_method_data: pm_data_encrypted,
-                            };
-
-                            db.update_payment_method(pm, pm_update)
-                                .await
-                                .change_context(errors::ApiErrorResponse::InternalServerError)
-                                .attach_printable("Failed to add payment method in db")?;
-                        }
-                        Err(err) => {
-                            if err.current_context().is_db_not_found() {
-                                insert_payment_method(
-                                    db,
-                                    &resp,
-                                    req,
-                                    key_store,
-                                    merchant_id,
-                                    &customer_id,
-                                    None,
-                                )
-                                .await
-                            } else {
-                                Err(err)
-                                    .change_context(errors::ApiErrorResponse::InternalServerError)
-                                    .attach_printable("Error while finding payment method")
-                            }?;
-                        }
-                    }
+                    let updated_card = Some(api::CardDetailFromLocker {
+                        scheme: None,
+                        last4_digits: Some(card.card_number.clone().get_last4()),
+                        issuer_country: None,
+                        card_number: Some(card.card_number),
+                        expiry_month: Some(card.card_exp_month),
+                        expiry_year: Some(card.card_exp_year),
+                        card_token: None,
+                        card_fingerprint: None,
+                        card_holder_name: card.card_holder_name,
+                        nick_name: card.nick_name,
+                        card_network: None,
+                        card_isin: None,
+                        card_issuer: None,
+                        card_type: None,
+                        saved_to_locker: true,
+                    });
+
+                    let updated_pmd = updated_card.as_ref().map(|card| {
+                        PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()))
+                    });
+                    let pm_data_encrypted =
+                        create_encrypted_payment_method_data(key_store, updated_pmd).await;
+
+                    let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
+                        payment_method_data: pm_data_encrypted,
+                    };
+
+                    db.update_payment_method(existing_pm, pm_update)
+                        .await
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to add payment method in db")?;
                 }
             }
         },
         None => {
             let pm_metadata = resp.metadata.as_ref().map(|data| data.peek());
 
+            let locker_id = Some(resp.payment_method_id);
+            resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
             insert_payment_method(
                 db,
                 &resp,
@@ -314,14 +358,16 @@ pub async fn add_payment_method(
                 merchant_id,
                 &customer_id,
                 pm_metadata.cloned(),
+                locker_id,
             )
             .await?;
         }
-    };
+    }
 
     Ok(services::ApplicationResponse::Json(resp))
 }
 
+#[allow(clippy::too_many_arguments)]
 pub async fn insert_payment_method(
     db: &dyn db::StorageInterface,
     resp: &api::PaymentMethodResponse,
@@ -330,7 +376,8 @@ pub async fn insert_payment_method(
     merchant_id: &str,
     customer_id: &str,
     pm_metadata: Option<serde_json::Value>,
-) -> errors::RouterResult<()> {
+    locker_id: Option<String>,
+) -> errors::RouterResult<diesel_models::PaymentMethod> {
     let pm_card_details = resp
         .card
         .as_ref()
@@ -341,14 +388,13 @@ pub async fn insert_payment_method(
         &req,
         customer_id,
         &resp.payment_method_id,
+        locker_id,
         merchant_id,
         pm_metadata,
         pm_data_encrypted,
         key_store,
     )
-    .await?;
-
-    Ok(())
+    .await
 }
 
 #[instrument(skip_all)]
@@ -372,7 +418,7 @@ pub async fn update_customer_payment_method(
             &state,
             &pm.customer_id,
             &pm.merchant_id,
-            &pm.payment_method_id,
+            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
         )
         .await?;
     };
@@ -927,7 +973,7 @@ pub async fn mock_call_to_locker_hs<'a>(
         duplication_check: None,
     };
     Ok(payment_methods::StoreCardResp {
-        status: "SUCCESS".to_string(),
+        status: "Ok".to_string(),
         error_code: None,
         error_message: None,
         payload: Some(payload),
@@ -1013,7 +1059,7 @@ pub async fn mock_delete_card_hs<'a>(
         .await
         .change_context(errors::VaultError::FetchCardFailed)?;
     Ok(payment_methods::DeleteCardResp {
-        status: "SUCCESS".to_string(),
+        status: "Ok".to_string(),
         error_code: None,
         error_message: None,
     })
@@ -1032,7 +1078,7 @@ pub async fn mock_delete_card<'a>(
         card_id: Some(locker_mock_up.card_id),
         external_id: Some(locker_mock_up.external_id),
         card_isin: None,
-        status: "SUCCESS".to_string(),
+        status: "Ok".to_string(),
     })
 }
 //------------------------------------------------------------------------------
@@ -2642,7 +2688,11 @@ pub async fn list_customer_payment_method(
                     (
                         card_details,
                         None,
-                        PaymentTokenData::permanent_card(pm.payment_method_id.clone()),
+                        PaymentTokenData::permanent_card(
+                            Some(pm.payment_method_id.clone()),
+                            pm.locker_id.clone().or(Some(pm.payment_method_id.clone())),
+                            pm.locker_id.clone().unwrap_or(pm.payment_method_id.clone()),
+                        ),
                     )
                 } else {
                     continue;
@@ -2662,7 +2712,7 @@ pub async fn list_customer_payment_method(
                             &token,
                             &pm.customer_id,
                             &pm.merchant_id,
-                            &pm.payment_method_id,
+                            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
                         )
                         .await?,
                     ),
@@ -2902,7 +2952,7 @@ pub async fn get_card_details_from_locker(
         state,
         &pm.customer_id,
         &pm.merchant_id,
-        &pm.payment_method_id,
+        pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
     )
     .await
     .change_context(errors::ApiErrorResponse::InternalServerError)
@@ -3167,7 +3217,7 @@ pub async fn retrieve_payment_method(
                 &state,
                 &pm.customer_id,
                 &pm.merchant_id,
-                &pm.payment_method_id,
+                pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
             )
             .await
             .change_context(errors::ApiErrorResponse::InternalServerError)
@@ -3217,11 +3267,11 @@ pub async fn delete_payment_method(
             &state,
             &key.customer_id,
             &key.merchant_id,
-            pm_id.payment_method_id.as_str(),
+            key.locker_id.as_ref().unwrap_or(&key.payment_method_id),
         )
         .await?;
 
-        if response.status == "SUCCESS" {
+        if response.status == "Ok" {
             logger::info!("Card From locker deleted Successfully!");
         } else {
             logger::error!("Error: Deleting Card From Locker!\n{:#?}", response);
diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs
index 57e46bc9769..6bad41630b5 100644
--- a/crates/router/src/core/payment_methods/transformers.rs
+++ b/crates/router/src/core/payment_methods/transformers.rs
@@ -397,46 +397,6 @@ pub fn mk_add_card_response_hs(
     }
 }
 
-pub fn mk_add_card_response(
-    card: api::CardDetail,
-    response: AddCardResponse,
-    req: api::PaymentMethodCreate,
-    merchant_id: &str,
-) -> api::PaymentMethodResponse {
-    let mut card_number = card.card_number.peek().to_owned();
-    let card = api::CardDetailFromLocker {
-        scheme: None,
-        last4_digits: Some(card_number.split_off(card_number.len() - 4)),
-        issuer_country: None, // [#256] bin mapping
-        card_number: Some(card.card_number),
-        expiry_month: Some(card.card_exp_month),
-        expiry_year: Some(card.card_exp_year),
-        card_token: Some(response.external_id.into()), // [#256]
-        card_fingerprint: Some(response.card_fingerprint),
-        card_holder_name: card.card_holder_name,
-        nick_name: card.nick_name,
-        card_isin: None,
-        card_issuer: None,
-        card_network: None,
-        card_type: None,
-        saved_to_locker: true,
-    };
-    api::PaymentMethodResponse {
-        merchant_id: merchant_id.to_owned(),
-        customer_id: req.customer_id,
-        payment_method_id: response.card_id,
-        payment_method: req.payment_method,
-        payment_method_type: req.payment_method_type,
-        bank_transfer: None,
-        card: Some(card),
-        metadata: req.metadata,
-        created: Some(common_utils::date_time::now()),
-        recurring_enabled: false,           // [#256]
-        installment_payment_enabled: false, // [#256] Pending on discussion, and not stored in the card locker
-        payment_experience: None,           // [#256]
-    }
-}
-
 pub fn mk_add_card_request(
     locker: &settings::Locker,
     card: &api::CardDetail,
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 2d7a09bdae1..d08f0208500 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -6,6 +6,7 @@ use router_env::{instrument, tracing};
 
 use super::helpers;
 use crate::{
+    consts,
     core::{
         errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt},
         mandate, payment_methods, payments,
@@ -19,7 +20,7 @@ use crate::{
         domain,
         storage::{self, enums as storage_enums},
     },
-    utils::OptionExt,
+    utils::{generate_id, OptionExt},
 };
 
 #[instrument(skip_all)]
@@ -85,7 +86,7 @@ where
                 .await?;
                 let merchant_id = &merchant_account.merchant_id;
 
-                let locker_response = if !state.conf.locker.locker_enabled {
+                let (mut resp, duplication_check) = if !state.conf.locker.locker_enabled {
                     skip_saving_card_in_locker(
                         merchant_account,
                         payment_method_create_request.to_owned(),
@@ -100,9 +101,7 @@ where
                     .await?
                 };
 
-                let duplication_check = locker_response.1;
-
-                let pm_card_details = locker_response.0.card.as_ref().map(|card| {
+                let pm_card_details = resp.card.as_ref().map(|card| {
                     api::payment_methods::PaymentMethodsData::Card(CardDetailsPaymentMethod::from(
                         card.clone(),
                     ))
@@ -115,13 +114,44 @@ where
                     )
                     .await;
 
+                let mut payment_method_id = resp.payment_method_id.clone();
+                let mut locker_id = None;
+
                 match duplication_check {
                     Some(duplication_check) => match duplication_check {
                         payment_methods::transformers::DataDuplicationCheck::Duplicated => {
-                            let existing_pm = db
-                                .find_payment_method(&locker_response.0.payment_method_id)
-                                .await;
-                            match existing_pm {
+                            let payment_method = {
+                                let existing_pm_by_pmid =
+                                    db.find_payment_method(&payment_method_id).await;
+
+                                if let Err(err) = existing_pm_by_pmid {
+                                    if err.current_context().is_db_not_found() {
+                                        locker_id = Some(payment_method_id.clone());
+                                        let existing_pm_by_locker_id = db
+                                            .find_payment_method_by_locker_id(&payment_method_id)
+                                            .await;
+
+                                        match &existing_pm_by_locker_id {
+                                            Ok(pm) => {
+                                                payment_method_id = pm.payment_method_id.clone()
+                                            }
+                                            Err(_) => {
+                                                payment_method_id =
+                                                    generate_id(consts::ID_LENGTH, "pm")
+                                            }
+                                        };
+                                        existing_pm_by_locker_id
+                                    } else {
+                                        Err(err)
+                                    }
+                                } else {
+                                    existing_pm_by_pmid
+                                }
+                            };
+
+                            resp.payment_method_id = payment_method_id;
+
+                            match payment_method {
                                 Ok(pm) => {
                                     let pm_metadata = create_payment_method_metadata(
                                         pm.metadata.as_ref(),
@@ -146,7 +176,8 @@ where
                                             db,
                                             &payment_method_create_request,
                                             &customer.customer_id,
-                                            &locker_response.0.payment_method_id,
+                                            &resp.payment_method_id,
+                                            locker_id,
                                             merchant_id,
                                             pm_metadata,
                                             pm_data_encrypted,
@@ -165,22 +196,87 @@ where
                         }
                         payment_methods::transformers::DataDuplicationCheck::MetaDataChanged => {
                             if let Some(card) = payment_method_create_request.card.clone() {
+                                let payment_method = {
+                                    let existing_pm_by_pmid =
+                                        db.find_payment_method(&payment_method_id).await;
+
+                                    if let Err(err) = existing_pm_by_pmid {
+                                        if err.current_context().is_db_not_found() {
+                                            locker_id = Some(payment_method_id.clone());
+                                            let existing_pm_by_locker_id = db
+                                                .find_payment_method_by_locker_id(
+                                                    &payment_method_id,
+                                                )
+                                                .await;
+
+                                            match &existing_pm_by_locker_id {
+                                                Ok(pm) => {
+                                                    payment_method_id = pm.payment_method_id.clone()
+                                                }
+                                                Err(_) => {
+                                                    payment_method_id =
+                                                        generate_id(consts::ID_LENGTH, "pm")
+                                                }
+                                            };
+                                            existing_pm_by_locker_id
+                                        } else {
+                                            Err(err)
+                                        }
+                                    } else {
+                                        existing_pm_by_pmid
+                                    }
+                                };
+
+                                resp.payment_method_id = payment_method_id;
+
+                                let existing_pm =
+                                    match payment_method {
+                                        Ok(pm) => Ok(pm),
+                                        Err(err) => {
+                                            if err.current_context().is_db_not_found() {
+                                                payment_methods::cards::insert_payment_method(
+                                                    db,
+                                                    &resp,
+                                                    payment_method_create_request.clone(),
+                                                    key_store,
+                                                    &merchant_account.merchant_id,
+                                                    &customer.customer_id,
+                                                    resp.metadata.clone().map(|val| val.expose()),
+                                                    locker_id,
+                                                )
+                                                .await
+                                            } else {
+                                                Err(err)
+                                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                                    .attach_printable("Error while finding payment method")
+                                            }
+                                        }
+                                    }?;
+
                                 payment_methods::cards::delete_card_from_locker(
                                     state,
                                     &customer.customer_id,
                                     merchant_id,
-                                    &locker_response.0.payment_method_id,
+                                    existing_pm
+                                        .locker_id
+                                        .as_ref()
+                                        .unwrap_or(&existing_pm.payment_method_id),
                                 )
                                 .await?;
 
                                 let add_card_resp = payment_methods::cards::add_card_hs(
                                     state,
-                                    payment_method_create_request.clone(),
+                                    payment_method_create_request,
                                     &card,
                                     customer.customer_id.clone(),
                                     merchant_account,
                                     api::enums::LockerChoice::HyperswitchCardVault,
-                                    Some(&locker_response.0.payment_method_id),
+                                    Some(
+                                        existing_pm
+                                            .locker_id
+                                            .as_ref()
+                                            .unwrap_or(&existing_pm.payment_method_id),
+                                    ),
                                 )
                                 .await;
 
@@ -188,7 +284,7 @@ where
                                     logger::error!(vault_err=?err);
                                     db.delete_payment_method_by_merchant_id_payment_method_id(
                                         merchant_id,
-                                        &locker_response.0.payment_method_id,
+                                        &resp.payment_method_id,
                                     )
                                     .await
                                     .to_not_found_response(
@@ -201,90 +297,59 @@ where
                                         ))?
                                 };
 
-                                let existing_pm = db
-                                    .find_payment_method(&locker_response.0.payment_method_id)
+                                let updated_card = Some(CardDetailFromLocker {
+                                    scheme: None,
+                                    last4_digits: Some(card.card_number.clone().get_last4()),
+                                    issuer_country: None,
+                                    card_number: Some(card.card_number),
+                                    expiry_month: Some(card.card_exp_month),
+                                    expiry_year: Some(card.card_exp_year),
+                                    card_token: None,
+                                    card_fingerprint: None,
+                                    card_holder_name: card.card_holder_name,
+                                    nick_name: card.nick_name,
+                                    card_network: None,
+                                    card_isin: None,
+                                    card_issuer: None,
+                                    card_type: None,
+                                    saved_to_locker: true,
+                                });
+
+                                let updated_pmd = updated_card.as_ref().map(|card| {
+                                    PaymentMethodsData::Card(CardDetailsPaymentMethod::from(
+                                        card.clone(),
+                                    ))
+                                });
+                                let pm_data_encrypted =
+                                    payment_methods::cards::create_encrypted_payment_method_data(
+                                        key_store,
+                                        updated_pmd,
+                                    )
                                     .await;
-                                match existing_pm {
-                                    Ok(pm) => {
-                                        let updated_card = Some(CardDetailFromLocker {
-                                            scheme: None,
-                                            last4_digits: Some(
-                                                card.card_number.clone().get_last4(),
-                                            ),
-                                            issuer_country: None,
-                                            card_number: Some(card.card_number),
-                                            expiry_month: Some(card.card_exp_month),
-                                            expiry_year: Some(card.card_exp_year),
-                                            card_token: None,
-                                            card_fingerprint: None,
-                                            card_holder_name: card.card_holder_name,
-                                            nick_name: card.nick_name,
-                                            card_network: None,
-                                            card_isin: None,
-                                            card_issuer: None,
-                                            card_type: None,
-                                            saved_to_locker: true,
-                                        });
-
-                                        let updated_pmd = updated_card.as_ref().map(|card| {
-                                            PaymentMethodsData::Card(
-                                                CardDetailsPaymentMethod::from(card.clone()),
-                                            )
-                                        });
-                                        let pm_data_encrypted =
-                                            payment_methods::cards::create_encrypted_payment_method_data(
-                                                key_store,
-                                                updated_pmd,
-                                            )
-                                            .await;
 
-                                        let pm_update =
-                                            storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
-                                                payment_method_data: pm_data_encrypted,
-                                            };
+                                let pm_update =
+                                    storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
+                                        payment_method_data: pm_data_encrypted,
+                                    };
 
-                                        db.update_payment_method(pm, pm_update)
-                                            .await
-                                            .change_context(
-                                                errors::ApiErrorResponse::InternalServerError,
-                                            )
-                                            .attach_printable(
-                                                "Failed to add payment method in db",
-                                            )?;
-                                    }
-                                    Err(err) => {
-                                        if err.current_context().is_db_not_found() {
-                                            payment_methods::cards::insert_payment_method(
-                                                db,
-                                                &locker_response.0,
-                                                payment_method_create_request,
-                                                key_store,
-                                                merchant_id,
-                                                &customer.customer_id,
-                                                None,
-                                            )
-                                            .await
-                                        } else {
-                                            Err(err)
-                                                .change_context(
-                                                    errors::ApiErrorResponse::InternalServerError,
-                                                )
-                                                .attach_printable(
-                                                    "Error while finding payment method",
-                                                )
-                                        }?;
-                                    }
-                                }
+                                db.update_payment_method(existing_pm, pm_update)
+                                    .await
+                                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                                    .attach_printable("Failed to add payment method in db")?;
                             }
                         }
                     },
                     None => {
                         let pm_metadata = create_payment_method_metadata(None, connector_token)?;
+
+                        locker_id = Some(resp.payment_method_id);
+                        resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
                         payment_methods::cards::create_payment_method(
                             db,
                             &payment_method_create_request,
                             &customer.customer_id,
-                            &locker_response.0.payment_method_id,
+                            &resp.payment_method_id,
+                            locker_id,
                             merchant_id,
                             pm_metadata,
                             pm_data_encrypted,
@@ -294,7 +359,7 @@ where
                     }
                 }
 
-                Some(locker_response.0.payment_method_id)
+                Some(resp.payment_method_id)
             } else {
                 None
             };
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index 7fb6fa3bde6..de57f8549db 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -78,9 +78,11 @@ pub async fn make_payout_method_data<'a>(
                 .attach_printable("failed to deserialize hyperswitch token data")?;
 
             let payment_token = match payment_token_data {
-                storage::PaymentTokenData::PermanentCard(storage::CardTokenData { token }) => {
-                    Some(token)
-                }
+                storage::PaymentTokenData::PermanentCard(storage::CardTokenData {
+                    locker_id,
+                    token,
+                    ..
+                }) => locker_id.or(Some(token)),
                 storage::PaymentTokenData::TemporaryGeneric(storage::GenericTokenData {
                     token,
                 }) => Some(token),
@@ -364,11 +366,13 @@ pub async fn save_payout_data_to_locker(
         card_network: None,
     };
 
+    let payment_method_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm");
     cards::create_payment_method(
         db,
         &payment_method,
         &payout_attempt.customer_id,
-        &stored_resp.card_reference,
+        &payment_method_id,
+        Some(stored_resp.card_reference),
         &merchant_account.merchant_id,
         None,
         card_details_encrypted,
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 78b95544ca1..9dbecd82485 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -1270,6 +1270,15 @@ impl PaymentMethodInterface for KafkaStore {
             .await
     }
 
+    async fn find_payment_method_by_locker_id(
+        &self,
+        locker_id: &str,
+    ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
+        self.diesel_store
+            .find_payment_method_by_locker_id(locker_id)
+            .await
+    }
+
     async fn insert_payment_method(
         &self,
         m: storage::PaymentMethodNew,
diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs
index b109d1fe5bd..4f4087f552e 100644
--- a/crates/router/src/db/payment_method.rs
+++ b/crates/router/src/db/payment_method.rs
@@ -15,6 +15,11 @@ pub trait PaymentMethodInterface {
         payment_method_id: &str,
     ) -> CustomResult<storage::PaymentMethod, errors::StorageError>;
 
+    async fn find_payment_method_by_locker_id(
+        &self,
+        locker_id: &str,
+    ) -> CustomResult<storage::PaymentMethod, errors::StorageError>;
+
     async fn find_payment_method_by_customer_id_merchant_id_list(
         &self,
         customer_id: &str,
@@ -52,6 +57,17 @@ impl PaymentMethodInterface for Store {
             .into_report()
     }
 
+    async fn find_payment_method_by_locker_id(
+        &self,
+        locker_id: &str,
+    ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
+        let conn = connection::pg_connection_read(self).await?;
+        storage::PaymentMethod::find_by_locker_id(&conn, locker_id)
+            .await
+            .map_err(Into::into)
+            .into_report()
+    }
+
     async fn insert_payment_method(
         &self,
         payment_method_new: storage::PaymentMethodNew,
@@ -127,6 +143,25 @@ impl PaymentMethodInterface for MockDb {
         }
     }
 
+    async fn find_payment_method_by_locker_id(
+        &self,
+        locker_id: &str,
+    ) -> CustomResult<storage::PaymentMethod, errors::StorageError> {
+        let payment_methods = self.payment_methods.lock().await;
+        let payment_method = payment_methods
+            .iter()
+            .find(|pm| pm.locker_id == Some(locker_id.to_string()))
+            .cloned();
+
+        match payment_method {
+            Some(pm) => Ok(pm),
+            None => Err(errors::StorageError::ValueNotFound(
+                "cannot find payment method".to_string(),
+            )
+            .into()),
+        }
+    }
+
     async fn insert_payment_method(
         &self,
         payment_method_new: storage::PaymentMethodNew,
@@ -142,6 +177,7 @@ impl PaymentMethodInterface for MockDb {
             customer_id: payment_method_new.customer_id,
             merchant_id: payment_method_new.merchant_id,
             payment_method_id: payment_method_new.payment_method_id,
+            locker_id: payment_method_new.locker_id,
             accepted_currency: payment_method_new.accepted_currency,
             scheme: payment_method_new.scheme,
             token: payment_method_new.token,
diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs
index 051547dfa92..10c56973dd3 100644
--- a/crates/router/src/types/api/mandates.rs
+++ b/crates/router/src/types/api/mandates.rs
@@ -51,7 +51,10 @@ impl MandateResponseExt for MandateResponse {
                     state,
                     &payment_method.customer_id,
                     &payment_method.merchant_id,
-                    &payment_method.payment_method_id,
+                    payment_method
+                        .locker_id
+                        .as_ref()
+                        .unwrap_or(&payment_method.payment_method_id),
                 )
                 .await?;
 
diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs
index 096303446dc..a787a4d932c 100644
--- a/crates/router/src/types/storage/payment_method.rs
+++ b/crates/router/src/types/storage/payment_method.rs
@@ -13,6 +13,8 @@ pub enum PaymentTokenKind {
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct CardTokenData {
+    pub payment_method_id: Option<String>,
+    pub locker_id: Option<String>,
     pub token: String,
 }
 
@@ -34,8 +36,16 @@ pub enum PaymentTokenData {
 }
 
 impl PaymentTokenData {
-    pub fn permanent_card(token: String) -> Self {
-        Self::PermanentCard(CardTokenData { token })
+    pub fn permanent_card(
+        payment_method_id: Option<String>,
+        locker_id: Option<String>,
+        token: String,
+    ) -> Self {
+        Self::PermanentCard(CardTokenData {
+            payment_method_id,
+            locker_id,
+            token,
+        })
     }
 
     pub fn temporary_generic(token: String) -> Self {
diff --git a/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/down.sql b/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/down.sql
new file mode 100644
index 00000000000..90eaebaf4da
--- /dev/null
+++ b/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+
+ALTER TABLE payment_methods DROP COLUMN IF EXISTS locker_id;
diff --git a/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/up.sql b/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/up.sql
new file mode 100644
index 00000000000..516dc8a8818
--- /dev/null
+++ b/migrations/2024-02-20-142032_add_locker_id_to_payment_methods/up.sql
@@ -0,0 +1,3 @@
+-- Your SQL goes here
+
+ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS locker_id VARCHAR(64) DEFAULT NULL;
\ No newline at end of file