Bug: Generate Client Secret for /payment_methods requests



Create a `PM Client Secret` during the `/payment_methods` API request.
This would to be stored in the PM Table and used as reference for updating the PM record once the card is saved.
diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs
index b27ca142c94..b8f4a9d6d90 100644
--- a/crates/api_models/src/events/payment.rs
+++ b/crates/api_models/src/events/payment.rs
@@ -90,7 +90,7 @@ impl ApiEventMetric for PaymentMethodResponse {
     fn get_api_event_type(&self) -> Option<ApiEventsType> {
         Some(ApiEventsType::PaymentMethod {
             payment_method_id: self.payment_method_id.clone(),
-            payment_method: Some(self.payment_method),
+            payment_method: self.payment_method,
             payment_method_type: self.payment_method_type,
         })
     }
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 7eb7e213e5b..e9ff261a632 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -22,7 +22,7 @@ use crate::{
 pub struct PaymentMethodCreate {
     /// The type of payment method use for the payment.
     #[schema(value_type = PaymentMethod,example = "card")]
-    pub payment_method: api_enums::PaymentMethod,
+    pub payment_method: Option<api_enums::PaymentMethod>,
 
     /// This is a sub-category of payment method.
     #[schema(value_type = Option<PaymentMethodType>,example = "credit")]
@@ -65,6 +65,16 @@ pub struct PaymentMethodCreate {
     #[cfg(feature = "payouts")]
     #[schema(value_type = Option<Wallet>)]
     pub wallet: Option<payouts::Wallet>,
+
+    /// For Client based calls, SDK will use the client_secret
+    /// in order to call /payment_methods
+    /// Client secret will be generated whenever a new
+    /// payment method is created
+    pub client_secret: Option<String>,
+
+    /// Payment method data to be passed in case of client
+    /// based flow
+    pub payment_method_data: Option<PaymentMethodCreateData>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
@@ -101,6 +111,15 @@ pub struct PaymentMethodUpdate {
     pub client_secret: Option<String>,
 }
 
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "snake_case")]
+#[serde(rename = "payment_method_data")]
+
+pub enum PaymentMethodCreateData {
+    Card(CardDetail),
+}
+
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
 #[serde(deny_unknown_fields)]
 pub struct CardDetail {
@@ -202,7 +221,7 @@ pub struct PaymentMethodResponse {
 
     /// The type of payment method use for the payment.
     #[schema(value_type = PaymentMethod, example = "card")]
-    pub payment_method: api_enums::PaymentMethod,
+    pub payment_method: Option<api_enums::PaymentMethod>,
 
     /// This is a sub-category of payment method.
     #[schema(value_type = Option<PaymentMethodType>, example = "credit")]
@@ -242,6 +261,9 @@ pub struct PaymentMethodResponse {
     #[schema(value_type = Option<PrimitiveDateTime>, example = "2024-02-24T11:04:09.922Z")]
     #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
     pub last_used_at: Option<time::PrimitiveDateTime>,
+
+    /// For Client based calls
+    pub client_secret: Option<String>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 9dbe5f4f4bc..a5caac3fb35 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -1252,6 +1252,8 @@ pub enum PaymentMethodStatus {
     /// Indicates that the payment method is awaiting some data or action before it can be marked
     /// as 'active'.
     Processing,
+    /// Indicates that the payment method is awaiting some data before changing state to active
+    AwaitingData,
 }
 
 impl From<AttemptStatus> for PaymentMethodStatus {
diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs
index dbdbc78aa05..e2807f3b5ea 100644
--- a/crates/diesel_models/src/payment_method.rs
+++ b/crates/diesel_models/src/payment_method.rs
@@ -27,7 +27,7 @@ pub struct PaymentMethod {
     pub direct_debit_token: Option<String>,
     pub created_at: PrimitiveDateTime,
     pub last_modified: PrimitiveDateTime,
-    pub payment_method: storage_enums::PaymentMethod,
+    pub payment_method: Option<storage_enums::PaymentMethod>,
     pub payment_method_type: Option<storage_enums::PaymentMethodType>,
     pub payment_method_issuer: Option<String>,
     pub payment_method_issuer_code: Option<storage_enums::PaymentMethodIssuerCode>,
@@ -39,6 +39,7 @@ pub struct PaymentMethod {
     pub customer_acceptance: Option<pii::SecretSerdeValue>,
     pub status: storage_enums::PaymentMethodStatus,
     pub network_transaction_id: Option<String>,
+    pub client_secret: Option<String>,
 }
 
 #[derive(
@@ -49,7 +50,7 @@ pub struct PaymentMethodNew {
     pub customer_id: String,
     pub merchant_id: String,
     pub payment_method_id: String,
-    pub payment_method: storage_enums::PaymentMethod,
+    pub payment_method: Option<storage_enums::PaymentMethod>,
     pub payment_method_type: Option<storage_enums::PaymentMethodType>,
     pub payment_method_issuer: Option<String>,
     pub payment_method_issuer_code: Option<storage_enums::PaymentMethodIssuerCode>,
@@ -73,6 +74,7 @@ pub struct PaymentMethodNew {
     pub customer_acceptance: Option<pii::SecretSerdeValue>,
     pub status: storage_enums::PaymentMethodStatus,
     pub network_transaction_id: Option<String>,
+    pub client_secret: Option<String>,
 }
 
 impl Default for PaymentMethodNew {
@@ -84,7 +86,7 @@ impl Default for PaymentMethodNew {
             merchant_id: String::default(),
             payment_method_id: String::default(),
             locker_id: Option::default(),
-            payment_method: storage_enums::PaymentMethod::default(),
+            payment_method: Option::default(),
             payment_method_type: Option::default(),
             payment_method_issuer: Option::default(),
             payment_method_issuer_code: Option::default(),
@@ -107,6 +109,7 @@ impl Default for PaymentMethodNew {
             customer_acceptance: Option::default(),
             status: storage_enums::PaymentMethodStatus::Active,
             network_transaction_id: Option::default(),
+            client_secret: Option::default(),
         }
     }
 }
@@ -135,6 +138,12 @@ pub enum PaymentMethodUpdate {
     StatusUpdate {
         status: Option<storage_enums::PaymentMethodStatus>,
     },
+    AdditionalDataUpdate {
+        payment_method_data: Option<Encryption>,
+        status: Option<storage_enums::PaymentMethodStatus>,
+        locker_id: Option<String>,
+        payment_method: Option<storage_enums::PaymentMethod>,
+    },
     ConnectorMandateDetailsUpdate {
         connector_mandate_details: Option<serde_json::Value>,
     },
@@ -150,6 +159,8 @@ pub struct PaymentMethodUpdateInternal {
     last_used_at: Option<PrimitiveDateTime>,
     network_transaction_id: Option<String>,
     status: Option<storage_enums::PaymentMethodStatus>,
+    locker_id: Option<String>,
+    payment_method: Option<storage_enums::PaymentMethod>,
     connector_mandate_details: Option<serde_json::Value>,
 }
 
@@ -168,6 +179,7 @@ impl PaymentMethodUpdateInternal {
             network_transaction_id,
             status,
             connector_mandate_details,
+            ..
         } = self;
 
         PaymentMethod {
@@ -193,6 +205,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 last_used_at: None,
                 network_transaction_id: None,
                 status: None,
+                locker_id: None,
+                payment_method: None,
                 connector_mandate_details: None,
             },
             PaymentMethodUpdate::PaymentMethodDataUpdate {
@@ -203,6 +217,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 last_used_at: None,
                 network_transaction_id: None,
                 status: None,
+                locker_id: None,
+                payment_method: None,
                 connector_mandate_details: None,
             },
             PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self {
@@ -211,6 +227,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 last_used_at: Some(last_used_at),
                 network_transaction_id: None,
                 status: None,
+                locker_id: None,
+                payment_method: None,
                 connector_mandate_details: None,
             },
             PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
@@ -222,6 +240,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 last_used_at: None,
                 network_transaction_id,
                 status,
+                locker_id: None,
+                payment_method: None,
                 connector_mandate_details: None,
             },
             PaymentMethodUpdate::StatusUpdate { status } => Self {
@@ -230,6 +250,23 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 last_used_at: None,
                 network_transaction_id: None,
                 status,
+                locker_id: None,
+                payment_method: None,
+                connector_mandate_details: None,
+            },
+            PaymentMethodUpdate::AdditionalDataUpdate {
+                payment_method_data,
+                status,
+                locker_id,
+                payment_method,
+            } => Self {
+                metadata: None,
+                payment_method_data,
+                last_used_at: None,
+                network_transaction_id: None,
+                status,
+                locker_id,
+                payment_method,
                 connector_mandate_details: None,
             },
             PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
@@ -239,6 +276,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 payment_method_data: None,
                 last_used_at: None,
                 status: None,
+                locker_id: None,
+                payment_method: None,
                 connector_mandate_details,
                 network_transaction_id: None,
             },
@@ -277,6 +316,7 @@ impl From<&PaymentMethodNew> for PaymentMethod {
             customer_acceptance: payment_method_new.customer_acceptance.clone(),
             status: payment_method_new.status,
             network_transaction_id: payment_method_new.network_transaction_id.clone(),
+            client_secret: payment_method_new.client_secret.clone(),
         }
     }
 }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index f4255bfd737..bbe8a8060fc 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -904,7 +904,7 @@ diesel::table! {
         direct_debit_token -> Nullable<Varchar>,
         created_at -> Timestamp,
         last_modified -> Timestamp,
-        payment_method -> Varchar,
+        payment_method -> Nullable<Varchar>,
         #[max_length = 64]
         payment_method_type -> Nullable<Varchar>,
         #[max_length = 128]
@@ -921,6 +921,8 @@ diesel::table! {
         status -> Varchar,
         #[max_length = 255]
         network_transaction_id -> Nullable<Varchar>,
+        #[max_length = 128]
+        client_secret -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs
index 972dde7542a..6e9ad5848c5 100644
--- a/crates/openapi/src/openapi.rs
+++ b/crates/openapi/src/openapi.rs
@@ -206,6 +206,7 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::payment_methods::PaymentMethodUpdate,
         api_models::payment_methods::CustomerDefaultPaymentMethodResponse,
         api_models::payment_methods::CardDetailFromLocker,
+        api_models::payment_methods::PaymentMethodCreateData,
         api_models::payment_methods::CardDetail,
         api_models::payment_methods::CardDetailUpdate,
         api_models::payment_methods::RequestPaymentMethodTypes,
diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs
index c38167f07c4..af97c500f3e 100644
--- a/crates/router/src/core/customers.rs
+++ b/crates/router/src/core/customers.rs
@@ -223,9 +223,10 @@ pub async fn delete_customer(
         )
         .await
     {
+        // check this in review
         Ok(customer_payment_methods) => {
             for pm in customer_payment_methods.into_iter() {
-                if pm.payment_method == enums::PaymentMethod::Card {
+                if pm.payment_method == Some(enums::PaymentMethod::Card) {
                     cards::delete_card_from_locker(
                         &state,
                         &req.customer_id,
diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs
index 53f503e8711..bad3b2184b5 100644
--- a/crates/router/src/core/locker_migration.rs
+++ b/crates/router/src/core/locker_migration.rs
@@ -85,7 +85,7 @@ pub async fn call_to_locker(
 
     for pm in payment_methods
         .into_iter()
-        .filter(|pm| matches!(pm.payment_method, storage_enums::PaymentMethod::Card))
+        .filter(|pm| matches!(pm.payment_method, Some(storage_enums::PaymentMethod::Card)))
     {
         let card = cards::get_card_from_locker(
             state,
@@ -128,6 +128,8 @@ pub async fn call_to_locker(
             metadata: pm.metadata,
             customer_id: Some(pm.customer_id),
             card_network: card.card_brand,
+            client_secret: None,
+            payment_method_data: None,
         };
 
         let add_card_result = cards::add_card_hs(
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index c14042c9e62..25bb869075c 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -85,6 +85,7 @@ pub async fn create_payment_method(
     payment_method_data: Option<Encryption>,
     key_store: &domain::MerchantKeyStore,
     connector_mandate_details: Option<serde_json::Value>,
+    status: Option<enums::PaymentMethodStatus>,
     network_transaction_id: Option<String>,
     storage_scheme: MerchantStorageScheme,
 ) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> {
@@ -98,6 +99,11 @@ pub async fn create_payment_method(
         .await
         .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
 
+    let client_secret = generate_id(
+        consts::ID_LENGTH,
+        format!("{payment_method_id}_secret").as_str(),
+    );
+
     let response = db
         .insert_payment_method(
             storage::PaymentMethodNew {
@@ -113,6 +119,8 @@ pub async fn create_payment_method(
                 payment_method_data,
                 connector_mandate_details,
                 customer_acceptance: customer_acceptance.map(masking::Secret::new),
+                client_secret: Some(client_secret),
+                status: status.unwrap_or(enums::PaymentMethodStatus::Active),
                 network_transaction_id: network_transaction_id.to_owned(),
                 ..storage::PaymentMethodNew::default()
             },
@@ -122,7 +130,7 @@ pub async fn create_payment_method(
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Failed to add payment method in db")?;
 
-    if customer.default_payment_method_id.is_none() {
+    if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
         let _ = set_default_payment_method(
             db,
             merchant_id.to_string(),
@@ -161,6 +169,7 @@ pub fn store_default_payment_method(
         installment_payment_enabled: false, //[#219]
         payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
         last_used_at: Some(common_utils::date_time::now()),
+        client_secret: None,
     };
 
     (payment_method_response, None)
@@ -233,6 +242,253 @@ pub async fn get_or_insert_payment_method(
     }
 }
 
+#[instrument(skip_all)]
+pub async fn get_client_secret_or_add_payment_method(
+    state: routes::AppState,
+    req: api::PaymentMethodCreate,
+    merchant_account: &domain::MerchantAccount,
+    key_store: &domain::MerchantKeyStore,
+) -> errors::RouterResponse<api::PaymentMethodResponse> {
+    let db = &*state.store;
+    let merchant_id = &merchant_account.merchant_id;
+    let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
+
+    #[cfg(not(feature = "payouts"))]
+    let condition = req.card.is_some();
+    #[cfg(feature = "payouts")]
+    let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some();
+
+    if condition {
+        add_payment_method(state, req, merchant_account, key_store).await
+    } else {
+        let payment_method_id = generate_id(consts::ID_LENGTH, "pm");
+
+        let res = create_payment_method(
+            db,
+            &req,
+            customer_id.as_str(),
+            payment_method_id.as_str(),
+            None,
+            merchant_id.as_str(),
+            None,
+            None,
+            None,
+            key_store,
+            None,
+            Some(enums::PaymentMethodStatus::AwaitingData),
+            None,
+            merchant_account.storage_scheme,
+        )
+        .await?;
+
+        Ok(services::api::ApplicationResponse::Json(
+            api::PaymentMethodResponse::foreign_from(res),
+        ))
+    }
+}
+
+#[instrument(skip_all)]
+pub fn authenticate_pm_client_secret_and_check_expiry(
+    req_client_secret: &String,
+    payment_method: &diesel_models::PaymentMethod,
+) -> errors::CustomResult<bool, errors::ApiErrorResponse> {
+    let stored_client_secret = payment_method
+        .client_secret
+        .clone()
+        .get_required_value("client_secret")
+        .change_context(errors::ApiErrorResponse::MissingRequiredField {
+            field_name: "client_secret",
+        })
+        .attach_printable("client secret not found in db")?;
+
+    if req_client_secret != &stored_client_secret {
+        Err((errors::ApiErrorResponse::ClientSecretInvalid).into())
+    } else {
+        let current_timestamp = common_utils::date_time::now();
+        let session_expiry = payment_method
+            .created_at
+            .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY));
+
+        let expired = current_timestamp > session_expiry;
+
+        Ok(expired)
+    }
+}
+
+#[instrument(skip_all)]
+pub async fn add_payment_method_data(
+    state: routes::AppState,
+    req: api::PaymentMethodCreate,
+    merchant_account: domain::MerchantAccount,
+    key_store: domain::MerchantKeyStore,
+    pm_id: String,
+) -> errors::RouterResponse<api::PaymentMethodResponse> {
+    let db = &*state.store;
+
+    let pmd = req
+        .payment_method_data
+        .clone()
+        .get_required_value("payment_method_data")?;
+    req.payment_method.get_required_value("payment_method")?;
+    let client_secret = req
+        .client_secret
+        .clone()
+        .get_required_value("client_secret")?;
+    let payment_method = db
+        .find_payment_method(pm_id.as_str(), merchant_account.storage_scheme)
+        .await
+        .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
+        .attach_printable("Unable to find payment method")?;
+
+    if payment_method.status != enums::PaymentMethodStatus::AwaitingData {
+        return Err((errors::ApiErrorResponse::DuplicatePaymentMethod).into());
+    }
+
+    let customer_id = payment_method.customer_id.clone();
+    let customer = db
+        .find_customer_by_customer_id_merchant_id(
+            customer_id.as_str(),
+            &merchant_account.merchant_id,
+            &key_store,
+            merchant_account.storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
+
+    let client_secret_expired =
+        authenticate_pm_client_secret_and_check_expiry(&client_secret, &payment_method)?;
+
+    if client_secret_expired {
+        return Err((errors::ApiErrorResponse::ClientSecretExpired).into());
+    };
+
+    match pmd {
+        api_models::payment_methods::PaymentMethodCreateData::Card(card) => {
+            helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?;
+            let resp = add_card_to_locker(
+                &state,
+                req.clone(),
+                &card,
+                &customer_id,
+                &merchant_account,
+                None,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError);
+
+            match resp {
+                Ok((mut pm_resp, duplication_check)) => {
+                    if duplication_check.is_some() {
+                        let pm_update = storage::PaymentMethodUpdate::StatusUpdate {
+                            status: Some(enums::PaymentMethodStatus::Inactive),
+                        };
+
+                        db.update_payment_method(
+                            payment_method,
+                            pm_update,
+                            merchant_account.storage_scheme,
+                        )
+                        .await
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to add payment method in db")?;
+
+                        get_or_insert_payment_method(
+                            db,
+                            req.clone(),
+                            &mut pm_resp,
+                            &merchant_account,
+                            &customer_id,
+                            &key_store,
+                        )
+                        .await?;
+
+                        return Ok(services::ApplicationResponse::Json(pm_resp));
+                    } else {
+                        let locker_id = pm_resp.payment_method_id.clone();
+                        pm_resp.payment_method_id = pm_id.clone();
+                        pm_resp.client_secret = Some(client_secret.clone());
+
+                        let card_isin = card.card_number.clone().get_card_isin();
+
+                        let card_info = db
+                            .get_card_info(card_isin.as_str())
+                            .await
+                            .change_context(errors::ApiErrorResponse::InternalServerError)
+                            .attach_printable("Failed to get card info")?;
+
+                        let updated_card = CardDetailsPaymentMethod {
+                            issuer_country: card_info
+                                .as_ref()
+                                .and_then(|ci| ci.card_issuing_country.clone()),
+                            last4_digits: Some(card.card_number.clone().get_last4()),
+                            expiry_month: Some(card.card_exp_month),
+                            expiry_year: Some(card.card_exp_year),
+                            nick_name: card.nick_name,
+                            card_holder_name: card.card_holder_name,
+                            card_network: card_info.as_ref().and_then(|ci| ci.card_network.clone()),
+                            card_isin: Some(card_isin),
+                            card_issuer: card_info.as_ref().and_then(|ci| ci.card_issuer.clone()),
+                            card_type: card_info.as_ref().and_then(|ci| ci.card_type.clone()),
+                            saved_to_locker: true,
+                        };
+
+                        let updated_pmd = Some(PaymentMethodsData::Card(updated_card));
+                        let pm_data_encrypted =
+                            create_encrypted_payment_method_data(&key_store, updated_pmd).await;
+
+                        let pm_update = storage::PaymentMethodUpdate::AdditionalDataUpdate {
+                            payment_method_data: pm_data_encrypted,
+                            status: Some(enums::PaymentMethodStatus::Active),
+                            locker_id: Some(locker_id),
+                            payment_method: req.payment_method,
+                        };
+
+                        db.update_payment_method(
+                            payment_method,
+                            pm_update,
+                            merchant_account.storage_scheme,
+                        )
+                        .await
+                        .change_context(errors::ApiErrorResponse::InternalServerError)
+                        .attach_printable("Failed to add payment method in db")?;
+
+                        if customer.default_payment_method_id.is_none() {
+                            let _ = set_default_payment_method(
+                                db,
+                                merchant_account.merchant_id.clone(),
+                                key_store.clone(),
+                                customer_id.as_str(),
+                               pm_id,
+                               merchant_account.storage_scheme,
+                            )
+                            .await
+                            .map_err(|err| logger::error!(error=?err,"Failed to set the payment method as default"));
+                        }
+
+                        return Ok(services::ApplicationResponse::Json(pm_resp));
+                    }
+                }
+                Err(e) => {
+                    let pm_update = storage::PaymentMethodUpdate::StatusUpdate {
+                        status: Some(enums::PaymentMethodStatus::Inactive),
+                    };
+
+                    db.update_payment_method(
+                        payment_method,
+                        pm_update,
+                        merchant_account.storage_scheme,
+                    )
+                    .await
+                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                    .attach_printable("Failed to update payment method in db")?;
+
+                    return Err(e.attach_printable("Failed to add card to locker"));
+                }
+            }
+        }
+    }
+}
+
 #[instrument(skip_all)]
 pub async fn add_payment_method(
     state: routes::AppState,
@@ -244,8 +500,9 @@ pub async fn add_payment_method(
     let db = &*state.store;
     let merchant_id = &merchant_account.merchant_id;
     let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
+    let payment_method = req.payment_method.get_required_value("payment_method")?;
 
-    let response = match req.payment_method {
+    let response = match payment_method {
         #[cfg(feature = "payouts")]
         api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() {
             Some(bank) => add_bank_to_locker(
@@ -402,8 +659,8 @@ pub async fn add_payment_method(
         None => {
             let pm_metadata = resp.metadata.as_ref().map(|data| data.peek());
 
-            let locker_id = if resp.payment_method == api_enums::PaymentMethod::Card
-                || resp.payment_method == api_enums::PaymentMethod::BankTransfer
+            let locker_id = if resp.payment_method == Some(api_enums::PaymentMethod::Card)
+                || resp.payment_method == Some(api_enums::PaymentMethod::BankTransfer)
             {
                 Some(resp.payment_method_id)
             } else {
@@ -463,6 +720,7 @@ pub async fn insert_payment_method(
         pm_data_encrypted,
         key_store,
         connector_mandate_details,
+        None,
         network_transaction_id,
         storage_scheme,
     )
@@ -554,6 +812,8 @@ pub async fn update_customer_payment_method(
                 wallet: req.wallet,
                 metadata: req.metadata,
                 customer_id: Some(pm.customer_id.clone()),
+                client_secret: None,
+                payment_method_data: None,
                 card_network: req
                     .card_network
                     .as_ref()
@@ -641,6 +901,7 @@ pub async fn update_customer_payment_method(
                 installment_payment_enabled: false,
                 payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
                 last_used_at: Some(common_utils::date_time::now()),
+                client_secret: None,
             }
         };
 
@@ -1602,7 +1863,7 @@ pub async fn list_payment_methods(
                         let customer_wallet_pm = customer_payment_methods
                             .iter()
                             .filter(|cust_pm| {
-                                cust_pm.payment_method == enums::PaymentMethod::Wallet
+                                cust_pm.payment_method == Some(enums::PaymentMethod::Wallet)
                             })
                             .collect::<Vec<_>>();
 
@@ -3029,7 +3290,9 @@ pub async fn list_customer_payment_method(
     for pm in resp.into_iter() {
         let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token");
 
-        let payment_method_retrieval_context = match pm.payment_method {
+        let payment_method = pm.payment_method.get_required_value("payment_method")?;
+
+        let payment_method_retrieval_context = match payment_method {
             enums::PaymentMethod::Card => {
                 let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?;
 
@@ -3111,7 +3374,7 @@ pub async fn list_customer_payment_method(
         };
 
         // Retrieve the masked bank details to be sent as a response
-        let bank_details = if pm.payment_method == enums::PaymentMethod::BankDebit {
+        let bank_details = if payment_method == enums::PaymentMethod::BankDebit {
             get_masked_bank_details(&pm, key)
                 .await
                 .unwrap_or_else(|err| {
@@ -3128,7 +3391,7 @@ pub async fn list_customer_payment_method(
             payment_token: parent_payment_method_token.to_owned(),
             payment_method_id: pm.payment_method_id.clone(),
             customer_id: pm.customer_id,
-            payment_method: pm.payment_method,
+            payment_method,
             payment_method_type: pm.payment_method_type,
             payment_method_issuer: pm.payment_method_issuer,
             card: payment_method_retrieval_context.card_details,
@@ -3432,9 +3695,14 @@ async fn get_bank_account_connector_details(
                     .get_required_value("payment_method_type")
                     .attach_printable("PaymentMethodType not found")?;
 
+                let pm = pm
+                    .payment_method
+                    .get_required_value("payment_method")
+                    .attach_printable("PaymentMethod not found")?;
+
                 let token_data = BankAccountTokenData {
                     payment_method_type: pm_type,
-                    payment_method: pm.payment_method,
+                    payment_method: pm,
                     connector_details: connector_details.clone(),
                 };
 
@@ -3467,6 +3735,9 @@ pub async fn set_default_payment_method(
         .find_payment_method(&payment_method_id, storage_scheme)
         .await
         .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
+    let pm = payment_method
+        .payment_method
+        .get_required_value("payment_method")?;
 
     utils::when(
         payment_method.customer_id != customer_id || payment_method.merchant_id != merchant_id,
@@ -3505,11 +3776,12 @@ pub async fn set_default_payment_method(
         .await
         .change_context(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Failed to update the default payment method id for the customer")?;
+
     let resp = CustomerDefaultPaymentMethodResponse {
         default_payment_method_id: updated_customer_details.default_payment_method_id,
         customer_id,
         payment_method_type: payment_method.payment_method_type,
-        payment_method: payment_method.payment_method,
+        payment_method: pm,
     };
 
     Ok(services::ApplicationResponse::Json(resp))
@@ -3689,7 +3961,7 @@ pub async fn retrieve_payment_method(
         .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
 
     let key = key_store.key.peek();
-    let card = if pm.payment_method == enums::PaymentMethod::Card {
+    let card = if pm.payment_method == Some(enums::PaymentMethod::Card) {
         let card_detail = if state.conf.locker.locker_enabled {
             let card = get_card_from_locker(
                 &state,
@@ -3726,6 +3998,7 @@ pub async fn retrieve_payment_method(
             installment_payment_enabled: false,
             payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
             last_used_at: Some(pm.last_used_at),
+            client_secret: pm.client_secret,
         },
     ))
 }
@@ -3773,7 +4046,7 @@ pub async fn delete_payment_method(
         || Err(errors::ApiErrorResponse::PaymentMethodDeleteFailed),
     )?;
 
-    if key.payment_method == enums::PaymentMethod::Card {
+    if key.payment_method == Some(enums::PaymentMethod::Card) {
         let response = delete_card_from_locker(
             &state,
             &key.customer_id,
diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs
index a9671cc6783..749508c9740 100644
--- a/crates/router/src/core/payment_methods/transformers.rs
+++ b/crates/router/src/core/payment_methods/transformers.rs
@@ -328,6 +328,7 @@ pub fn mk_add_bank_response_hs(
         installment_payment_enabled: false, // #[#256]
         payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
         last_used_at: Some(common_utils::date_time::now()),
+        client_secret: None,
     }
 }
 
@@ -373,6 +374,7 @@ pub fn mk_add_card_response_hs(
         installment_payment_enabled: false, // #[#256]
         payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
         last_used_at: Some(common_utils::date_time::now()), // [#256]
+        client_secret: None,
     }
 }
 
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index a3292e3cd69..b9a8795aa54 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -3366,7 +3366,7 @@ pub fn is_network_transaction_id_flow(
         .connector_list;
 
     pg_agnostic == "true"
-        && payment_method_info.payment_method == storage_enums::PaymentMethod::Card
+        && payment_method_info.payment_method == Some(storage_enums::PaymentMethod::Card)
         && ntid_supported_connectors.contains(&connector)
         && payment_method_info.network_transaction_id.is_some()
 }
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 48a6d411780..de2fdd99b7b 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -486,7 +486,7 @@ pub async fn get_token_pm_type_mandate_details(
 
                         (
                             None,
-                            Some(payment_method_info.payment_method),
+                            payment_method_info.payment_method,
                             payment_method_info.payment_method_type,
                             None,
                             None,
@@ -635,7 +635,7 @@ pub async fn get_token_for_recurring_mandate(
         merchant_connector_id: mandate.merchant_connector_id,
     };
 
-    if let diesel_models::enums::PaymentMethod::Card = payment_method.payment_method {
+    if let Some(diesel_models::enums::PaymentMethod::Card) = payment_method.payment_method {
         if state.conf.locker.locker_enabled {
             let _ = cards::get_lookup_key_from_locker(
                 state,
@@ -648,11 +648,14 @@ pub async fn get_token_for_recurring_mandate(
 
         if let Some(payment_method_from_request) = req.payment_method {
             let pm: storage_enums::PaymentMethod = payment_method_from_request;
-            if pm != payment_method.payment_method {
+            if payment_method
+                .payment_method
+                .is_some_and(|payment_method| payment_method != pm)
+            {
                 Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                     message:
                         "payment method in request does not match previously provided payment \
-                        method information"
+                            method information"
                             .into()
                 }))?
             }
@@ -660,7 +663,7 @@ pub async fn get_token_for_recurring_mandate(
 
         Ok(MandateGenericData {
             token: Some(token),
-            payment_method: Some(payment_method.payment_method),
+            payment_method: payment_method.payment_method,
             recurring_mandate_payment_data: Some(payments::RecurringMandatePaymentData {
                 payment_method_type,
                 original_payment_authorized_amount,
@@ -674,7 +677,7 @@ pub async fn get_token_for_recurring_mandate(
     } else {
         Ok(MandateGenericData {
             token: None,
-            payment_method: Some(payment_method.payment_method),
+            payment_method: payment_method.payment_method,
             recurring_mandate_payment_data: Some(payments::RecurringMandatePaymentData {
                 payment_method_type,
                 original_payment_authorized_amount,
@@ -1264,7 +1267,7 @@ pub(crate) async fn get_payment_method_create_request(
                     };
                     let customer_id = customer.customer_id.clone();
                     let payment_method_request = api::PaymentMethodCreate {
-                        payment_method,
+                        payment_method: Some(payment_method),
                         payment_method_type,
                         payment_method_issuer: card.card_issuer.clone(),
                         payment_method_issuer_code: None,
@@ -1279,12 +1282,14 @@ pub(crate) async fn get_payment_method_create_request(
                             .card_network
                             .as_ref()
                             .map(|card_network| card_network.to_string()),
+                        client_secret: None,
+                        payment_method_data: None,
                     };
                     Ok(payment_method_request)
                 }
                 _ => {
                     let payment_method_request = api::PaymentMethodCreate {
-                        payment_method,
+                        payment_method: Some(payment_method),
                         payment_method_type,
                         payment_method_issuer: None,
                         payment_method_issuer_code: None,
@@ -1296,6 +1301,8 @@ pub(crate) async fn get_payment_method_create_request(
                         metadata: None,
                         customer_id: Some(customer.customer_id.to_owned()),
                         card_network: None,
+                        client_secret: None,
+                        payment_method_data: None,
                     };
 
                     Ok(payment_method_request)
@@ -1900,7 +1907,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>(
 
     if payment_data.token_data.is_none() {
         if let Some(payment_method_info) = &payment_data.payment_method_info {
-            if payment_method_info.payment_method == storage_enums::PaymentMethod::Card {
+            if payment_method_info.payment_method == Some(storage_enums::PaymentMethod::Card) {
                 payment_data.token_data =
                     Some(storage::PaymentTokenData::PermanentCard(CardTokenData {
                         payment_method_id: Some(payment_method_info.payment_method_id.clone()),
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 62acfb449eb..5bf6b2e7013 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -306,6 +306,7 @@ where
                                             pm_data_encrypted,
                                             key_store,
                                             connector_mandate_details,
+                                            None,
                                             network_transaction_id,
                                             merchant_account.storage_scheme,
                                         )
@@ -503,11 +504,13 @@ where
                     None => {
                         let pm_metadata = create_payment_method_metadata(None, connector_token)?;
 
-                        locker_id = if resp.payment_method == PaymentMethod::Card {
-                            Some(resp.payment_method_id)
-                        } else {
-                            None
-                        };
+                        locker_id = resp.payment_method.and_then(|pm| {
+                            if pm == PaymentMethod::Card {
+                                Some(resp.payment_method_id)
+                            } else {
+                                None
+                            }
+                        });
 
                         resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
                         payment_methods::cards::create_payment_method(
@@ -522,6 +525,7 @@ where
                             pm_data_encrypted,
                             key_store,
                             connector_mandate_details,
+                            None,
                             network_transaction_id,
                             merchant_account.storage_scheme,
                         )
@@ -598,6 +602,7 @@ async fn skip_saving_card_in_locker(
                 #[cfg(feature = "payouts")]
                 bank_transfer: None,
                 last_used_at: Some(common_utils::date_time::now()),
+                client_secret: None,
             };
 
             Ok((pm_resp, None))
@@ -619,6 +624,7 @@ async fn skip_saving_card_in_locker(
                 #[cfg(feature = "payouts")]
                 bank_transfer: None,
                 last_used_at: Some(common_utils::date_time::now()),
+                client_secret: None,
             };
             Ok((payment_method_response, None))
         }
@@ -668,6 +674,7 @@ pub async fn save_in_locker(
                 installment_payment_enabled: false, //[#219]
                 payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219]
                 last_used_at: Some(common_utils::date_time::now()),
+                client_secret: None,
             };
             Ok((payment_method_response, None))
         }
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index 8280ddb6c2f..e521fac9a80 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -376,9 +376,9 @@ pub async fn save_payout_data_to_locker(
                 .map(|c| c.card_number.clone().get_card_isin());
 
             let mut payment_method = api::PaymentMethodCreate {
-                payment_method: api_enums::PaymentMethod::foreign_from(
+                payment_method: Some(api_enums::PaymentMethod::foreign_from(
                     payout_method_data.to_owned(),
-                ),
+                )),
                 payment_method_type: Some(payment_method_type),
                 payment_method_issuer: None,
                 payment_method_issuer_code: None,
@@ -388,6 +388,8 @@ pub async fn save_payout_data_to_locker(
                 metadata: None,
                 customer_id: Some(payout_attempt.customer_id.to_owned()),
                 card_network: None,
+                client_secret: None,
+                payment_method_data: None,
             };
 
             let pm_data = card_isin
@@ -455,9 +457,9 @@ pub async fn save_payout_data_to_locker(
             (
                 None,
                 api::PaymentMethodCreate {
-                    payment_method: api_enums::PaymentMethod::foreign_from(
+                    payment_method: Some(api_enums::PaymentMethod::foreign_from(
                         payout_method_data.to_owned(),
-                    ),
+                    )),
                     payment_method_type: Some(payment_method_type),
                     payment_method_issuer: None,
                     payment_method_issuer_code: None,
@@ -467,6 +469,8 @@ pub async fn save_payout_data_to_locker(
                     metadata: None,
                     customer_id: Some(payout_attempt.customer_id.to_owned()),
                     card_network: None,
+                    client_secret: None,
+                    payment_method_data: None,
                 },
             )
         };
@@ -487,6 +491,7 @@ pub async fn save_payout_data_to_locker(
             key_store,
             None,
             None,
+            None,
             merchant_account.storage_scheme,
         )
         .await?;
diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs
index 09b8ad9f868..6d5ae7a08c3 100644
--- a/crates/router/src/core/pm_auth.rs
+++ b/crates/router/src/core/pm_auth.rs
@@ -311,7 +311,7 @@ async fn store_bank_details_in_payment_methods(
     > = HashMap::new();
 
     for pm in payment_methods {
-        if pm.payment_method == enums::PaymentMethod::BankDebit {
+        if pm.payment_method == Some(enums::PaymentMethod::BankDebit) {
             let bank_details_pm_data = decrypt::<serde_json::Value, masking::WithType>(
                 pm.payment_method_data.clone(),
                 key,
@@ -442,7 +442,7 @@ async fn store_bank_details_in_payment_methods(
                 customer_id: customer_id.clone(),
                 merchant_id: merchant_account.merchant_id.clone(),
                 payment_method_id: pm_id,
-                payment_method: enums::PaymentMethod::BankDebit,
+                payment_method: Some(enums::PaymentMethod::BankDebit),
                 payment_method_type: Some(creds.payment_method_type),
                 payment_method_issuer: None,
                 scheme: None,
diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs
index 72676ca81ee..528861a70aa 100644
--- a/crates/router/src/db/payment_method.rs
+++ b/crates/router/src/db/payment_method.rs
@@ -660,6 +660,7 @@ impl PaymentMethodInterface for MockDb {
             connector_mandate_details: payment_method_new.connector_mandate_details,
             customer_acceptance: payment_method_new.customer_acceptance,
             status: payment_method_new.status,
+            client_secret: payment_method_new.client_secret,
             network_transaction_id: payment_method_new.network_transaction_id,
         };
         payment_methods.push(payment_method.clone());
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 2771ee42947..ff660dbf7b7 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -782,6 +782,10 @@ impl PaymentMethods {
                     web::resource("/{payment_method_id}/update")
                         .route(web::post().to(payment_method_update_api)),
                 )
+                .service(
+                    web::resource("/{payment_method_id}/save")
+                        .route(web::post().to(save_payment_method_api)),
+                )
                 .service(
                     web::resource("/auth/link").route(web::post().to(pm_auth::link_token_create)),
                 )
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 8e29b28fb66..aec3a5f2c1f 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -99,7 +99,8 @@ impl From<Flow> for ApiIdentifier {
             | Flow::PaymentMethodsDelete
             | Flow::ValidatePaymentMethod
             | Flow::ListCountriesCurrencies
-            | Flow::DefaultPaymentMethodsSet => Self::PaymentMethods,
+            | Flow::DefaultPaymentMethodsSet
+            | Flow::PaymentMethodSave => Self::PaymentMethods,
 
             Flow::PmAuthLinkTokenCreate | Flow::PmAuthExchangeToken => Self::PaymentMethodAuth,
 
diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs
index a5ffcdabe33..e9dacd9f84f 100644
--- a/crates/router/src/routes/payment_methods.rs
+++ b/crates/router/src/routes/payment_methods.rs
@@ -23,13 +23,14 @@ pub async fn create_payment_method_api(
     json_payload: web::Json<payment_methods::PaymentMethodCreate>,
 ) -> HttpResponse {
     let flow = Flow::PaymentMethodsCreate;
+
     Box::pin(api::server_wrap(
         flow,
         state,
         &req,
         json_payload.into_inner(),
         |state, auth, req, _| async move {
-            Box::pin(cards::add_payment_method(
+            Box::pin(cards::get_client_secret_or_add_payment_method(
                 state,
                 req,
                 &auth.merchant_account,
@@ -43,6 +44,41 @@ pub async fn create_payment_method_api(
     .await
 }
 
+#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSave))]
+pub async fn save_payment_method_api(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<payment_methods::PaymentMethodCreate>,
+    path: web::Path<String>,
+) -> HttpResponse {
+    let flow = Flow::PaymentMethodSave;
+    let payload = json_payload.into_inner();
+    let pm_id = path.into_inner();
+    let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) {
+        Ok((auth, _auth_flow)) => (auth, _auth_flow),
+        Err(e) => return api::log_and_return_error_response(e),
+    };
+
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        payload,
+        |state, auth, req, _| {
+            Box::pin(cards::add_payment_method_data(
+                state,
+                req,
+                auth.merchant_account,
+                auth.key_store,
+                pm_id.clone(),
+            ))
+        },
+        &*auth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsList))]
 pub async fn list_payment_method_api(
     state: web::Data<AppState>,
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index b04d3f3b6f6..5203b20c55b 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -1,5 +1,8 @@
 use actix_web::http::header::HeaderMap;
-use api_models::{payment_methods::PaymentMethodListRequest, payments};
+use api_models::{
+    payment_methods::{PaymentMethodCreate, PaymentMethodListRequest},
+    payments,
+};
 use async_trait::async_trait;
 use common_utils::date_time;
 use error_stack::{report, ResultExt};
@@ -832,6 +835,12 @@ impl ClientSecretFetch for PaymentMethodListRequest {
     }
 }
 
+impl ClientSecretFetch for PaymentMethodCreate {
+    fn get_client_secret(&self) -> Option<&String> {
+        self.client_secret.as_ref()
+    }
+}
+
 impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest {
     fn get_client_secret(&self) -> Option<&String> {
         self.client_secret.as_ref()
diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs
index dda6dc0cfcc..0e80fd224da 100644
--- a/crates/router/src/types/api/mandates.rs
+++ b/crates/router/src/types/api/mandates.rs
@@ -1,5 +1,6 @@
 use api_models::mandates;
 pub use api_models::mandates::{MandateId, MandateResponse, MandateRevokedResponse};
+use common_utils::ext_traits::OptionExt;
 use error_stack::ResultExt;
 use masking::PeekInterface;
 use serde::{Deserialize, Serialize};
@@ -46,7 +47,13 @@ impl MandateResponseExt for MandateResponse {
             .await
             .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
 
-        let card = if payment_method.payment_method == storage_enums::PaymentMethod::Card {
+        let pm = payment_method
+            .payment_method
+            .get_required_value("payment_method")
+            .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
+            .attach_printable("payment_method not found")?;
+
+        let card = if pm == storage_enums::PaymentMethod::Card {
             // if locker is disabled , decrypt the payment method data
             let card_details = if state.conf.locker.locker_enabled {
                 let card = payment_methods::cards::get_card_from_locker(
@@ -95,7 +102,7 @@ impl MandateResponseExt for MandateResponse {
             }),
             card,
             status: mandate.mandate_status,
-            payment_method: payment_method.payment_method.to_string(),
+            payment_method: pm.to_string(),
             payment_method_type,
             payment_method_id: mandate.payment_method_id,
         })
diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs
index 13c9ec61f2a..6bf100e4d67 100644
--- a/crates/router/src/types/api/payment_methods.rs
+++ b/crates/router/src/types/api/payment_methods.rs
@@ -2,8 +2,8 @@ pub use api_models::payment_methods::{
     CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod,
     CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest,
     GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest,
-    PaymentMethodCreate, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList,
-    PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse,
+    PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId,
+    PaymentMethodList, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse,
     PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest,
     TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2,
 };
@@ -21,15 +21,14 @@ pub(crate) trait PaymentMethodCreateExt {
 // convert self.payment_method_type to payment_method and compare it against self.payment_method
 impl PaymentMethodCreateExt for PaymentMethodCreate {
     fn validate(&self) -> RouterResult<()> {
-        if let Some(payment_method_type) = self.payment_method_type {
-            if !validate_payment_method_type_against_payment_method(
-                self.payment_method,
-                payment_method_type,
-            ) {
-                return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
-                    message: "Invalid 'payment_method_type' provided".to_string()
-                })
-                .attach_printable("Invalid payment method type"));
+        if let Some(pm) = self.payment_method {
+            if let Some(payment_method_type) = self.payment_method_type {
+                if !validate_payment_method_type_against_payment_method(pm, payment_method_type) {
+                    return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
+                        message: "Invalid 'payment_method_type' provided".to_string()
+                    })
+                    .attach_printable("Invalid payment method type"));
+                }
             }
         }
         Ok(())
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index d0cd2b7a11d..6ecd53afaed 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -1,6 +1,9 @@
 // use actix_web::HttpMessage;
 use actix_web::http::header::HeaderMap;
-use api_models::{enums as api_enums, gsm as gsm_api_types, payments, routing::ConnectorSelection};
+use api_models::{
+    enums as api_enums, gsm as gsm_api_types, payment_methods, payments,
+    routing::ConnectorSelection,
+};
 use common_utils::{
     consts::X_HS_LATENCY,
     crypto::Encryptable,
@@ -71,6 +74,28 @@ impl ForeignFrom<api_models::refunds::RefundType> for storage_enums::RefundType
     }
 }
 
+impl ForeignFrom<diesel_models::PaymentMethod> for payment_methods::PaymentMethodResponse {
+    fn foreign_from(item: diesel_models::PaymentMethod) -> Self {
+        Self {
+            merchant_id: item.merchant_id,
+            customer_id: Some(item.customer_id),
+            payment_method_id: item.payment_method_id,
+            payment_method: item.payment_method,
+            payment_method_type: item.payment_method_type,
+            card: None,
+            recurring_enabled: false,
+            installment_payment_enabled: false,
+            payment_experience: None,
+            metadata: item.metadata,
+            created: Some(item.created_at),
+            #[cfg(feature = "payouts")]
+            bank_transfer: None,
+            last_used_at: None,
+            client_secret: item.client_secret,
+        }
+    }
+}
+
 impl ForeignFrom<storage_enums::AttemptStatus> for storage_enums::IntentStatus {
     fn foreign_from(s: storage_enums::AttemptStatus) -> Self {
         match s {
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 991db38635b..0ec6894debe 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -113,6 +113,8 @@ pub enum Flow {
     PaymentMethodsCreate,
     /// Payment methods list flow.
     PaymentMethodsList,
+    /// Payment method save flow
+    PaymentMethodSave,
     /// Customer payment methods list flow.
     CustomerPaymentMethodsList,
     /// List Customers for a merchant
diff --git a/migrations/2024-03-15-133951_pm-client-secret/down.sql b/migrations/2024-03-15-133951_pm-client-secret/down.sql
new file mode 100644
index 00000000000..90b2d0dba14
--- /dev/null
+++ b/migrations/2024-03-15-133951_pm-client-secret/down.sql
@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE payment_methods DROP COLUMN IF EXISTS client_secret;
+ALTER TABLE payment_methods ALTER COLUMN payment_method SET NOT NULL;
\ No newline at end of file
diff --git a/migrations/2024-03-15-133951_pm-client-secret/up.sql b/migrations/2024-03-15-133951_pm-client-secret/up.sql
new file mode 100644
index 00000000000..e933d3ff382
--- /dev/null
+++ b/migrations/2024-03-15-133951_pm-client-secret/up.sql
@@ -0,0 +1,3 @@
+-- Your SQL goes here
+ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS client_secret VARCHAR(128) DEFAULT NULL;
+ALTER TABLE payment_methods ALTER COLUMN payment_method DROP NOT NULL;
\ No newline at end of file
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index f646ba8b8d9..0bc0059e949 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -12498,10 +12498,38 @@
               }
             ],
             "nullable": true
+          },
+          "client_secret": {
+            "type": "string",
+            "description": "For Client based calls, SDK will use the client_secret\nin order to call /payment_methods\nClient secret will be generated whenever a new\npayment method is created",
+            "nullable": true
+          },
+          "payment_method_data": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/PaymentMethodCreateData"
+              }
+            ],
+            "nullable": true
           }
         },
         "additionalProperties": false
       },
+      "PaymentMethodCreateData": {
+        "oneOf": [
+          {
+            "type": "object",
+            "required": [
+              "card"
+            ],
+            "properties": {
+              "card": {
+                "$ref": "#/components/schemas/CardDetail"
+              }
+            }
+          }
+        ]
+      },
       "PaymentMethodData": {
         "oneOf": [
           {
@@ -12887,6 +12915,11 @@
             "format": "date-time",
             "example": "2024-02-24T11:04:09.922Z",
             "nullable": true
+          },
+          "client_secret": {
+            "type": "string",
+            "description": "For Client based calls",
+            "nullable": true
           }
         }
       },
@@ -12895,7 +12928,8 @@
         "enum": [
           "active",
           "inactive",
-          "processing"
+          "processing",
+          "awaiting_data"
         ]
       },
       "PaymentMethodType": {

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

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

## Description
<!-- Describe your changes in detail -->
**Vaulting without authentication in a non-payments context.**
This PR enables - 
-  client_secret auth for `/payment_methods` (Generation and storage of client_secret)
- Vaulting of payment_method with client_secret auth using `/payment_methods/:pm_id/save`
- Making payment_method a nullable in schema


### Additional Changes

- [x] 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)?
-->
1. Create a customer
```
curl --location --request POST 'http://localhost:8080/customers' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_y5XDvbHKvRI6K2dAhTgXvNgQykJiqLXeYtSysCQzPGBGz3RZoUPjPcfD1LW8teYO' \
--data-raw '{
    "email": "guest@example.com",
    "name": "John Doe1",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": {
        "line1": "1467",
        "line2": "Harrison Street",
        "line3": "Harrison Street",
        "city": "San Fransico",
        "state": "California",
        "zip": "94122",
        "country": "US",
        "first_name": "John",
        "last_name": "Doe"
    },
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
2. Create a payment method
```
curl --location --request POST 'http://localhost:8080/payment_methods' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_63yd7Y0i2CvRcJYKfXcIE8wAva0b5YMtKHr6oTpcnggVmWqsK0Q6CvoKB1WiXoTz' \
--data-raw '{
    "customer_id": "cus_RHnmMuG8mKhICwigyUeP"
}'
```
Response - 
```
{
    "merchant_id": "sarthak",
    "customer_id": "cus_RHnmMuG8mKhICwigyUeP",
    "payment_method_id": "pm_DC74n7ocq8SKGvNVai0M",
    "payment_method": null,
    "payment_method_type": null,
    "card": null,
    "recurring_enabled": false,
    "installment_payment_enabled": false,
    "payment_experience": null,
    "metadata": null,
    "created": null,
    "last_used_at": null,
    "client_secret": "pm_DC74n7ocq8SKGvNVai0M_secret_9wUZYKaUcORN13IdGCOo"
}
```
DB entry - 
<img width="1512" alt="image" src="https://github.com/juspay/hyperswitch/assets/76486416/97359e0a-0979-4c98-844e-cb3887adfaf0">


3. Save the payment method with client_secret
```
curl --location --request POST 'http://localhost:8080/payment_methods/pm_DC74n7ocq8SKGvNVai0M/save' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: pk_dev_660e47ec672f43849d6b0403fcaf76ac' \
--data-raw '{
    "payment_method": "card",
    "client_secret": "pm_DC74n7ocq8SKGvNVai0M_secret_9wUZYKaUcORN13IdGCOo",
    "customer_id": "cus_RHnmMuG8mKhICwigyUeP",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "10",
            "card_exp_year": "2026",
            "card_holder_name": "joseph"
        }
    }
}'
```
Response - 
```
{
    "merchant_id": "sarthak",
    "customer_id": "cus_RHnmMuG8mKhICwigyUeP",
    "payment_method_id": "pm_DC74n7ocq8SKGvNVai0M",
    "payment_method": "card",
    "payment_method_type": null,
    "card": {
        "scheme": null,
        "issuer_country": null,
        "last4_digits": "4242",
        "expiry_month": "10",
        "expiry_year": "2026",
        "card_token": null,
        "card_holder_name": "joseph",
        "card_fingerprint": null,
        "nick_name": null,
        "card_network": null,
        "card_isin": "424242",
        "card_issuer": null,
        "card_type": null,
        "saved_to_locker": true
    },
    "recurring_enabled": false,
    "installment_payment_enabled": false,
    "payment_experience": [
        "redirect_to_url"
    ],
    "metadata": null,
    "created": "2024-04-03T13:02:00.926Z",
    "last_used_at": "2024-04-03T13:02:00.926Z",
    "client_secret": "pm_DC74n7ocq8SKGvNVai0M_secret_9wUZYKaUcORN13IdGCOo"
}
```
DB entry - 
<img width="1512" alt="image" src="https://github.com/juspay/hyperswitch/assets/76486416/e4eea6ea-2223-4ef9-a631-d316c8777fbe">




## 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
