Bug: Storing MIT details in PM table instead of Mandate table



Move the connector recurring payment information (`connector_mandate_id`) to the PM table. 

- Create a column for storing recurring_payment_details in PM table
- The connector_mandate_id to be stored against he merchant_connector_id
- This columns will hold a simple JSON in the following format
`{
<MCA_ID> : <connector_mandate_id>,
<MCA_ID> : <connector_mandate_id>
.
.
}`
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 4fdf957c842..5b059f255f9 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -84,6 +84,7 @@ pub async fn create_payment_method(
     customer_acceptance: Option<serde_json::Value>,
     payment_method_data: Option<Encryption>,
     key_store: &domain::MerchantKeyStore,
+    connector_mandate_details: Option<serde_json::Value>,
 ) -> errors::CustomResult<storage::PaymentMethod, errors::ApiErrorResponse> {
     db.find_customer_by_customer_id_merchant_id(customer_id, merchant_id, key_store)
         .await
@@ -101,6 +102,7 @@ pub async fn create_payment_method(
             scheme: req.card_network.clone(),
             metadata: pm_metadata.map(masking::Secret::new),
             payment_method_data,
+            connector_mandate_details,
             customer_acceptance: customer_acceptance.map(masking::Secret::new),
             ..storage::PaymentMethodNew::default()
         })
@@ -187,6 +189,7 @@ pub async fn get_or_insert_payment_method(
                     resp.metadata.clone().map(|val| val.expose()),
                     None,
                     locker_id,
+                    None,
                 )
                 .await
             } else {
@@ -372,6 +375,7 @@ pub async fn add_payment_method(
                 pm_metadata.cloned(),
                 None,
                 locker_id,
+                None,
             )
             .await?;
         }
@@ -391,6 +395,7 @@ pub async fn insert_payment_method(
     pm_metadata: Option<serde_json::Value>,
     customer_acceptance: Option<serde_json::Value>,
     locker_id: Option<String>,
+    connector_mandate_details: Option<serde_json::Value>,
 ) -> errors::RouterResult<diesel_models::PaymentMethod> {
     let pm_card_details = resp
         .card
@@ -408,6 +413,7 @@ pub async fn insert_payment_method(
         customer_acceptance,
         pm_data_encrypted,
         key_store,
+        connector_mandate_details,
     )
     .await
 }
@@ -1249,12 +1255,16 @@ pub async fn list_payment_methods(
         })
         .await
         .transpose()?;
