Bug: Update a PM entry with connector_mandate_details when re-used in a payment



Update and store connector mandate details when an already saved card is used to make a payment and routed through a different connector
diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs
index ef153c23a7b..488725f6ae8 100644
--- a/crates/diesel_models/src/payment_method.rs
+++ b/crates/diesel_models/src/payment_method.rs
@@ -126,6 +126,9 @@ pub enum PaymentMethodUpdate {
     StatusUpdate {
         status: Option<storage_enums::PaymentMethodStatus>,
     },
+    ConnectorMandateDetailsUpdate {
+        connector_mandate_details: Option<serde_json::Value>,
+    },
 }
 
 #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
@@ -135,6 +138,7 @@ pub struct PaymentMethodUpdateInternal {
     payment_method_data: Option<Encryption>,
     last_used_at: Option<PrimitiveDateTime>,
     status: Option<storage_enums::PaymentMethodStatus>,
+    connector_mandate_details: Option<serde_json::Value>,
 }
 
 impl PaymentMethodUpdateInternal {
@@ -153,6 +157,7 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 payment_method_data: None,
                 last_used_at: None,
                 status: None,
+                connector_mandate_details: None,
             },
             PaymentMethodUpdate::PaymentMethodDataUpdate {
                 payment_method_data,
@@ -161,18 +166,30 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
                 payment_method_data,
                 last_used_at: None,
                 status: None,
+                connector_mandate_details: None,
             },
             PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self {
                 metadata: None,
                 payment_method_data: None,
                 last_used_at: Some(last_used_at),
                 status: None,
+                connector_mandate_details: None,
             },
             PaymentMethodUpdate::StatusUpdate { status } => Self {
                 metadata: None,
                 payment_method_data: None,
                 last_used_at: None,
                 status,
+                connector_mandate_details: None,
+            },
+            PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
+                connector_mandate_details,
+            } => Self {
+                metadata: None,
+                payment_method_data: None,
+                last_used_at: None,
+                status: None,
+                connector_mandate_details,
             },
         }
     }
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 289654f93ed..3f9afe42a40 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -846,6 +846,20 @@ pub async fn update_payment_method(
     Ok(())
 }
 
+pub async fn update_payment_method_connector_mandate_details(
+    db: &dyn db::StorageInterface,
+    pm: payment_method::PaymentMethod,
+    connector_mandate_details: Option<serde_json::Value>,
+) -> errors::CustomResult<(), errors::VaultError> {
+    let pm_update = payment_method::PaymentMethodUpdate::ConnectorMandateDetailsUpdate {
+        connector_mandate_details,
+    };
+
+    db.update_payment_method(pm, pm_update)
+        .await
+        .change_context(errors::VaultError::UpdateInPaymentMethodDataTableFailed)?;
+    Ok(())
+}
 #[instrument(skip_all)]
 pub async fn get_card_from_hs_locker<'a>(
     state: &'a routes::AppState,
diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs
index 0167f16cb8a..6b48f246673 100644
--- a/crates/router/src/core/payments/operations/payment_response.rs
+++ b/crates/router/src/core/payments/operations/payment_response.rs
@@ -19,6 +19,7 @@ use crate::{
         mandate,
         payment_methods::PaymentMethodRetrieve,
         payments::{
+            self,
             helpers::{
                 self as payments_helpers,
                 update_additional_payment_data_with_connector_response_pm_data,
@@ -880,6 +881,37 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
         .in_current_span(),
     );
 
+    let flow_name = core_utils::get_flow_name::<F>()?;
+    if flow_name == "PSync" || flow_name == "CompleteAuthorize" {
+        let connector_mandate_id = match router_data.response.clone() {
+            Ok(resp) => match resp {
+                types::PaymentsResponseData::TransactionResponse {
+                    ref mandate_reference,
+                    ..
+                } => {
+                    if let Some(mandate_ref) = mandate_reference {
+                        mandate_ref.connector_mandate_id.clone()
+                    } else {
+                        None
+                    }
+                }
+                _ => None,
+            },
+            Err(_) => None,
+        };
+        if let Some(ref payment_method) = payment_data.payment_method_info {
+            payments::tokenization::update_connector_mandate_details_in_payment_method(
+                payment_method.clone(),
+                payment_method.payment_method_type,
+                Some(payment_data.payment_attempt.amount),
+                payment_data.payment_attempt.currency,
+                payment_data.payment_attempt.merchant_connector_id.clone(),
+                connector_mandate_id,
+            )
+            .await?;
+        }
+    }
+
     // When connector requires redirection for mandate creation it can update the connector mandate_id during Psync and CompleteAuthorize
     let m_db = state.clone().store;
     let m_payment_method_id = payment_data.payment_attempt.payment_method_id.clone();
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 10e39b2d1b9..3941fad5a42 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -89,19 +89,33 @@ where
                 .change_context(errors::ApiErrorResponse::InternalServerError)
                 .attach_printable("Unable to serialize customer acceptance to value")?;
 
-            let connector_mandate_details = if resp.request.get_setup_mandate_details().is_none()
+            let connector_mandate_id = match responses {
+                types::PaymentsResponseData::TransactionResponse {
+                    ref mandate_reference,
+                    ..
+                } => {
+                    if let Some(mandate_ref) = mandate_reference {
+                        mandate_ref.connector_mandate_id.clone()
+                    } else {
+                        None
+                    }
+                }
+                _ => None,
+            };
+            let check_for_mit_mandates = resp.request.get_setup_mandate_details().is_none()
                 && resp
                     .request
                     .get_setup_future_usage()
                     .map(|future_usage| future_usage == storage_enums::FutureUsage::OffSession)
-                    .unwrap_or(false)
-            {
+                    .unwrap_or(false);
+            // insert in PaymentMethods if its a off-session mit payment
+            let connector_mandate_details = if check_for_mit_mandates {
                 add_connector_mandate_details_in_payment_method(
-                    responses,
                     payment_method_type,
                     amount,
                     currency,
-                    connector,
+                    connector.merchant_connector_id.clone(),
+                    connector_mandate_id.clone(),
                 )
             } else {
                 None
@@ -196,7 +210,9 @@ where
                                     )?;
                                     if let Some(metadata) = pm_metadata {
                                         payment_methods::cards::update_payment_method(
-                                            db, pm, metadata,
+                                            db,
+                                            pm.clone(),
+                                            metadata,
                                         )
                                         .await
                                         .change_context(
@@ -204,6 +220,24 @@ where
                                         )
                                         .attach_printable("Failed to add payment method in db")?;
                                     };
+                                    // update if its a off-session mit payment
+                                    if check_for_mit_mandates {
+                                        let connector_mandate_details =
+                                            update_connector_mandate_details_in_payment_method(
+                                                pm.clone(),
+                                                payment_method_type,
+                                                amount,
+                                                currency,
+                                                connector.merchant_connector_id.clone(),
+                                                connector_mandate_id.clone(),
+                                            )
+                                            .await?;
+
+                                        payment_methods::cards::update_payment_method_connector_mandate_details(db, pm, connector_mandate_details).await.change_context(
+                                        errors::ApiErrorResponse::InternalServerError,
+                                    )
+                                    .attach_printable("Failed to update payment method in db")?;
+                                    }
                                 }
                                 Err(err) => {
                                     if err.current_context().is_db_not_found() {
@@ -268,31 +302,54 @@ where
 
                                 resp.payment_method_id = payment_method_id;
 
-                                let existing_pm =
-                                    match payment_method {
-                                        Ok(pm) => Ok(pm),
-                                        Err(err) => {
-                                            if err.current_context().is_db_not_found() {
-                                                payment_methods::cards::insert_payment_method(
-                                                    db,
-                                                    &resp,
-                                                    payment_method_create_request.clone(),
-                                                    key_store,
-                                                    &merchant_account.merchant_id,
-                                                    &customer.customer_id,
-                                                    resp.metadata.clone().map(|val| val.expose()),
-                                                    customer_acceptance,
-                                                    locker_id,
-                                                    connector_mandate_details,
+                                let existing_pm = match payment_method {
+                                    Ok(pm) => {
+                                        // update if its a off-session mit payment
+                                        if check_for_mit_mandates {
+                                            let connector_mandate_details =
+                                                update_connector_mandate_details_in_payment_method(
+                                                    pm.clone(),
+                                                    payment_method_type,
+                                                    amount,
+                                                    currency,
+                                                    connector.merchant_connector_id.clone(),
+                                                    connector_mandate_id.clone(),
                                                 )
-                                                .await
-                                            } else {
-                                                Err(err)
-                                    .change_context(errors::ApiErrorResponse::InternalServerError)
-                                    .attach_printable("Error while finding payment method")
-                                            }
+                                                .await?;
+
+                                            payment_methods::cards::update_payment_method_connector_mandate_details(db, pm.clone(), connector_mandate_details).await.change_context(
+                                            errors::ApiErrorResponse::InternalServerError,
+                                        )
+                                        .attach_printable("Failed to update payment method in db")?;
                                         }
-                                    }?;
+                                        Ok(pm)
+                                    }
+                                    Err(err) => {
+                                        if err.current_context().is_db_not_found() {
+                                            payment_methods::cards::insert_payment_method(
+                                                db,
+                                                &resp,
+                                                payment_method_create_request.clone(),
+                                                key_store,
+                                                &merchant_account.merchant_id,
+                                                &customer.customer_id,
+                                                resp.metadata.clone().map(|val| val.expose()),
+                                                customer_acceptance,
+                                                locker_id,
+                                                connector_mandate_details,
+                                            )
+                                            .await
+                                        } else {
+                                            Err(err)
+                                                .change_context(
+                                                    errors::ApiErrorResponse::InternalServerError,
+                                                )
+                                                .attach_printable(
+                                                    "Error while finding payment method",
+                                                )
+                                        }
+                                    }
+                                }?;
 
                                 payment_methods::cards::delete_card_from_locker(
                                     state,
@@ -657,32 +714,17 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
     }
 }
 
-fn add_connector_mandate_details_in_payment_method(
-    resp: types::PaymentsResponseData,
+pub fn add_connector_mandate_details_in_payment_method(
     payment_method_type: Option<storage_enums::PaymentMethodType>,
     authorized_amount: Option<i64>,
     authorized_currency: Option<storage_enums::Currency>,
-    connector: &api::ConnectorData,
+    merchant_connector_id: Option<String>,
+    connector_mandate_id: Option<String>,
 ) -> Option<storage::PaymentsMandateReference> {
     let mut mandate_details = HashMap::new();
 
-    let connector_mandate_id = match resp {
-        types::PaymentsResponseData::TransactionResponse {
-            mandate_reference, ..
-        } => {
-            if let Some(mandate_ref) = mandate_reference {
-                mandate_ref.connector_mandate_id.clone()
-            } else {
-                None
-            }
-        }
-        _ => None,
-    };
-
-    if let Some((mca_id, connector_mandate_id)) = connector
-        .merchant_connector_id
-        .clone()
-        .zip(connector_mandate_id)
+    if let Some((mca_id, connector_mandate_id)) =
+        merchant_connector_id.clone().zip(connector_mandate_id)
     {
         mandate_details.insert(
             mca_id,
@@ -698,3 +740,63 @@ fn add_connector_mandate_details_in_payment_method(
         None
     }
 }
+
+pub async fn update_connector_mandate_details_in_payment_method(
+    payment_method: diesel_models::PaymentMethod,
+    payment_method_type: Option<storage_enums::PaymentMethodType>,
+    authorized_amount: Option<i64>,
+    authorized_currency: Option<storage_enums::Currency>,
+    merchant_connector_id: Option<String>,
+    connector_mandate_id: Option<String>,
+) -> RouterResult<Option<serde_json::Value>> {
+    let mandate_reference = match payment_method.connector_mandate_details {
+        Some(_) => {
+            let mandate_details = payment_method
+                .connector_mandate_details
+                .map(|val| {
+                    val.parse_value::<storage::PaymentsMandateReference>("PaymentsMandateReference")
+                })
+                .transpose()
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to deserialize to Payment Mandate Reference ")?;
+
+            if let Some((mca_id, connector_mandate_id)) =
+                merchant_connector_id.clone().zip(connector_mandate_id)
+            {
+                let updated_record = storage::PaymentsMandateReferenceRecord {
+                    connector_mandate_id: connector_mandate_id.clone(),
+                    payment_method_type,
+                    original_payment_authorized_amount: authorized_amount,
+                    original_payment_authorized_currency: authorized_currency,
+                };
+                mandate_details.map(|mut payment_mandate_reference| {
+                    payment_mandate_reference
+                        .entry(mca_id)
+                        .and_modify(|pm| *pm = updated_record)
+                        .or_insert(storage::PaymentsMandateReferenceRecord {
+                            connector_mandate_id,
+                            payment_method_type,
+                            original_payment_authorized_amount: authorized_amount,
+                            original_payment_authorized_currency: authorized_currency,
+                        });
+                    payment_mandate_reference
+                })
+            } else {
+                None
+            }
+        }
+        None => add_connector_mandate_details_in_payment_method(
+            payment_method_type,
+            authorized_amount,
+            authorized_currency,
+            merchant_connector_id,
+            connector_mandate_id,
+        ),
+    };
+    let connector_mandate_details = mandate_reference
+        .map(|mand| mand.encode_to_value())
+        .transpose()
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Unable to serialize customer acceptance to value")?;
+    Ok(connector_mandate_details)
+}

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

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

## Description

The connector_mandate_details, were stored in the db earlier. In this PR if two mandates are created with two different connectors, but for same payment_method, We would update the PaymentMethod entry in the db. Also if the expiry was changed then also we would  update connector_mandate_details.


### Additional Changes

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

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

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


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

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


## How did you test it?
 
 - Create a MA and an MCA_1 with cyber source.
 
 `Scenario 1`
 - Make an off session payment
 -> `Create`
 ```
 curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data-raw '{
    "amount": 1000,
    "currency": "USD",
    "confirm": false,
    
    "customer_id": "new-c7",
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
     "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
"setup_future_usage":"off_session",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },

    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    }
}'
```
-> `Confirm`
```
curl --location 'http://localhost:8080/payments/pay_eWzzcEBGyPDdSqmqr7qE/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data '{
    "confirm": true,
       "payment_method": "card",
    "payment_method_type": "credit",
    
    "payment_method_data": {
        "card": {
             "card_number": "4111111111111111",
            
            
            "card_exp_month": "12",
            "card_exp_year": "2043",
            "card_holder_name": "Cy1",
            "card_cvc": "123"
        }
    },
  
      "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "in sit",
                "user_agent": "amet irure esse"
            }}
}'
```
- A entry would be made in the connector_mandate_details with its respective mca_id
<img width="1727" alt="Screenshot 2024-03-28 at 12 55 01 PM" src="https://github.com/juspay/hyperswitch/assets/55580080/5f83c740-883b-4c53-9587-3c653fb80ef4">




`Scenario 2`

- Create an MCA_2 with cyber source for the same profile, just change the `"connector_label":"cyber source_default_2"`
-Make another off_session payment, with the same payment_method
 -> `Create`
 ```
 curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data-raw '{
    "amount": 1000,
    "currency": "USD",
    "confirm": false,
    
    "customer_id": "new-c7",
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
     "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
"setup_future_usage":"off_session",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },

    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    }
}'
```

-> Confirm
```
curl --location 'http://localhost:8080/payments/pay_eWzzcEBGyPDdSqmqr7qE/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data '{
    "confirm": true,
   
          "payment_method": "card",
    "payment_method_type": "credit",
    
    "payment_method_data": {
        "card": {
             "card_number": "4111111111111111",
            
            
            "card_exp_month": "12",
            "card_exp_year": "2043",
            "card_holder_name": "Cy1",
            "card_cvc": "123"
        }
    },
        
            "routing": {
        "type": "single",
        "data": {"connector":"cybersource","merchant_connector_id":"mca_1GR9IUCJ08Ip7lbRMsG5"}
      },
      "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "in sit",
                "user_agent": "amet irure esse"
            }}
}'
```
- A entry with updated connector_mandate_details is shown, with its respective `mca_id's` 
<img width="1727" alt="Screenshot 2024-03-28 at 12 54 52 PM" src="https://github.com/juspay/hyperswitch/assets/55580080/ccda0b88-b18e-492e-9f59-43cdf7027a64">


         `Scenario- 3`
- Make a payment with some Metadata-change, for eg:update the expiry number of the card`

 -> `Create`
 ```
 curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data-raw '{
    "amount": 1000,
    "currency": "USD",
    "confirm": false,
    
    "customer_id": "new-c7",
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
     "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
"setup_future_usage":"off_session",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },

    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    }
}'
```

-> `Confirm`
```
curl --location 'http://localhost:8080/payments/pay_eWzzcEBGyPDdSqmqr7qE/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data '{
    "confirm": true,
       "payment_method": "card",
    "payment_method_type": "credit",
    
    "payment_method_data": {
        "card": {
             "card_number": "4111111111111111",
            
            
            "card_exp_month": "10",
            "card_exp_year": "2043",
            "card_holder_name": "Cy1",
            "card_cvc": "123"
        }
    },
  
      "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "in sit",
                "user_agent": "amet irure esse"
            }}
}'
```
- A updated entry of connector_mandate_id for that mca_id is shown
<img width="1727" alt="Screenshot 2024-03-28 at 12 57 01 PM" src="https://github.com/juspay/hyperswitch/assets/55580080/ba1f55d5-a06a-4170-991f-3c2863efe5b4">

`Scenario- 4`
- Make a `off_session` payment with the same card .

 -> `Create`
 ```
 curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data-raw '{
    "amount": 1000,
    "currency": "USD",
    "confirm": false,
    
    "customer_id": "new-c7",
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
     "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
"setup_future_usage":"off_session",
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },

    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    }
}'
```

-> `Confirm`
```
curl --location 'http://localhost:8080/payments/pay_eWzzcEBGyPDdSqmqr7qE/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_BgXaGEnYjJ4qj7ko3kJ7Rj40BnLYhYT4IEryQ5f2HPnGJhrW59GkdRUKqHWC2jdy' \
--data '{
    "confirm": true,
       "payment_method": "card",
    "payment_method_type": "credit",
    
    "payment_method_data": {
        "card": {
             "card_number": "4111111111111111",
            
            
            "card_exp_month": "10",
            "card_exp_year": "2043",
            "card_holder_name": "Cy1",
            "card_cvc": "123"
        }
    },
  
      "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "in sit",
                "user_agent": "amet irure esse"
            }}
}'
```
- The entry would be updated with the new connector_mandate_details, with the same card

<img width="1727" alt="Screenshot 2024-03-28 at 12 58 04 PM" src="https://github.com/juspay/hyperswitch/assets/55580080/4401d70b-d062-4cc5-925d-bfa2996bf344">

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