Bug: add retry support for debit routing



In the case of debit routing flow, the routing output (`CallConnectorType`) will include both the connector and its associated card network. In our current implementation, we simply iterate over the connector list and pass the connector. With the updated requirement, we will also need to pass the corresponding card network for each connector in the case of debit routing.
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index 2cd9f507893..0994dadb5c5 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -1516,7 +1516,7 @@ fn get_connector_data_with_routing_decision(
             let routing_decision =
                 routing_helpers::RoutingDecisionData::get_debit_routing_decision_data(
                     card_network,
-                    debit_routing_output,
+                    Some(debit_routing_output),
                 );
             return Ok((data, Some(routing_decision)));
         }
diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs
index df36f9f5117..b8127940ff1 100644
--- a/crates/router/src/core/payments/retry.rs
+++ b/crates/router/src/core/payments/retry.rs
@@ -16,6 +16,7 @@ use crate::{
             flows::{ConstructFlowSpecificData, Feature},
             operations,
         },
+        routing::helpers as routing_helpers,
     },
     db::StorageInterface,
     routes::{
@@ -109,6 +110,7 @@ where
             frm_suggestion,
             business_profile,
             false, //should_retry_with_pan is not applicable for step-up
+            None,
         )
         .await?;
     }
@@ -155,11 +157,20 @@ where
                             .unwrap_or(false)
                         && business_profile.is_clear_pan_retries_enabled;
 
-                    let connector = if should_retry_with_pan {
+                    let (connector, routing_decision) = if should_retry_with_pan {
                         // If should_retry_with_pan is true, it indicates that we are retrying with PAN using the same connector.
-                        original_connector_data.clone()
+                        (original_connector_data.clone(), None)
                     } else {
-                        super::get_connector_data(&mut connector_routing_data)?.connector_data
+                        let connector_routing_data =
+                            super::get_connector_data(&mut connector_routing_data)?;
+                        let routing_decision = connector_routing_data.network.map(|card_network| {
+                            routing_helpers::RoutingDecisionData::get_debit_routing_decision_data(
+                                card_network,
+                                None,
+                            )
+                        });
+
+                        (connector_routing_data.connector_data, routing_decision)
                     };
 
                     router_data = do_retry(
@@ -178,6 +189,7 @@ where
                         frm_suggestion,
                         business_profile,
                         should_retry_with_pan,
+                        routing_decision,
                     )
                     .await?;
 
@@ -329,6 +341,7 @@ pub async fn do_retry<F, ApiRequest, FData, D>(
     frm_suggestion: Option<storage_enums::FrmSuggestion>,
     business_profile: &domain::Profile,
     should_retry_with_pan: bool,
+    routing_decision: Option<routing_helpers::RoutingDecisionData>,
 ) -> RouterResult<types::RouterData<F, FData, types::PaymentsResponseData>>
 where
     F: Clone + Send + Sync,
@@ -372,7 +385,7 @@ where
         business_profile,
         true,
         should_retry_with_pan,
-        None,
+        routing_decision,
         None,
     )
     .await?;
diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs
index 4798cce9b02..ca23d8c2d74 100644
--- a/crates/router/src/core/routing/helpers.rs
+++ b/crates/router/src/core/routing/helpers.rs
@@ -330,7 +330,7 @@ pub enum RoutingDecisionData {
 #[cfg(feature = "v1")]
 pub struct DebitRoutingDecisionData {
     pub card_network: common_enums::enums::CardNetwork,
-    pub debit_routing_result: open_router::DebitRoutingOutput,
+    pub debit_routing_result: Option<open_router::DebitRoutingOutput>,
 }
 #[cfg(feature = "v1")]
 impl RoutingDecisionData {
@@ -350,7 +350,7 @@ impl RoutingDecisionData {
 
     pub fn get_debit_routing_decision_data(
         network: common_enums::enums::CardNetwork,
-        debit_routing_result: open_router::DebitRoutingOutput,
+        debit_routing_result: Option<open_router::DebitRoutingOutput>,
     ) -> Self {
         Self::DebitRouting(DebitRoutingDecisionData {
             card_network: network,
@@ -370,7 +370,9 @@ impl DebitRoutingDecisionData {
             + Clone,
     {
         payment_data.set_card_network(self.card_network.clone());
-        payment_data.set_co_badged_card_data(&self.debit_routing_result);
+        self.debit_routing_result
+            .as_ref()
+            .map(|data| payment_data.set_co_badged_card_data(data));
     }
 }
 #[derive(Clone, Debug)]

## 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 -->
In the case of debit routing flow, the routing output (`CallConnectorType`) will include both the connector and its associated card network. In our current implementation, we simply iterate over the connector list and pass the connector. With the updated requirement, we will also need to pass the corresponding card network for each connector in the case of debit routing.

### 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 a business profile and enable debit routing for it 
```
curl --location 'http://localhost:8080/account/merchant_1748851578/business_profile/pro_gEwS6ve9KVqywWL0hmAq' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: ' \
--data '{
    "is_debit_routing_enabled": true
}'
```
-> Create a adyen connector
-> I have created a db entry that contains the wrong card network so that the payment can be retried
```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-feature: router-custom' \
--header 'x-tenant-id: hyperswitch' \
--header 'api-key: ' \
--data-raw '{
    "amount": 10000,
    "amount_to_capture": 10000,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "setup_future_usage": "on_session",
    "capture_on": "2022-09-10T10:11:12Z",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "customer_id": "cu_1748948839",
    "return_url": "http://127.0.0.1:4040",
    "payment_method": "card",
    "payment_method_type": "debit",
    "payment_method_data": {
        "card": {
            "card_number": "4000033003300335",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "737"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX",
            "last_name": "ss"
        },
        "email": "raj@gmail.com"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "browser_info": {
        "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/70.0.3538.110 Safari\/537.36",
        "accept_header": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8",
        "language": "nl-NL",
        "color_depth": 24,
        "screen_height": 723,
        "screen_width": 1536,
        "time_zone": 0,
        "java_enabled": true,
        "java_script_enabled": true,
        "ip_address": "125.0.0.1"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
```
{
    "payment_id": "pay_JMfKcXwsfYNl06UWsCc0",
    "merchant_id": "merchant_1748851578",
    "status": "succeeded",
    "amount": 10000,
    "net_amount": 10000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 10000,
    "connector": "adyen",
    "client_secret": "pay_JMfKcXwsfYNl06UWsCc0_secret_IHW3HhesmOpMBaUyGd8l",
    "created": "2025-06-03T11:07:12.121Z",
    "currency": "USD",
    "customer_id": "cu_1748948832",
    "customer": {
        "id": "cu_1748948832",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0335",
            "card_type": "DEBIT",
            "card_network": "Visa",
            "card_issuer": "VISA U.S.A. INC.",
            "card_issuing_country": "UNITEDSTATES",
            "card_isin": "400003",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "San",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": "ss"
        },
        "phone": null,
        "email": "raj@gmail.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "http://127.0.0.1:4040/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "debit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "cu_1748948832",
        "created_at": 1748948832,
        "expires": 1748952432,
        "secret": "epk_caf2195bee8848358df1077b25cf2a73"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "DF4NDC3W8WH68775",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_JMfKcXwsfYNl06UWsCc0_2",
    "payment_link": null,
    "profile_id": "pro_gEwS6ve9KVqywWL0hmAq",
    "surcharge_details": null,
    "attempt_count": 2,
    "merchant_decision": null,
    "merchant_connector_id": "mca_zMyPaKk9qUVauLL7wFrV",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-06-03T11:22:12.120Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "125.0.0.1",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
        "color_depth": 24,
        "java_enabled": true,
        "screen_width": 1536,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 723,
        "java_script_enabled": true
    },
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2025-06-03T11:07:15.083Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null
}
```
-> the payment response contains two attempts
-> List all the attempts for a payments
```
curl --location 'http://localhost:8080/payments/pay_JMfKcXwsfYNl06UWsCc0?force_sync=true&expand_attempts=true' \
--header 'Accept: application/json' \
--header 'api-key: '
```
```
{
    "payment_id": "pay_JMfKcXwsfYNl06UWsCc0",
    "merchant_id": "merchant_1748851578",
    "status": "succeeded",
    "amount": 10000,
    "net_amount": 10000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 10000,
    "connector": "adyen",
    "client_secret": "pay_JMfKcXwsfYNl06UWsCc0_secret_IHW3HhesmOpMBaUyGd8l",
    "created": "2025-06-03T11:07:12.121Z",
    "currency": "USD",
    "customer_id": "cu_1748948832",
    "customer": {
        "id": "cu_1748948832",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "attempts": [
        {
            "attempt_id": "pay_JMfKcXwsfYNl06UWsCc0_2",
            "status": "charged",
            "amount": 10000,
            "order_tax_amount": null,
            "currency": "USD",
            "connector": "adyen",
            "error_message": null,
            "payment_method": "card",
            "connector_transaction_id": "DF4NDC3W8WH68775",
            "capture_method": "automatic",
            "authentication_type": "no_three_ds",
            "created_at": "2025-06-03T11:07:14.071Z",
            "modified_at": "2025-06-03T11:07:15.143Z",
            "cancellation_reason": null,
            "mandate_id": null,
            "error_code": null,
            "payment_token": null,
            "connector_metadata": null,
            "payment_experience": null,
            "payment_method_type": "debit",
            "reference_id": "pay_JMfKcXwsfYNl06UWsCc0_2",
            "unified_code": null,
            "unified_message": null,
            "client_source": null,
            "client_version": null
        },
        {
            "attempt_id": "pay_JMfKcXwsfYNl06UWsCc0_1",
            "status": "failure",
            "amount": 10000,
            "order_tax_amount": null,
            "currency": "USD",
            "connector": "adyen",
            "error_message": "Invalid variant",
            "payment_method": "card",
            "connector_transaction_id": "ZBRJ4V4RGLQD2HV5",
            "capture_method": "automatic",
            "authentication_type": "no_three_ds",
            "created_at": "2025-06-03T11:07:12.121Z",
            "modified_at": "2025-06-03T11:07:14.075Z",
            "cancellation_reason": null,
            "mandate_id": null,
            "error_code": "109",
            "payment_token": null,
            "connector_metadata": null,
            "payment_experience": null,
            "payment_method_type": "debit",
            "reference_id": null,
            "unified_code": null,
            "unified_message": null,
            "client_source": null,
            "client_version": null
        }
    ],
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0335",
            "card_type": "DEBIT",
            "card_network": "Visa",
            "card_issuer": "VISA U.S.A. INC.",
            "card_issuing_country": "UNITEDSTATES",
            "card_isin": "400003",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "San",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": "ss"
        },
        "phone": null,
        "email": "raj@gmail.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "http://127.0.0.1:4040/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "debit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": false,
    "connector_transaction_id": "DF4NDC3W8WH68775",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_JMfKcXwsfYNl06UWsCc0_2",
    "payment_link": null,
    "profile_id": "pro_gEwS6ve9KVqywWL0hmAq",
    "surcharge_details": null,
    "attempt_count": 2,
    "merchant_decision": null,
    "merchant_connector_id": "mca_zMyPaKk9qUVauLL7wFrV",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-06-03T11:22:12.120Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "125.0.0.1",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
        "color_depth": 24,
        "java_enabled": true,
        "screen_width": 1536,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 723,
        "java_script_enabled": true
    },
    "payment_method_id": "pm_d6OTFivsWBpZpC9u53Hh",
    "payment_method_status": "active",
    "updated": "2025-06-03T11:07:15.083Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null
}
```
-> The payment was first tried with `star` card network and then with the `visa`
![image](https://github.com/user-attachments/assets/9167038f-b03b-411a-8d13-a52b14c9685e)
![image](https://github.com/user-attachments/assets/7f1f1120-b3ff-4556-9140-248f922b279b)

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