Bug: Override the `setup_future_usage` field 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.

`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\"]"
}'
```

Also add a validation to add duplicate apple pay payment method being saved.
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index 50eedcfb568..b4ac878d372 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -3558,6 +3558,25 @@ 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);
+                    }
+                }
+            };
+
             let first_choice = connectors
                 .first()
                 .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs
index 7eada733aed..1a2f0a5cbaa 100644
--- a/crates/router/src/core/payments/helpers.rs
+++ b/crates/router/src/core/payments/helpers.rs
@@ -4793,3 +4793,26 @@ pub async fn get_payment_external_authentication_flow_during_confirm<F: Clone>(
 pub fn get_redis_key_for_extended_card_info(merchant_id: &str, payment_id: &str) -> String {
     format!("{merchant_id}_{payment_id}_extended_card_info")
 }
+
+pub async fn config_skip_saving_wallet_at_connector(
+    db: &dyn StorageInterface,
+    merchant_id: &String,
+) -> CustomResult<Option<Vec<storage_enums::PaymentMethodType>>, errors::ApiErrorResponse> {
+    let config = db
+        .find_config_by_key_unwrap_or(
+            format!("skip_saving_wallet_at_connector_{}", merchant_id).as_str(),
+            Some("[]".to_string()),
+        )
+        .await;
+    Ok(match config {
+        Ok(conf) => Some(
+            serde_json::from_str::<Vec<storage_enums::PaymentMethodType>>(&conf.config)
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("skip_save_wallet_at_connector config parsing failed")?,
+        ),
+        Err(err) => {
+            logger::error!("{err}");
+            None
+        }
+    })
+}
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index b014f42a5ce..2d5d216a856 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -515,35 +515,79 @@ where
                         }
                     },
                     None => {
-                        let pm_metadata = create_payment_method_metadata(None, connector_token)?;
-
-                        locker_id = resp.payment_method.and_then(|pm| {
-                            if pm == PaymentMethod::Card {
-                                Some(resp.payment_method_id)
-                            } else {
-                                None
+                        let customer_apple_pay_saved_pm_id_option = if payment_method_type
+                            == Some(api_models::enums::PaymentMethodType::ApplePay)
+                        {
+                            match state
+                                .store
+                                .find_payment_method_by_customer_id_merchant_id_list(
+                                    &customer_id,
+                                    merchant_id,
+                                    None,
+                                )
+                                .await
+                            {
+                                Ok(customer_payment_methods) => Ok(customer_payment_methods
+                                    .iter()
+                                    .find(|payment_method| {
+                                        payment_method.payment_method_type
+                                            == Some(api_models::enums::PaymentMethodType::ApplePay)
+                                    })
+                                    .map(|pm| pm.payment_method_id.clone())),
+                                Err(error) => {
+                                    if error.current_context().is_db_not_found() {
+                                        Ok(None)
+                                    } else {
+                                        Err(error)
+                                            .change_context(
+                                                errors::ApiErrorResponse::InternalServerError,
+                                            )
+                                            .attach_printable(
+                                                "failed to find payment methods for a customer",
+                                            )
+                                    }
+                                }
                             }
-                        });
-
-                        resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
-                        payment_methods::cards::create_payment_method(
-                            db,
-                            &payment_method_create_request,
-                            &customer_id,
-                            &resp.payment_method_id,
-                            locker_id,
-                            merchant_id,
-                            pm_metadata,
-                            customer_acceptance,
-                            pm_data_encrypted,
-                            key_store,
-                            connector_mandate_details,
-                            None,
-                            network_transaction_id,
-                            merchant_account.storage_scheme,
-                            encrypted_payment_method_billing_address,
-                        )
-                        .await?;
+                        } else {
+                            Ok(None)
+                        }?;
+
+                        if let Some(customer_apple_pay_saved_pm_id) =
+                            customer_apple_pay_saved_pm_id_option
+                        {
+                            resp.payment_method_id = customer_apple_pay_saved_pm_id;
+                        } else {
+                            let pm_metadata =
+                                create_payment_method_metadata(None, connector_token)?;
+
+                            locker_id = resp.payment_method.and_then(|pm| {
+                                if pm == PaymentMethod::Card {
+                                    Some(resp.payment_method_id)
+                                } else {
+                                    None
+                                }
+                            });
+
+                            resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm");
+                            payment_methods::cards::create_payment_method(
+                                db,
+                                &payment_method_create_request,
+                                &customer_id,
+                                &resp.payment_method_id,
+                                locker_id,
+                                merchant_id,
+                                pm_metadata,
+                                customer_acceptance,
+                                pm_data_encrypted,
+                                key_store,
+                                connector_mandate_details,
+                                None,
+                                network_transaction_id,
+                                merchant_account.storage_scheme,
+                                encrypted_payment_method_billing_address,
+                            )
+                            .await?;
+                        };
                     }
                 }
 

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

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

## Description
<!-- Describe your changes in detail -->
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.

`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\"]"
}'
```

Also add a validation to add duplicate apple pay payment method being saved.

