Bug: [FEATURE] Update card_details for an existing mandate



### Feature Description

There is an existing mandate, with current card details and the Customer for some reason wants to update his card details for that particular mandate.

### Possible Implementation

The possible implementation for this would is, 

- while making a `/payments`  call create the `Mandate_data` object with an `update_existing_mandate` field which would have the `current/existing mandate`.
- if in the request we get the `update_existing_mandate` as some valid mandate for that particular merchant, we create a new Mandate on the connector's end and if that creation is a success, we revoke the Previous Mandate with the Previous Card Details on the connectors end
- And then we'll map the `new_connector_mandate_id` with our existing `mandate_id` and update the following in the db
- Create a new list that contains the `old_connector_mandate_id' for keeping a reference of older mandates that were revoked or need to be revoked




### TBDs

- [ ] What do we do when there’s no revoke call supported by connector? 

>  We add the task in process tracker

- [ ] What if the revoke retries call fails even after adding the task to the process tracker?

- [ ] How do we deal if there's a Connector Error while creating a mandate?

- [ ] https://github.com/juspay/hyperswitch/issues/3465  


- [ ] https://github.com/juspay/hyperswitch/pull/3452

- [ ] https://github.com/juspay/hyperswitch/issues/3464

- [ ] https://github.com/juspay/hyperswitch/issues/3538


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

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

### Have you read the Contributing Guidelines?

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

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index 57c66dc5a7b..8c27f498d7a 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -617,10 +617,18 @@ pub enum MandateReferenceId {
     NetworkMandateId(String), // network_txns_id send by Issuer to connector, Used for PG agnostic mandate txns
 }
 
-#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Eq, PartialEq)]
 pub struct ConnectorMandateReferenceId {
     pub connector_mandate_id: Option<String>,
     pub payment_method_id: Option<String>,
+    pub update_history: Option<Vec<UpdateHistory>>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
+pub struct UpdateHistory {
+    pub connector_mandate_id: Option<String>,
+    pub payment_method_id: String,
+    pub original_payment_id: Option<String>,
 }
 
 impl MandateIds {
@@ -637,6 +645,8 @@ impl MandateIds {
 #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
 #[serde(deny_unknown_fields)]
 pub struct MandateData {
+    /// A way to update the mandate's payment method details
+    pub update_mandate_id: Option<String>,
     /// A concent from the customer to store the payment method
     pub customer_acceptance: Option<CustomerAcceptance>,
     /// A way to select the type of mandate used
diff --git a/crates/data_models/src/mandates.rs b/crates/data_models/src/mandates.rs
index afdcda3a40e..319a78cf661 100644
--- a/crates/data_models/src/mandates.rs
+++ b/crates/data_models/src/mandates.rs
@@ -9,6 +9,13 @@ use error_stack::{IntoReport, ResultExt};
 use masking::{PeekInterface, Secret};
 use time::PrimitiveDateTime;
 
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub struct MandateDetails {
+    pub update_mandate_id: Option<String>,
+    pub mandate_type: Option<MandateDataType>,
+}
+
 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
 #[serde(rename_all = "snake_case")]
 pub enum MandateDataType {
@@ -16,6 +23,13 @@ pub enum MandateDataType {
     MultiUse(Option<MandateAmountData>),
 }
 
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+#[serde(untagged)]
+pub enum MandateTypeDetails {
+    MandateType(MandateDataType),
+    MandateDetails(MandateDetails),
+}
 #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
 pub struct MandateAmountData {
     pub amount: i64,
@@ -29,6 +43,8 @@ pub struct MandateAmountData {
 // information about creating mandates
 #[derive(Default, Eq, PartialEq, Debug, Clone)]
 pub struct MandateData {
+    /// A way to update the mandate's payment method details
+    pub update_mandate_id: Option<String>,
     /// A concent from the customer to store the payment method
     pub customer_acceptance: Option<CustomerAcceptance>,
     /// A way to select the type of mandate used
@@ -90,6 +106,7 @@ impl From<ApiMandateData> for MandateData {
         Self {
             customer_acceptance: value.customer_acceptance.map(|d| d.into()),
             mandate_type: value.mandate_type.map(|d| d.into()),
+            update_mandate_id: value.update_mandate_id,
         }
     }
 }
diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs
index 3e6ba9e37f8..e6b9950b459 100644
--- a/crates/data_models/src/payments/payment_attempt.rs
+++ b/crates/data_models/src/payments/payment_attempt.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
 use time::PrimitiveDateTime;
 
 use super::PaymentIntent;
-use crate::{errors, mandates::MandateDataType, ForeignIDRef};
+use crate::{errors, mandates::MandateTypeDetails, ForeignIDRef};
 
 #[async_trait::async_trait]
 pub trait PaymentAttemptInterface {
@@ -143,7 +143,7 @@ pub struct PaymentAttempt {
     pub straight_through_algorithm: Option<serde_json::Value>,
     pub preprocessing_step_id: Option<String>,
     // providing a location to store mandate details intermediately for transaction
-    pub mandate_details: Option<MandateDataType>,
+    pub mandate_details: Option<MandateTypeDetails>,
     pub error_reason: Option<String>,
     pub multiple_capture_count: Option<i16>,
     // reference to the payment at connector side
@@ -184,7 +184,7 @@ pub struct PaymentAttemptNew {
     pub attempt_id: String,
     pub status: storage_enums::AttemptStatus,
     pub amount: i64,
-    /// amount + surcharge_amount + tax_amount  
+    /// amount + surcharge_amount + tax_amount
     /// This field will always be derived before updating in the Database
     pub net_amount: i64,
     pub currency: Option<storage_enums::Currency>,
@@ -221,7 +221,7 @@ pub struct PaymentAttemptNew {
     pub business_sub_label: Option<String>,
     pub straight_through_algorithm: Option<serde_json::Value>,
     pub preprocessing_step_id: Option<String>,
-    pub mandate_details: Option<MandateDataType>,
+    pub mandate_details: Option<MandateTypeDetails>,
     pub error_reason: Option<String>,
     pub connector_response_reference_id: Option<String>,
     pub multiple_capture_count: Option<i16>,
diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs
index a06937c99a6..babffdbc4a8 100644
--- a/crates/diesel_models/src/enums.rs
+++ b/crates/diesel_models/src/enums.rs
@@ -166,6 +166,17 @@ use diesel::{
     expression::AsExpression,
     sql_types::Jsonb,
 };
+
+#[derive(
+    serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression,
+)]
+#[diesel(sql_type = Jsonb)]
+#[serde(rename_all = "snake_case")]
+pub struct MandateDetails {
+    pub update_mandate_id: Option<String>,
+    pub mandate_type: Option<MandateDataType>,
+}
+
 #[derive(
     serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression,
 )]
@@ -185,6 +196,7 @@ where
         Ok(serde_json::from_value(value)?)
     }
 }
+
 impl ToSql<Jsonb, diesel::pg::Pg> for MandateDataType
 where
     serde_json::Value: ToSql<Jsonb, diesel::pg::Pg>,
@@ -199,6 +211,40 @@ where
     }
 }
 
+#[derive(
+    serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression,
+)]
+#[diesel(sql_type = Jsonb)]
+#[serde(untagged)]
+#[serde(rename_all = "snake_case")]
+pub enum MandateTypeDetails {
+    MandateType(MandateDataType),
+    MandateDetails(MandateDetails),
+}
+
+impl<DB: Backend> FromSql<Jsonb, DB> for MandateTypeDetails
+where
+    serde_json::Value: FromSql<Jsonb, DB>,
+{
+    fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
+        let value = <serde_json::Value as FromSql<Jsonb, DB>>::from_sql(bytes)?;
+        Ok(serde_json::from_value(value)?)
+    }
+}
+impl ToSql<Jsonb, diesel::pg::Pg> for MandateTypeDetails
+where
+    serde_json::Value: ToSql<Jsonb, diesel::pg::Pg>,
+{
+    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> diesel::serialize::Result {
+        let value = serde_json::to_value(self)?;
+
+        // the function `reborrow` only works in case of `Pg` backend. But, in case of other backends
+        // please refer to the diesel migration blog:
+        // https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations
+        <serde_json::Value as ToSql<Jsonb, diesel::pg::Pg>>::to_sql(&value, &mut out.reborrow())
+    }
+}
+
 #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
 pub struct MandateAmountData {
     pub amount: i64,
diff --git a/crates/diesel_models/src/mandate.rs b/crates/diesel_models/src/mandate.rs
index cc3474914c0..31c6ef62f2b 100644
--- a/crates/diesel_models/src/mandate.rs
+++ b/crates/diesel_models/src/mandate.rs
@@ -75,6 +75,12 @@ pub enum MandateUpdate {
     ConnectorReferenceUpdate {
         connector_mandate_ids: Option<pii::SecretSerdeValue>,
     },
+    ConnectorMandateIdUpdate {
+        connector_mandate_id: Option<String>,
+        connector_mandate_ids: Option<pii::SecretSerdeValue>,
+        payment_method_id: String,
+        original_payment_id: Option<String>,
+    },
 }
 
 #[derive(Clone, Eq, PartialEq, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]
@@ -89,6 +95,9 @@ pub struct MandateUpdateInternal {
     mandate_status: Option<storage_enums::MandateStatus>,
     amount_captured: Option<i64>,
     connector_mandate_ids: Option<pii::SecretSerdeValue>,
+    connector_mandate_id: Option<String>,
+    payment_method_id: Option<String>,
+    original_payment_id: Option<String>,
 }
 
 impl From<MandateUpdate> for MandateUpdateInternal {
@@ -98,16 +107,34 @@ impl From<MandateUpdate> for MandateUpdateInternal {
                 mandate_status: Some(mandate_status),
                 connector_mandate_ids: None,
                 amount_captured: None,
+                connector_mandate_id: None,
+                payment_method_id: None,
+                original_payment_id: None,
             },
             MandateUpdate::CaptureAmountUpdate { amount_captured } => Self {
                 mandate_status: None,
                 amount_captured,
                 connector_mandate_ids: None,
+                connector_mandate_id: None,
+                payment_method_id: None,
+                original_payment_id: None,
             },
             MandateUpdate::ConnectorReferenceUpdate {
-                connector_mandate_ids: connector_mandate_id,
+                connector_mandate_ids,
+            } => Self {
+                connector_mandate_ids,
+                ..Default::default()
+            },
+            MandateUpdate::ConnectorMandateIdUpdate {
+                connector_mandate_id,
+                connector_mandate_ids,
+                payment_method_id,
+                original_payment_id,
             } => Self {
-                connector_mandate_ids: connector_mandate_id,
+                connector_mandate_id,
+                connector_mandate_ids,
+                payment_method_id: Some(payment_method_id),
+                original_payment_id,
                 ..Default::default()
             },
         }
diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs
index d08c146b0b8..4a7603384c5 100644
--- a/crates/diesel_models/src/payment_attempt.rs
+++ b/crates/diesel_models/src/payment_attempt.rs
@@ -51,7 +51,7 @@ pub struct PaymentAttempt {
     pub straight_through_algorithm: Option<serde_json::Value>,
     pub preprocessing_step_id: Option<String>,
     // providing a location to store mandate details intermediately for transaction
-    pub mandate_details: Option<storage_enums::MandateDataType>,
+    pub mandate_details: Option<storage_enums::MandateTypeDetails>,
     pub error_reason: Option<String>,
     pub multiple_capture_count: Option<i16>,
     // reference to the payment at connector side
@@ -126,7 +126,7 @@ pub struct PaymentAttemptNew {
     pub business_sub_label: Option<String>,
     pub straight_through_algorithm: Option<serde_json::Value>,
     pub preprocessing_step_id: Option<String>,
-    pub mandate_details: Option<storage_enums::MandateDataType>,
+    pub mandate_details: Option<storage_enums::MandateTypeDetails>,
     pub error_reason: Option<String>,
     pub connector_response_reference_id: Option<String>,
     pub multiple_capture_count: Option<i16>,
diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs
index 5a1ea696c56..5a2226f0676 100644
--- a/crates/diesel_models/src/user/sample_data.rs
+++ b/crates/diesel_models/src/user/sample_data.rs
@@ -5,7 +5,7 @@ use common_enums::{
 use serde::{Deserialize, Serialize};
 use time::PrimitiveDateTime;
 
-use crate::{enums::MandateDataType, schema::payment_attempt, PaymentAttemptNew};
+use crate::{enums::MandateTypeDetails, schema::payment_attempt, PaymentAttemptNew};
 
 #[derive(
     Clone, Debug, Default, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize,
@@ -50,7 +50,7 @@ pub struct PaymentAttemptBatchNew {
     pub business_sub_label: Option<String>,
     pub straight_through_algorithm: Option<serde_json::Value>,
     pub preprocessing_step_id: Option<String>,
-    pub mandate_details: Option<MandateDataType>,
+    pub mandate_details: Option<MandateTypeDetails>,
     pub error_reason: Option<String>,
     pub connector_response_reference_id: Option<String>,
     pub connector_transaction_id: Option<String>,
diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs
index 810e0ed1d28..aac150b5079 100644
--- a/crates/router/src/compatibility/stripe/payment_intents/types.rs
+++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs
@@ -753,6 +753,7 @@ impl ForeignTryFrom<(Option<MandateData>, Option<String>)> for Option<payments::
                         user_agent: online.user_agent,
                     }),
             }),
+            update_mandate_id: None,
         });
         Ok(mandate_data)
     }
diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs
index b6837d14f82..7299ef624d8 100644
--- a/crates/router/src/core/mandate.rs
+++ b/crates/router/src/core/mandate.rs
@@ -1,13 +1,13 @@
+pub mod helpers;
 pub mod utils;
-
 use api_models::payments;
 use common_utils::{ext_traits::Encode, pii};
-use diesel_models::enums as storage_enums;
+use diesel_models::{enums as storage_enums, Mandate};
 use error_stack::{report, IntoReport, ResultExt};
 use futures::future;
 use router_env::{instrument, logger, tracing};
 
-use super::payments::helpers;
+use super::payments::helpers as payment_helper;
 use crate::{
     core::{
         errors::{self, RouterResponse, StorageErrorExt},
@@ -64,34 +64,17 @@ pub async fn revoke_mandate(
         common_enums::MandateStatus::Active
         | common_enums::MandateStatus::Inactive
         | common_enums::MandateStatus::Pending => {
-            let profile_id = if let Some(ref payment_id) = mandate.original_payment_id {
-                let pi = db
-                    .find_payment_intent_by_payment_id_merchant_id(
-                        payment_id,
-                        &merchant_account.merchant_id,
-                        merchant_account.storage_scheme,
-                    )
-                    .await
-                    .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
-                let profile_id = pi.profile_id.clone().ok_or(
-                    errors::ApiErrorResponse::BusinessProfileNotFound {
-                        id: pi
-                            .profile_id
-                            .unwrap_or_else(|| "Profile id is Null".to_string()),
-                    },
-                )?;
-                Ok(profile_id)
-            } else {
-                Err(errors::ApiErrorResponse::PaymentNotFound)
-            }?;
+            let profile_id =
+                helpers::get_profile_id_for_mandate(&state, &merchant_account, mandate.clone())
+                    .await?;
 
-            let merchant_connector_account = helpers::get_merchant_connector_account(
+            let merchant_connector_account = payment_helper::get_merchant_connector_account(
                 &state,
                 &merchant_account.merchant_id,
                 None,
                 &key_store,
                 &profile_id,
-                &mandate.connector,
+                &mandate.connector.clone(),
                 mandate.merchant_connector_id.as_ref(),
             )
             .await?;
@@ -243,7 +226,72 @@ where
         _ => Some(router_data.request.get_payment_method_data()),
     }
 }
+pub async fn update_mandate_procedure<F, FData>(
+    state: &AppState,
+    resp: types::RouterData<F, FData, types::PaymentsResponseData>,
+    mandate: Mandate,
+    merchant_id: &str,
+    pm_id: Option<String>,
+) -> errors::RouterResult<types::RouterData<F, FData, types::PaymentsResponseData>>
+where
+    FData: MandateBehaviour,
+{
+    let mandate_details = match &resp.response {
+        Ok(types::PaymentsResponseData::TransactionResponse {
+            mandate_reference, ..
+        }) => mandate_reference,
+        Ok(_) => Err(errors::ApiErrorResponse::InternalServerError)
+            .into_report()
+            .attach_printable("Unexpected response received")?,
+        Err(_) => return Ok(resp),
+    };
 
+    let old_record = payments::UpdateHistory {
+        connector_mandate_id: mandate.connector_mandate_id,
+        payment_method_id: mandate.payment_method_id,
+        original_payment_id: mandate.original_payment_id,
+    };
+
+    let mandate_ref = mandate
+        .connector_mandate_ids
+        .parse_value::<payments::ConnectorMandateReferenceId>("Connector Reference Id")
+        .change_context(errors::ApiErrorResponse::MandateDeserializationFailed)?;
+
+    let mut update_history = mandate_ref.update_history.unwrap_or_default();
+    update_history.push(old_record);
+
+    let updated_mandate_ref = payments::ConnectorMandateReferenceId {
+        connector_mandate_id: mandate_details
+            .as_ref()
+            .and_then(|mandate_ref| mandate_ref.connector_mandate_id.clone()),
+        payment_method_id: pm_id.clone(),
+        update_history: Some(update_history),
+    };
+
+    let connector_mandate_ids =
+        Encode::<types::MandateReference>::encode_to_value(&updated_mandate_ref)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .map(masking::Secret::new)?;
+
+    let _update_mandate_details = state
+        .store
+        .update_mandate_by_merchant_id_mandate_id(
+            merchant_id,
+            &mandate.mandate_id,
+            diesel_models::MandateUpdate::ConnectorMandateIdUpdate {
+                connector_mandate_id: mandate_details
+                    .as_ref()
+                    .and_then(|man_ref| man_ref.connector_mandate_id.clone()),
+                connector_mandate_ids: Some(connector_mandate_ids),
+                payment_method_id: pm_id
+                    .unwrap_or("Error retrieving the payment_method_id".to_string()),
+                original_payment_id: Some(resp.payment_id.clone()),
+            },
+        )
+        .await
+        .change_context(errors::ApiErrorResponse::MandateUpdateFailed)?;
+    Ok(resp)
+}
 pub async fn mandate_procedure<F, FData>(
     state: &AppState,
     mut resp: types::RouterData<F, FData, types::PaymentsResponseData>,
@@ -324,7 +372,7 @@ where
                         })
                         .transpose()?;
 
-                    if let Some(new_mandate_data) = helpers::generate_mandate(
+                    if let Some(new_mandate_data) = payment_helper::generate_mandate(
                         resp.merchant_id.clone(),
                         resp.payment_id.clone(),
                         resp.connector.clone(),
@@ -363,6 +411,8 @@ where
                                     api_models::payments::ConnectorMandateReferenceId {
                                         connector_mandate_id: connector_id.connector_mandate_id,
                                         payment_method_id: connector_id.payment_method_id,
+                                        update_history:None,
+
                                     }
                                 )))
                         }));
diff --git a/crates/router/src/core/mandate/helpers.rs b/crates/router/src/core/mandate/helpers.rs
new file mode 100644
index 00000000000..150130ed9e5
--- /dev/null
+++ b/crates/router/src/core/mandate/helpers.rs
@@ -0,0 +1,35 @@
+use common_utils::errors::CustomResult;
+use diesel_models::Mandate;
+use error_stack::ResultExt;
+
+use crate::{core::errors, routes::AppState, types::domain};
+
+pub async fn get_profile_id_for_mandate(
+    state: &AppState,
+    merchant_account: &domain::MerchantAccount,
+    mandate: Mandate,
+) -> CustomResult<String, errors::ApiErrorResponse> {
+    let profile_id = if let Some(ref payment_id) = mandate.original_payment_id {
+        let pi = state
+            .store
+            .find_payment_intent_by_payment_id_merchant_id(
+                payment_id,
+                &merchant_account.merchant_id,
+                merchant_account.storage_scheme,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
+        let profile_id =
+            pi.profile_id
+                .clone()
+                .ok_or(errors::ApiErrorResponse::BusinessProfileNotFound {
+                    id: pi
+                        .profile_id
+                        .unwrap_or_else(|| "Profile id is Null".to_string()),
+                })?;
+        Ok(profile_id)
+    } else {
+        Err(errors::ApiErrorResponse::PaymentNotFound)
+    }?;
+    Ok(profile_id)
+}
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 3dbd6fcb215..e08dbc61f5e 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -1823,14 +1823,24 @@ pub async fn list_payment_methods(
         } else {
             api_surcharge_decision_configs::MerchantSurchargeConfigs::default()
         };
+    print!("PAMT{:?}", payment_attempt);
     Ok(services::ApplicationResponse::Json(
         api::PaymentMethodListResponse {
             redirect_url: merchant_account.return_url,
             merchant_name: merchant_account.merchant_name,
             payment_type,
             payment_methods: payment_method_responses,
-            mandate_payment: payment_attempt.and_then(|inner| inner.mandate_details).map(
-                |d| match d {
+            mandate_payment: payment_attempt
+                .and_then(|inner| inner.mandate_details)
+                .and_then(|man_type_details| match man_type_details {
+                    data_models::mandates::MandateTypeDetails::MandateType(mandate_type) => {
+                        Some(mandate_type)
+                    }
+                    data_models::mandates::MandateTypeDetails::MandateDetails(mandate_details) => {
+                        mandate_details.mandate_type
+                    }
+                })
+                .map(|d| match d {
                     data_models::mandates::MandateDataType::SingleUse(i) => {
                         api::MandateType::SingleUse(api::MandateAmountData {
                             amount: i.amount,
@@ -1852,8 +1862,7 @@ pub async fn list_payment_methods(
                     data_models::mandates::MandateDataType::MultiUse(None) => {
                         api::MandateType::MultiUse(None)
                     }
-                },
-            ),
+                }),
             show_surcharge_breakup_screen: merchant_surcharge_configs
                 .show_surcharge_breakup_screen
                 .unwrap_or_default(),
diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs
index d6343ed871b..8b0b54158fd 100644
--- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs
+++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs
@@ -1,9 +1,10 @@
 use async_trait::async_trait;
+use error_stack::{IntoReport, ResultExt};
 
 use super::{ConstructFlowSpecificData, Feature};
 use crate::{
     core::{
-        errors::{self, ConnectorErrorExt, RouterResult},
+        errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt},
         mandate,
         payments::{
             self, access_token, customers, helpers, tokenization, transformers, PaymentData,
@@ -65,16 +66,16 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
             types::SetupMandateRequestData,
             types::PaymentsResponseData,
         > = connector.connector.get_connector_integration();
+
         let resp = services::execute_connector_processing_step(
             state,
             connector_integration,
             &self,
-            call_connector_action,
+            call_connector_action.clone(),
             connector_request,
         )
         .await
         .to_setup_mandate_failed_response()?;
-
         let pm_id = Box::pin(tokenization::save_payment_method(
             state,
             connector,
@@ -86,14 +87,84 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
         ))
         .await?;
 
-        mandate::mandate_procedure(
-            state,
-            resp,
-            maybe_customer,
-            pm_id,
-            connector.merchant_connector_id.clone(),
-        )
-        .await
+        if let Some(mandate_id) = self
+            .request
+            .setup_mandate_details
+            .as_ref()
+            .and_then(|mandate_data| mandate_data.update_mandate_id.clone())
+        {
+            let mandate = state
+                .store
+                .find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &mandate_id)
+                .await
+                .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?;
+
+            let profile_id = mandate::helpers::get_profile_id_for_mandate(
+                state,
+                merchant_account,
+                mandate.clone(),
+            )
+            .await?;
+            match resp.response {
+                Ok(types::PaymentsResponseData::TransactionResponse { .. }) => {
+                    let connector_integration: services::BoxedConnectorIntegration<
+                        '_,
+                        types::api::MandateRevoke,
+                        types::MandateRevokeRequestData,
+                        types::MandateRevokeResponseData,
+                    > = connector.connector.get_connector_integration();
+                    let merchant_connector_account = helpers::get_merchant_connector_account(
+                        state,
+                        &merchant_account.merchant_id,
+                        None,
+                        key_store,
+                        &profile_id,
+                        &mandate.connector,
+                        mandate.merchant_connector_id.as_ref(),
+                    )
+                    .await?;
+
+                    let router_data = mandate::utils::construct_mandate_revoke_router_data(
+                        merchant_connector_account,
+                        merchant_account,
+                        mandate.clone(),
+                    )
+                    .await?;
+
+                    let _response = services::execute_connector_processing_step(
+                        state,
+                        connector_integration,
+                        &router_data,
+                        call_connector_action,
+                        None,
+                    )
+                    .await
+                    .change_context(errors::ApiErrorResponse::InternalServerError)?;
+                    // TODO:Add the revoke mandate task to process tracker
+                    mandate::update_mandate_procedure(
+                        state,
+                        resp,
+                        mandate,
+                        &merchant_account.merchant_id,
+                        pm_id,
+                    )
+                    .await
+                }
+                Ok(_) => Err(errors::ApiErrorResponse::InternalServerError)
+                    .into_report()
+                    .attach_printable("Unexpected response received")?,
+                Err(_) => Ok(resp),
+            }
+        } else {
+            mandate::mandate_procedure(
+                state,
+                resp,
+                maybe_customer,
+                pm_id,
+                connector.merchant_connector_id.clone(),
+            )
+            .await
+        }
     }
 
     async fn add_access_token<'a>(
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 0cbed255348..520582eb22f 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -840,7 +840,7 @@ fn validate_new_mandate_request(
     let mandate_details = match mandate_data.mandate_type {
         Some(api_models::payments::MandateType::SingleUse(details)) => Some(details),
         Some(api_models::payments::MandateType::MultiUse(details)) => details,
-        None => None,
+        _ => None,
     };
     mandate_details.and_then(|md| md.start_date.zip(md.end_date)).map(|(start_date, end_date)|
         utils::when (start_date >= end_date, || {
diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs
index c81145c5de7..14fc28d6723 100644
--- a/crates/router/src/core/payments/operations/payment_confirm.rs
+++ b/crates/router/src/core/payments/operations/payment_confirm.rs
@@ -431,7 +431,29 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
 
         // The operation merges mandate data from both request and payment_attempt
         setup_mandate = setup_mandate.map(|mut sm| {
-            sm.mandate_type = payment_attempt.mandate_details.clone().or(sm.mandate_type);
+            sm.mandate_type = payment_attempt
+                .mandate_details
+                .clone()
+                .and_then(|mandate| match mandate {
+                    data_models::mandates::MandateTypeDetails::MandateType(mandate_type) => {
+                        Some(mandate_type)
+                    }
+                    data_models::mandates::MandateTypeDetails::MandateDetails(mandate_details) => {
+                        mandate_details.mandate_type
+                    }
+                })
+                .or(sm.mandate_type);
+            sm.update_mandate_id = payment_attempt
+                .mandate_details
+                .clone()
+                .and_then(|mandate| match mandate {
+                    data_models::mandates::MandateTypeDetails::MandateType(_) => None,
+                    data_models::mandates::MandateTypeDetails::MandateDetails(update_id) => {
+                        Some(update_id.update_mandate_id)
+                    }
+                })
+                .flatten()
+                .or(sm.update_mandate_id);
             sm
         });
 
diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs
index 2b25a74deb1..d02ad15fbd6 100644
--- a/crates/router/src/core/payments/operations/payment_create.rs
+++ b/crates/router/src/core/payments/operations/payment_create.rs
@@ -3,7 +3,10 @@ use std::marker::PhantomData;
 use api_models::enums::FrmSuggestion;
 use async_trait::async_trait;
 use common_utils::ext_traits::{AsyncExt, Encode, ValueExt};
-use data_models::{mandates::MandateData, payments::payment_attempt::PaymentAttempt};
+use data_models::{
+    mandates::{MandateData, MandateDetails, MandateTypeDetails},
+    payments::payment_attempt::PaymentAttempt,
+};
 use diesel_models::ephemeral_key;
 use error_stack::{self, ResultExt};
 use masking::PeekInterface;
@@ -255,7 +258,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
             .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayment {
                 payment_id: payment_id.clone(),
             })?;
-
+        // connector mandate reference update history
         let mandate_id = request
             .mandate_id
             .as_ref()
@@ -284,10 +287,11 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
                             api_models::payments::MandateIds {
                                 mandate_id: mandate_obj.mandate_id,
                                 mandate_reference_id: Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
-                                    api_models::payments::ConnectorMandateReferenceId {
-                                        connector_mandate_id: connector_id.connector_mandate_id,
-                                        payment_method_id: connector_id.payment_method_id,
-                                    },
+                                api_models::payments::ConnectorMandateReferenceId{
+                                    connector_mandate_id: connector_id.connector_mandate_id,
+                                    payment_method_id: connector_id.payment_method_id,
+                                    update_history: None
+                                }
                                 ))
                             }
                          }),
@@ -701,6 +705,35 @@ impl PaymentCreate {
             .surcharge_details
             .and_then(|surcharge_details| surcharge_details.tax_amount);
 
+        if request.mandate_data.as_ref().map_or(false, |mandate_data| {
+            mandate_data.update_mandate_id.is_some() && mandate_data.mandate_type.is_some()
+        }) {
+            Err(errors::ApiErrorResponse::InvalidRequestData {message:"Only one field out of 'mandate_type' and 'update_mandate_id' was expected, found both".to_string()})?
+        }
+
+        let mandate_dets = if let Some(update_id) = request
+            .mandate_data
+            .as_ref()
+            .and_then(|inner| inner.update_mandate_id.clone())
+        {
+            let mandate_data = MandateDetails {
+                update_mandate_id: Some(update_id),
+                mandate_type: None,
+            };
+            Some(MandateTypeDetails::MandateDetails(mandate_data))
+        } else {
+            // let mandate_type: data_models::mandates::MandateDataType =
+
+            let mandate_data = MandateDetails {
+                update_mandate_id: None,
+                mandate_type: request
+                    .mandate_data
+                    .as_ref()
+                    .and_then(|inner| inner.mandate_type.clone().map(Into::into)),
+            };
+            Some(MandateTypeDetails::MandateDetails(mandate_data))
+        };
+
         Ok((
             storage::PaymentAttemptNew {
                 payment_id: payment_id.to_string(),
@@ -727,10 +760,7 @@ impl PaymentCreate {
                 business_sub_label: request.business_sub_label.clone(),
                 surcharge_amount,
                 tax_amount,
-                mandate_details: request
-                    .mandate_data
-                    .as_ref()
-                    .and_then(|inner| inner.mandate_type.clone().map(Into::into)),
+                mandate_details: mandate_dets,
                 ..storage::PaymentAttemptNew::default()
             },
             additional_pm_data,
diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs
index e002b92d181..015ef5cea6e 100644
--- a/crates/router/src/core/payments/operations/payment_update.rs
+++ b/crates/router/src/core/payments/operations/payment_update.rs
@@ -255,10 +255,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
                             api_models::payments::MandateIds {
                                 mandate_id: mandate_obj.mandate_id,
                                 mandate_reference_id: Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
-                                    api_models::payments::ConnectorMandateReferenceId {
-                                        connector_mandate_id: connector_id.connector_mandate_id,
-                                        payment_method_id: connector_id.payment_method_id,
-                                    },
+                                    api_models::payments::ConnectorMandateReferenceId {connector_mandate_id:connector_id.connector_mandate_id,payment_method_id:connector_id.payment_method_id, update_history: None },
                                 ))
                             }
                          }),
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index 5f0c702a29d..61917fdcd2e 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -636,6 +636,7 @@ where
                                         api::MandateType::MultiUse(None)
                                     }
                                 }),
+                                update_mandate_id: d.update_mandate_id,
                             }),
                             auth_flow == services::AuthFlow::Merchant,
                         )
diff --git a/crates/router/src/db/mandate.rs b/crates/router/src/db/mandate.rs
index fcd71719657..0cf5cabf2e4 100644
--- a/crates/router/src/db/mandate.rs
+++ b/crates/router/src/db/mandate.rs
@@ -202,6 +202,18 @@ impl MandateInterface for MockDb {
                     } => {
                         mandate.connector_mandate_ids = connector_mandate_ids;
                     }
+
+                    diesel_models::MandateUpdate::ConnectorMandateIdUpdate {
+                        connector_mandate_id,
+                        connector_mandate_ids,
+                        payment_method_id,
+                        original_payment_id,
+                    } => {
+                        mandate.connector_mandate_ids = connector_mandate_ids;
+                        mandate.connector_mandate_id = connector_mandate_id;
+                        mandate.payment_method_id = payment_method_id;
+                        mandate.original_payment_id = original_payment_id
+                    }
                 }
                 Ok(mandate.clone())
             }
diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs
index 307dff55071..bc8ab2e05e7 100644
--- a/crates/router/src/services/api.rs
+++ b/crates/router/src/services/api.rs
@@ -1674,7 +1674,7 @@ pub fn build_redirection_form(
 
                     // Initialize the ThreeDSService
                     const threeDS = gateway.get3DSecure();
-            
+
                     const options = {{
                         customerVaultId: '{customer_vault_id}',
                         currency: '{currency}',
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 786a8c55182..41aefc9026d 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -323,6 +323,7 @@ impl ForeignFrom<api_models::payments::MandateData> for data_models::mandates::M
                     data_models::mandates::MandateDataType::MultiUse(None)
                 }
             }),
+            update_mandate_id: d.update_mandate_id,
         }
     }
 }
diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs
index f8f752c6bc8..b8d71cb32b7 100644
--- a/crates/storage_impl/src/payments/payment_attempt.rs
+++ b/crates/storage_impl/src/payments/payment_attempt.rs
@@ -2,7 +2,7 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMet
 use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found};
 use data_models::{
     errors,
-    mandates::{MandateAmountData, MandateDataType},
+    mandates::{MandateAmountData, MandateDataType, MandateDetails, MandateTypeDetails},
     payments::{
         payment_attempt::{
             PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate,
@@ -14,6 +14,7 @@ use data_models::{
 use diesel_models::{
     enums::{
         MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType,
+        MandateDetails as DieselMandateDetails, MandateTypeDetails as DieselMandateTypeOrDetails,
         MerchantStorageScheme,
     },
     kv,
@@ -999,6 +1000,50 @@ impl DataModelExt for MandateAmountData {
         }
     }
 }
+impl DataModelExt for MandateDetails {
+    type StorageModel = DieselMandateDetails;
+    fn to_storage_model(self) -> Self::StorageModel {
+        DieselMandateDetails {
+            update_mandate_id: self.update_mandate_id,
+            mandate_type: self
+                .mandate_type
+                .map(|mand_type| mand_type.to_storage_model()),
+        }
+    }
+    fn from_storage_model(storage_model: Self::StorageModel) -> Self {
+        Self {
+            update_mandate_id: storage_model.update_mandate_id,
+            mandate_type: storage_model
+                .mandate_type
+                .map(MandateDataType::from_storage_model),
+        }
+    }
+}
+impl DataModelExt for MandateTypeDetails {
+    type StorageModel = DieselMandateTypeOrDetails;
+
+    fn to_storage_model(self) -> Self::StorageModel {
+        match self {
+            Self::MandateType(mandate_type) => {
+                DieselMandateTypeOrDetails::MandateType(mandate_type.to_storage_model())
+            }
+            Self::MandateDetails(mandate_details) => {
+                DieselMandateTypeOrDetails::MandateDetails(mandate_details.to_storage_model())
+            }
+        }
+    }
+
+    fn from_storage_model(storage_model: Self::StorageModel) -> Self {
+        match storage_model {
+            DieselMandateTypeOrDetails::MandateType(data) => {
+                Self::MandateType(MandateDataType::from_storage_model(data))
+            }
+            DieselMandateTypeOrDetails::MandateDetails(data) => {
+                Self::MandateDetails(MandateDetails::from_storage_model(data))
+            }
+        }
+    }
+}
 
 impl DataModelExt for MandateDataType {
     type StorageModel = DieselMandateType;
@@ -1123,7 +1168,7 @@ impl DataModelExt for PaymentAttempt {
             preprocessing_step_id: storage_model.preprocessing_step_id,
             mandate_details: storage_model
                 .mandate_details
-                .map(MandateDataType::from_storage_model),
+                .map(MandateTypeDetails::from_storage_model),
             error_reason: storage_model.error_reason,
             multiple_capture_count: storage_model.multiple_capture_count,
             connector_response_reference_id: storage_model.connector_response_reference_id,
@@ -1231,7 +1276,7 @@ impl DataModelExt for PaymentAttemptNew {
             preprocessing_step_id: storage_model.preprocessing_step_id,
             mandate_details: storage_model
                 .mandate_details
-                .map(MandateDataType::from_storage_model),
+                .map(MandateTypeDetails::from_storage_model),
             error_reason: storage_model.error_reason,
             connector_response_reference_id: storage_model.connector_response_reference_id,
             multiple_capture_count: storage_model.multiple_capture_count,
diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json
index 7858029961a..09cb5fe1404 100644
--- a/openapi/openapi_spec.json
+++ b/openapi/openapi_spec.json
@@ -9026,6 +9026,11 @@
       "MandateData": {
         "type": "object",
         "properties": {
+          "update_mandate_id": {
+            "type": "string",
+            "description": "A way to update the mandate's payment method details",
+            "nullable": true
+          },
           "customer_acceptance": {
             "allOf": [
               {

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

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

## Description

 Update card_details for an existing mandate

### Additional Changes

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

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

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


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

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


## How did you test it?
- Create an MA and MCA(for connectors that support mandate)
- Create a mandate payment with payment_type as `setup_mandate`
- Now update the new card details with `mandate_data` with `mandate_type` as

 ```
     "mandate_data": {
             "customer_acceptance" :..
              "update_mandate_id":"{{existing_mandate_id}}"

        }
 ```
- The card gets updated for the existing mandate_id

<img width="1195" alt="Screenshot 2024-01-25 at 12 08 25 AM" src="https://github.com/juspay/hyperswitch/assets/55580080/0a093794-7fda-4016-a7d8-600f4608a658">

- List mandate for customer with that mandate_id
<img width="1195" alt="Screenshot 2024-01-25 at 12 11 03 AM" src="https://github.com/juspay/hyperswitch/assets/55580080/1b2de9b4-f640-4d83-abf3-8e5843ac4c78">

- Also the `connector_mandate_id` would we updated in the mandate table with all the past records with its past `payment_method_id` as well as `old_connector_mandate_id`



## 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`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
