Bug: `override setup_future_usage` filed to on_session based on merchant config



This is the use case where in which we need if the setup_future_usage is set to off_session in the payment method needs to be just saved at hyperswitch and not at connector. This feature is not working as expected during the pre-routing, hence needs to override it in the pre-routing as well.

`skip_saving_wallet_at_connector_<merchant_id>` is the config which takes vector of payment method type as the value. When this config is set, the `setup_future_usage` is overridden to `on_session` by which mandates won't be created at the connector's end.
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index f65cc052eaf..4af5a2ed308 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -3279,7 +3279,7 @@ where
                 && should_do_retry
             {
                 let retryable_connector_data = helpers::get_apple_pay_retryable_connectors(
-                    state,
+                    &state,
                     merchant_account,
                     payment_data,
                     key_store,
@@ -3303,6 +3303,8 @@ where
                 .first()
                 .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)?;
 
+            helpers::override_setup_future_usage_to_on_session(&*state.store, payment_data).await?;
+
             return Ok(ConnectorCallType::PreDetermined(
                 first_pre_routing_connector_data_list.clone(),
             ));
@@ -3598,24 +3600,7 @@ pub async fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone
             Ok(ConnectorCallType::PreDetermined(chosen_connector_data))
         }
         _ => {
-            let skip_saving_wallet_at_connector_optional =
-                helpers::config_skip_saving_wallet_at_connector(
-                    &*state.store,
-                    &payment_data.payment_intent.merchant_id,
-                )
-                .await?;
-
-            if let Some(skip_saving_wallet_at_connector) = skip_saving_wallet_at_connector_optional
-            {
-                if let Some(payment_method_type) = payment_data.payment_attempt.payment_method_type
-                {
-                    if skip_saving_wallet_at_connector.contains(&payment_method_type) {
-                        logger::debug!("Override setup_future_usage from off_session to on_session based on the merchant's skip_saving_wallet_at_connector configuration to avoid creating a connector mandate.");
-                        payment_data.payment_intent.setup_future_usage =
-                            Some(enums::FutureUsage::OnSession);
-                    }
-                }
-            };
+            helpers::override_setup_future_usage_to_on_session(&*state.store, payment_data).await?;
 
             let first_choice = connectors
                 .first()
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 58f061115a1..0af7cebfd85 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -450,6 +450,7 @@ pub async fn get_token_pm_type_mandate_details(
     merchant_account: &domain::MerchantAccount,
     merchant_key_store: &domain::MerchantKeyStore,
     payment_method_id: Option<String>,
+    customer_id: &Option<id_type::CustomerId>,
 ) -> RouterResult<MandateGenericData> {
     let mandate_data = request.mandate_data.clone().map(MandateData::foreign_from);
     let (
@@ -553,7 +554,9 @@ pub async fn get_token_pm_type_mandate_details(
                         || request.payment_method_type
                             == Some(api_models::enums::PaymentMethodType::GooglePay)
                     {
-                        if let Some(customer_id) = &request.customer_id {
+                        if let Some(customer_id) =
+                            &request.customer_id.clone().or(customer_id.clone())
+                        {
                             let customer_saved_pm_option = match state
                                 .store
                                 .find_payment_method_by_customer_id_merchant_id_list(
@@ -4156,7 +4159,7 @@ pub fn get_applepay_metadata(
 
 #[cfg(all(feature = "retry", feature = "connector_choice_mca_id"))]
 pub async fn get_apple_pay_retryable_connectors<F>(
-    state: SessionState,
+    state: &SessionState,
     merchant_account: &domain::MerchantAccount,
     payment_data: &mut PaymentData<F>,
     key_store: &domain::MerchantKeyStore,
@@ -4180,7 +4183,7 @@ where
         .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)?;
 
     let merchant_connector_account_type = get_merchant_connector_account(
-        &state,
+        state,
         merchant_account.merchant_id.as_str(),
         payment_data.creds_identifier.to_owned(),
         key_store,
@@ -4968,3 +4971,25 @@ pub async fn config_skip_saving_wallet_at_connector(
         }
     })
 }
+
+pub async fn override_setup_future_usage_to_on_session<F: Clone>(
+    db: &dyn StorageInterface,
+    payment_data: &mut PaymentData<F>,
+) -> CustomResult<(), errors::ApiErrorResponse> {
+    if payment_data.payment_intent.setup_future_usage == Some(enums::FutureUsage::OffSession) {
+        let skip_saving_wallet_at_connector_optional =
+            config_skip_saving_wallet_at_connector(db, &payment_data.payment_intent.merchant_id)
+                .await?;
+
+        if let Some(skip_saving_wallet_at_connector) = skip_saving_wallet_at_connector_optional {
+            if let Some(payment_method_type) = payment_data.payment_attempt.payment_method_type {
+                if skip_saving_wallet_at_connector.contains(&payment_method_type) {
+                    logger::debug!("Override setup_future_usage from off_session to on_session based on the merchant's skip_saving_wallet_at_connector configuration to avoid creating a connector mandate.");
+                    payment_data.payment_intent.setup_future_usage =
+                        Some(enums::FutureUsage::OnSession);
+                }
+            }
+        };
+    };
+    Ok(())
+}
diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs
index 4fd427e38fa..7188a8216de 100644
--- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs
+++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs
@@ -124,6 +124,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
             merchant_account,
             key_store,
             payment_attempt.payment_method_id.clone(),
+            &payment_intent.customer_id,
         )
         .await?;
         let token = token.or_else(|| payment_attempt.payment_token.clone());
diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs
index 5e35b918a0f..99ac4e3e97c 100644
--- a/crates/router/src/core/payments/operations/payment_confirm.rs
+++ b/crates/router/src/core/payments/operations/payment_confirm.rs
@@ -495,6 +495,8 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
         let m_request = request.clone();
         let m_key_store = key_store.clone();
 
+        let payment_intent_customer_id = payment_intent.customer_id.clone();
+
         let mandate_details_fut = tokio::spawn(
             async move {
                 helpers::get_token_pm_type_mandate_details(
@@ -504,6 +506,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
                     &m_merchant_account,
                     &m_key_store,
                     None,
+                    &payment_intent_customer_id,
                 )
                 .await
             }
diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs
index b8045487d9f..fc1f53d358d 100644
--- a/crates/router/src/core/payments/operations/payment_create.rs
+++ b/crates/router/src/core/payments/operations/payment_create.rs
@@ -136,6 +136,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
             merchant_account,
             merchant_key_store,
             None,
+            &request.customer_id,
         )
         .await?;
 
diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs
index c34a40fd908..c9b778fae31 100644
--- a/crates/router/src/core/payments/operations/payment_update.rs
+++ b/crates/router/src/core/payments/operations/payment_update.rs
@@ -148,6 +148,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
             merchant_account,
             key_store,
             None,
+            &payment_intent.customer_id,
         )
         .await?;
         helpers::validate_amount_to_capture_and_capture_method(Some(&payment_attempt), request)?;

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

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

## Description
<!-- Describe your changes in detail -->
This is the use case where in which we need if the setup_future_usage is set to off_session in the payment method needs to be just saved at hyperswitch and not at connector. This is fix to override it during the pre-routing as well.

`skip_saving_wallet_at_connector_<merchant_id>` is the config which takes vector of payment method type as the value. When this config is set, the `setup_future_usage` is overridden to `on_session` by which mandates won't be created at the connector's end.

```
curl --location 'localhost:8080/configs/' \
--header 'api-key: test_admin' \
--header 'Content-Type: application/json' \
--data '{
    "key": "skip_saving_wallet_at_connector_merchant_1718351667",
    "value": "[\"apple_pay\"]"
}'
```

### 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?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
-> Create merchant connector account
-> Make a CIT with off_session and customer acceptance
```
{
    "amount": 800,
    "currency": "USD",
    "confirm": true,
    "amount_to_capture": 800,
    "customer_id": "{{$timestamp}}",
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "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",
    "statement_descriptor_name": "Juspay",
    "statement_descriptor_suffix": "Router",
    "payment_method": "wallet",
    "payment_method_type": "apple_pay",
    "payment_method_data": {
        "wallet": {
            "apple_pay": {
                "payment_data":"\==",
                "payment_method": {
                    "display_name": "Visa 0326",
                    "network": "Mastercard",
                    "type": "debit"
                },
                "transaction_identifier": "55CC32D7BF7890B9064433F15B9F23F849CF84AFD01E4E65DD8ADE306300E9D8"
            }
        }
    },
    "setup_future_usage": "off_session",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "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"
        }
    },
    "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    }
}
```
<img width="1123" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/5ecd2a7a-6c4d-402f-b46d-8292f046bedc">

-> Create confirm false
```
{
    "amount": 2000,
    "currency": "USD",
    "confirm": false,
    "amount_to_capture": 2000,
    "customer_id": "1720081192",
    "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",
    "statement_descriptor_name": "Juspay",
    "statement_descriptor_suffix": "Router",
    "setup_future_usage": "off_session"
}
```
<img width="1109" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/4c3aff70-f371-4665-b901-610f10349aa9">

-> Confirm with payment data without customer acceptance
```
{
    "client_secret": "pay_Fgkr31SdjHPZJhq2ImD8_secret_SaWA7AtdvD467lCSnApQ",
    "payment_type": "new_mandate",
    "payment_method": "wallet",
    "payment_method_type": "apple_pay",
    "payment_method_data": {
        "wallet": {
            "apple_pay": {
                "payment_data": "==",
                "payment_method": {
                    "display_name": "Visa 0326",
                    "network": "Mastercard",
                    "type": "debit"
                },
                "transaction_identifier": "55CC32D7BF7890B9064433F15B9F23F849CF84AFD01E4E65DD8ADE306300E9D8"
            }
        }
    }
}
```

-> Now when listed the last used should be updated
```
curl --location 'http://localhost:8080/customers/1720081349/payment_methods' \
--header 'Accept: application/json' \
--header 'api-key: aaa'
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