### 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
-> Set below config for the merchant id
```
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\"]"
}'
```
-> Create a Apple pay CIT with `setup_future_usage = off_session`
```
curl --location 'http://localhost:8080/payments' \
--header 'Accept: application/json' \
--header 'api-key: dev_EWsohyGByFVxgvYj8nbZbHTQObe6hRlLMS7rSKH2bFsySSjCc86Z6eOIH7tmLbUL' \
--header 'Content-Type: application/json' \
--data-raw '{
    "amount": 800,
    "currency": "USD",
    "confirm": true,
    "amount_to_capture": 800,
    "customer_id": "aaabbbcccdddeeee",
    "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":"eyJkYXRhIjoiSWJKaWp1TTRXT0lpM1BZcTRWVlpIL2wwUld1Qm5JRk5vMHBsWGp1L1E2bW9uQ0JVOE9rVHFDbnkwWDlZVVBZYzVBMXRIckE1eTV0NlpsSFVkZFJyYlY3QXIzMVVEc0NSbVlDVDRweFdEQ0oxTnF5WWYrK3NPQThZRkh5di83aUJiTnJ6K1g0Q0dwelNtelRNL2V6TUxzY2VCdHM4dkozQk05VnRGdVY2bXJsN2lPcGNHUGlNam5wK2ZkaG1OYlNHcGt0Qmt4QVRmaHlpM3VjbWNBejNjYWlMV3RSN2Yrc2owUmlzL0pBcmhuVzVZdmNodE53K2prSFBXZW9HeG1KTEFFUW1HRnZ3dGJCaWFKVFEvVnpmdEJtK3RGYkliWTBYeFdrRXdSQllIajlCVnVmbEVDaVlKK2NYLytBdVhlS2ZHZHZMQWZrNjNSYmZJQm5hTVR6dWlXZ2tEM1N5T3hJbE82MGxveHFPSmkxbkgvQXZSdFlMOUt4NWM0dzEwYnZVcENZZys4aytpdmg0aEdIdTF3PT0iLCJzaWduYXR1cmUiOiJNSUFHQ1NxR1NJYjNEUUVIQXFDQU1JQUNBUUV4RFRBTEJnbGdoa2dCWlFNRUFnRXdnQVlKS29aSWh2Y05BUWNCQUFDZ2dEQ0NBK013Z2dPSW9BTUNBUUlDQ0JaalRJc09NRmNYTUFvR0NDcUdTTTQ5QkFNQ01Ib3hMakFzQmdOVkJBTU1KVUZ3Y0d4bElFRndjR3hwWTJGMGFXOXVJRWx1ZEdWbmNtRjBhVzl1SUVOQklDMGdSek14SmpBa0JnTlZCQXNNSFVGd2NHeGxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1STXdFUVlEVlFRS0RBcEJjSEJzWlNCSmJtTXVNUXN3Q1FZRFZRUUdFd0pWVXpBZUZ3MHlOREEwTWpreE56UTNNamRhRncweU9UQTBNamd4TnpRM01qWmFNRjh4SlRBakJnTlZCQU1NSEdWall5MXpiWEF0WW5KdmEyVnlMWE5wWjI1ZlZVTTBMVkJTVDBReEZEQVNCZ05WQkFzTUMybFBVeUJ9CZ05WSFE4QkFmOEVCQU1DQVFZd0VBWUtLb1pJaHZkalpBWUNEZ1FDQlFBd0NnWUlLb1pJemowRUF3SURad0F3WkFJd09zOXlnMUVXbWJHRyt6WERWc3Bpdi9RWDdka1BkVTJpanI3eG5JRmVRcmVKK0pqM20xbWZtTlZCRFkrZDZjTCtBakF5TGRWRUliQ2pCWGRzWGZNNE81Qm4vUmQ4TENGdGxrL0djbW1DRW05VStIcDlHNW5MbXdtSklXRUdtUThKa2gwQUFER0NBWWN3Z2dHREFnRUJNSUdHTUhveExqQXNCZ05WQkFNTUpVRndjR3hsSUVGd2NHeHBZMkYwYVc5dUlFbHVkR1ZuY21GMGFXOXVJRU5CSUMwZ1J6TXhKakFrQmdOVkJBc01IVUZ3Y0d4bElFTmxjblJwWm1sallYUnBiMjRnUVhWMGFHOXlhWFI1TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRRYcjZ3OFIraU9TUm1qUDV5ZWlWUEs4OWFoZDJYM1JtaUZtUjQxdHN4S1AwOEpBZVYrSXpvUXBnPT0iLCJ0cmFuc2FjdGlvbklkIjoiMmEzZDYyYTUzZDBmMzg1NGUwNTA0Y2RhZDU2MzlmYjA2MjJiZTI0YzY0ZDg2ZGYxMDlkMTNjZjdkNTAxNjA1MSJ9LCJ2ZXJzaW9uIjoiRUNfdjEifQ==",
                "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="992" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/57b0ffac-c806-4b0d-b933-68a5c79fcf58">

-> Retrieve the payment
```
curl --location 'http://localhost:8080/payments/pay_loeJ3oCXJWjjMsghuaId?expand_attempts=true' \
--header 'Accept: application/json' \
--header 'api-key: dev_EWsohyGByFVxgvYj8nbZbHTQObe6hRlLMS7rSKH2bFsySSjCc86Z6eOIH7tmLbUL'
```
<img width="769" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/482c8ca3-6616-4ad2-bdc2-c6b08647d00b">
-> Do list customer payment methods
```
curl --location 'http://localhost:8080/customers/aaabbbcccdddeeee/payment_methods' \
--header 'Accept: application/json' \
--header 'api-key: dev_EWsohyGByFVxgvYj8nbZbHTQObe6hRlLMS7rSKH2bFsySSjCc86Z6eOIH7tmLbUL'
```
<img width="895" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/9e22fdcf-2c94-4401-81ee-deda17de0375">

-> Perform the CIT with the same request (for the same customer). After which perform the list customer payment methods and it should I have still only one apple pay payment method saved.
-> Also for the above listed payment method verify there is no `connector_wallets_details` in the `payment_methods` table.
<img width="1254" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/9e1b4e9f-70a0-4c23-a16f-ba5cda2dc0f4">




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