-
+    let setup_future_usage = payment_intent.as_ref().and_then(|pi| pi.setup_future_usage);
     let payment_type = payment_attempt.as_ref().map(|pa| {
         let amount = api::Amount::from(pa.amount);
         let mandate_type = if pa.mandate_id.is_some() {
             Some(api::MandateTransactionType::RecurringMandateTransaction)
-        } else if pa.mandate_details.is_some() {
+        } else if pa.mandate_details.is_some()
+            || setup_future_usage
+                .map(|future_usage| future_usage == api_enums::FutureUsage::OffSession)
+                .unwrap_or(false)
+        {
             Some(api::MandateTransactionType::NewMandateTransaction)
         } else {
             None
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index 943550ef434..c449b76c608 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+
 use api_models::payment_methods::PaymentMethodsData;
 use common_enums::PaymentMethod;
 use common_utils::{
@@ -42,7 +44,7 @@ where
     FData: mandate::MandateBehaviour,
 {
     match resp.response {
-        Ok(_) => {
+        Ok(responses) => {
             let db = &*state.store;
             let token_store = state
                 .conf
@@ -84,6 +86,22 @@ 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()
+                && resp
+                    .request
+                    .get_setup_future_usage()
+                    .map(|future_usage| future_usage == storage_enums::FutureUsage::OffSession)
+                    .unwrap_or(false)
+            {
+                add_connector_mandate_details_in_payment_method(responses, connector)
+            } else {
+                None
+            }
+            .map(|connector_mandate_data| connector_mandate_data.encode_to_value())
+            .transpose()
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Unable to serialize customer acceptance to value")?;
+
             let pm_id = if resp.request.get_setup_future_usage().is_some()
                 && customer_acceptance.is_some()
             {
@@ -194,6 +212,7 @@ where
                                             customer_acceptance,
                                             pm_data_encrypted,
                                             key_store,
+                                            connector_mandate_details,
                                         )
                                         .await
                                     } else {
@@ -256,6 +275,7 @@ where
                                                     resp.metadata.clone().map(|val| val.expose()),
                                                     customer_acceptance,
                                                     locker_id,
+                                                    connector_mandate_details,
                                                 )
                                                 .await
                                             } else {
@@ -372,6 +392,7 @@ where
                             customer_acceptance,
                             pm_data_encrypted,
                             key_store,
+                            connector_mandate_details,
                         )
                         .await?;
                     }
@@ -623,3 +644,30 @@ pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
         _ => Ok(None),
     }
 }
+
+fn add_connector_mandate_details_in_payment_method(
+    resp: types::PaymentsResponseData,
+    connector: &api::ConnectorData,
+) -> 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.merchant_connector_id.clone() {
+        mandate_details.insert(mca_id, connector_mandate_id);
+        Some(storage::PaymentsMandateReference(mandate_details))
+    } else {
+        None
+    }
+}
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index 7005e955dff..9fd7458155a 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -385,6 +385,7 @@ pub async fn save_payout_data_to_locker(
         None,
         card_details_encrypted,
         key_store,
+        None,
     )
     .await?;
 
diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs
index a787a4d932c..dee7a8d452f 100644
--- a/crates/router/src/types/storage/payment_method.rs
+++ b/crates/router/src/types/storage/payment_method.rs
@@ -1,9 +1,10 @@
+use std::collections::HashMap;
+
 use api_models::payment_methods;
 pub use diesel_models::payment_method::{
     PaymentMethod, PaymentMethodNew, PaymentMethodUpdate, PaymentMethodUpdateInternal,
     TokenizeCoreWorkflow,
 };
-
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 #[serde(rename_all = "snake_case")]
 pub enum PaymentTokenKind {
@@ -52,3 +53,6 @@ impl PaymentTokenData {
         Self::TemporaryGeneric(GenericTokenData { token })
     }
 }
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct PaymentsMandateReference(pub HashMap<String, Option<String>>);

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

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

## Description
store connector_mandate_details in PaymentMethods table, if its a MIT payment, i.e, if its off_session and mandate_type is not present.
Also If we do the `LIstPaymentMethodforMerchnats` we get the payment_type as setup_mandate

### 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 an MA and an MCA with cybersource(cause currently creating a mandate with 0 dollars and just off_session,i.e,MIT is just supported by cybersource)

-Make a zero dollar payment, with off_session and no mandate_type
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_O7Znp5PhvGpSeE22Hxn4K8snHW6ObRfbA8R1isvLhUXpJRGHOINvlGufM4mGDAqj' \
--data-raw ' {"amount": 0,
    "currency": "USD",
    "confirm": false,
    "capture_method": "automatic",
    "authentication_type": "no_three_ds",
    "customer_id": "q7cc",
    "email": "something@gmail.com",
    "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"
        }
    },
    "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"
        }
    },
    "metadata": {
        "order_details": {
            "product_name": "Apple iphone 15",
            "quantity": 1
        }
    },
    "business_country": "US",
    "business_label": "default",
"setup_future_usage": "off_session"
     
}'
```
![Screenshot 2024-03-05 at 1 20 20 PM](https://github.com/juspay/hyperswitch/assets/55580080/a2975f96-f1f1-4aeb-8b56-ece43ed23e41)

- Do a  LIstPaymentMethodforMerchnats , the payment_type = 'setup_mandate'
![Screenshot 2024-03-05 at 1 20 00 PM](https://github.com/juspay/hyperswitch/assets/55580080/6e1538b8-5dc9-4a59-bd51-a85b9b0e3687)

- Confirm the payment_with customer_acceptance the card will be save and a mandate would be saved in the database against that payment_method
```
curl --location 'http://localhost:8080/payments/pay_847yeh8NWPsQJRr29528/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_O7Znp5PhvGpSeE22Hxn4K8snHW6ObRfbA8R1isvLhUXpJRGHOINvlGufM4mGDAqj' \
--data '{
    "confirm": true,
      "payment_type": "setup_mandate",
    
      "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "in sit",
                "user_agent": "amet irure esse"
            }},

    "payment_method": "card",

    "payment_method_type": "debit",
    "payment_method_data": {
        "card": {
            
            
       "card_number":"4242424242424242",
            "card_exp_month": "11",
            "card_exp_year": "2040",
            "card_holder_name": "AKA",
            "card_cvc": "123",
            "nick_name": "HELO"
        }
    }
}'
```
<img width="974" alt="Screenshot 2024-03-05 at 1 22 58 PM" src="https://github.com/juspay/hyperswitch/assets/55580080/8ebddf69-344b-4d17-ab68-6131bc79662a">


-It'll be stored as  a HashMap<merchant_connector_id, connector_mandate_id>
<img width="1728" alt="Screenshot 2024-03-05 at 1 45 50 AM" src="https://github.com/juspay/hyperswitch/assets/55580080/34305f6b-ff3b-4910-b3eb-9cca2836efe7">



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