Bug: Use NRID in recurring payments if the merchant config is enabled



During the MIT process, there needs to be validation to check the off_session field. If set to true, then check if the `pg_agnostic_mit` config is enabled. If it is, perform a lookup in the payment methods table with the `payment_method_id` in the MIT payment request for the NRID. Retrieve the card details from the locker for the specific `payment_method_id`. Make the confirm call to the connector decided by routing.

If the confirm call fails, retry across the retry-able connectors specified in the pg_agnostic_mit configuration.

If the NRID is not present for that particular `payment_method_id`, or if the `pg_agnostic_mit` flag is not enabled, call the initial connector with which the CIT was performed.
diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs
index 45aeb1e9074..19a37ab0fb7 100644
--- a/crates/router/src/connector/stripe/transformers.rs
+++ b/crates/router/src/connector/stripe/transformers.rs
@@ -136,6 +136,7 @@ pub struct PaymentIntentRequest {
     #[serde(flatten)]
     pub payment_data: Option<StripePaymentMethodData>,
     pub capture_method: StripeCaptureMethod,
+    #[serde(flatten)]
     pub payment_method_options: Option<StripePaymentMethodOptions>, // For mandate txns using network_txns_id, needs to be validated
     pub setup_future_usage: Option<enums::FutureUsage>,
     pub off_session: Option<bool>,
@@ -189,9 +190,9 @@ pub struct StripeCardData {
     #[serde(rename = "payment_method_data[card][exp_year]")]
     pub payment_method_data_card_exp_year: Secret<String>,
     #[serde(rename = "payment_method_data[card][cvc]")]
-    pub payment_method_data_card_cvc: Secret<String>,
+    pub payment_method_data_card_cvc: Option<Secret<String>>,
     #[serde(rename = "payment_method_options[card][request_three_d_secure]")]
-    pub payment_method_auth_type: Auth3ds,
+    pub payment_method_auth_type: Option<Auth3ds>,
 }
 #[derive(Debug, Eq, PartialEq, Serialize)]
 pub struct StripePayLaterData {
@@ -1498,8 +1499,8 @@ impl TryFrom<(&domain::Card, Auth3ds)> for StripePaymentMethodData {
             payment_method_data_card_number: card.card_number.clone(),
             payment_method_data_card_exp_month: card.card_exp_month.clone(),
             payment_method_data_card_exp_year: card.card_exp_year.clone(),
-            payment_method_data_card_cvc: card.card_cvc.clone(),
-            payment_method_auth_type,
+            payment_method_data_card_cvc: Some(card.card_cvc.clone()),
+            payment_method_auth_type: Some(payment_method_auth_type),
         }))
     }
 }
@@ -1821,7 +1822,44 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
                             network_transaction_id: Secret::new(network_transaction_id),
                         }),
                     });
-                    (None, None, StripeBillingAddress::default(), None)
+
+                    let payment_data = match item.request.payment_method_data {
+                        domain::payments::PaymentMethodData::Card(ref card) => {
+                            StripePaymentMethodData::Card(StripeCardData {
+                                payment_method_data_type: StripePaymentMethodType::Card,
+                                payment_method_data_card_number: card.card_number.clone(),
+                                payment_method_data_card_exp_month: card.card_exp_month.clone(),
+                                payment_method_data_card_exp_year: card.card_exp_year.clone(),
+                                payment_method_data_card_cvc: None,
+                                payment_method_auth_type: None,
+                            })
+                        }
+                        domain::payments::PaymentMethodData::CardRedirect(_)
+                        | domain::payments::PaymentMethodData::Wallet(_)
+                        | domain::payments::PaymentMethodData::PayLater(_)
+                        | domain::payments::PaymentMethodData::BankRedirect(_)
+                        | domain::payments::PaymentMethodData::BankDebit(_)
+                        | domain::payments::PaymentMethodData::BankTransfer(_)
+                        | domain::payments::PaymentMethodData::Crypto(_)
+                        | domain::payments::PaymentMethodData::MandatePayment
+                        | domain::payments::PaymentMethodData::Reward
+                        | domain::payments::PaymentMethodData::Upi(_)
+                        | domain::payments::PaymentMethodData::Voucher(_)
+                        | domain::payments::PaymentMethodData::GiftCard(_)
+                        | domain::payments::PaymentMethodData::CardToken(_) => {
+                            Err(errors::ConnectorError::NotSupported {
+                                message: "Network tokenization for payment method".to_string(),
+                                connector: "Stripe",
+                            })?
+                        }
+                    };
+
+                    (
+                        Some(payment_data),
+                        None,
+                        StripeBillingAddress::default(),
+                        None,
+                    )
                 }
                 _ => {
                     let (payment_method_data, payment_method_type, billing_address) =
@@ -3105,10 +3143,13 @@ impl TryFrom<&types::PaymentsCancelRouterData> for CancelRequest {
 #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
 #[non_exhaustive]
 #[serde(rename_all = "snake_case")]
+#[serde(untagged)]
 pub enum StripePaymentMethodOptions {
     Card {
         mandate_options: Option<StripeMandateOptions>,
+        #[serde(rename = "payment_method_options[card][network_transaction_id]")]
         network_transaction_id: Option<Secret<String>>,
+        #[serde(flatten)]
         mit_exemption: Option<MitExemption>, // To be used for MIT mandate txns
     },
     Klarna {},
@@ -3139,6 +3180,7 @@ pub enum StripePaymentMethodOptions {
 
 #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
 pub struct MitExemption {
+    #[serde(rename = "payment_method_options[card][mit_exemption][network_transaction_id]")]
     pub network_transaction_id: Secret<String>,
 }
 
diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs
index e7ec99f61b6..22f7cb311c6 100644
--- a/crates/router/src/core/payments.rs
+++ b/crates/router/src/core/payments.rs
@@ -2929,10 +2929,12 @@ where
             .attach_printable("Invalid connector name received")?;
 
         return decide_multiplex_connector_for_normal_or_recurring_payment(
+            &state,
             payment_data,
             routing_data,
             connector_data,
-        );
+        )
+        .await;
     }
 
     if let Some(ref routing_algorithm) = routing_data.routing_info.algorithm {
@@ -2983,10 +2985,12 @@ where
             .attach_printable("Invalid connector name received")?;
 
         return decide_multiplex_connector_for_normal_or_recurring_payment(
+            &state,
             payment_data,
             routing_data,
             connector_data,
-        );
+        )
+        .await;
     }
 
     route_connector_v1(
@@ -3001,7 +3005,8 @@ where
     .await
 }
 
-pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
+pub async fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
+    state: &AppState,
     payment_data: &mut PaymentData<F>,
     routing_data: &mut storage::RoutingData,
     connectors: Vec<api::ConnectorData>,
@@ -3010,9 +3015,11 @@ pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
         payment_data.payment_intent.setup_future_usage,
         payment_data.token_data.as_ref(),
         payment_data.recurring_details.as_ref(),
+        payment_data.payment_intent.off_session,
     ) {
-        (Some(storage_enums::FutureUsage::OffSession), Some(_), None)
-        | (None, None, Some(RecurringDetails::PaymentMethodId(_))) => {
+        (Some(storage_enums::FutureUsage::OffSession), Some(_), None, None)
+        | (None, None, Some(RecurringDetails::PaymentMethodId(_)), Some(true))
+        | (None, Some(_), None, Some(true)) => {
             logger::debug!("performing routing for token-based MIT flow");
 
             let payment_method_info = payment_data
@@ -3020,32 +3027,108 @@ pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
                 .as_ref()
                 .get_required_value("payment_method_info")?;
 
-            let connector_mandate_details = payment_method_info
-            .connector_mandate_details
-            .clone()
-            .map(|details| {
-                details.parse_value::<storage::PaymentsMandateReference>("connector_mandate_details")
-            })
-            .transpose()
-            .change_context(errors::ApiErrorResponse::InternalServerError)
-            .attach_printable("unable to deserialize connector mandate details")?
-            .get_required_value("connector_mandate_details")
-            .change_context(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
-            .attach_printable("no eligible connector found for token-based MIT flow since there were no connector mandate details")?;
+            let connector_mandate_details = &payment_method_info
+                .connector_mandate_details
+                .clone()
+                .map(|details| {
+                    details.parse_value::<storage::PaymentsMandateReference>(
+                        "connector_mandate_details",
+                    )
+                })
+                .transpose()
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("unable to deserialize connector mandate details")?;
+
+            let profile_id = payment_data
+                .payment_intent
+                .profile_id
+                .as_ref()
+                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?;
+
+            let pg_agnostic = state
+                .store
+                .find_config_by_key_unwrap_or(
+                    &format!("pg_agnostic_mandate_{}", profile_id),
+                    Some("false".to_string()),
+                )
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("The pg_agnostic config was not found in the DB")?;
 
             let mut connector_choice = None;
+
             for connector_data in connectors {
-                if let Some(merchant_connector_id) = connector_data.merchant_connector_id.as_ref() {
-                    if let Some(mandate_reference_record) =
-                        connector_mandate_details.get(merchant_connector_id)
+                let merchant_connector_id = connector_data
+                    .merchant_connector_id
+                    .as_ref()
+                    .ok_or(errors::ApiErrorResponse::InternalServerError)?;
+
+                if is_network_transaction_id_flow(
+                    state,
+                    &pg_agnostic.config,
+                    connector_data.connector_name,
+                    payment_method_info,
+                ) {
+                    let network_transaction_id = payment_method_info
+                        .network_transaction_id
+                        .as_ref()
+                        .ok_or(errors::ApiErrorResponse::InternalServerError)?;
+
+                    let mandate_reference_id =
+                        Some(payments_api::MandateReferenceId::NetworkMandateId(
+                            network_transaction_id.to_string(),
+                        ));
+
+                    connector_choice = Some((connector_data, mandate_reference_id.clone()));
+                    break;
+                } else if connector_mandate_details
+                    .clone()
+                    .map(|connector_mandate_details| {
+                        connector_mandate_details.contains_key(merchant_connector_id)
+                    })
+                    .unwrap_or(false)
+                {
+                    if let Some(merchant_connector_id) =
+                        connector_data.merchant_connector_id.as_ref()
                     {
-                        connector_choice = Some((connector_data, mandate_reference_record.clone()));
-                        break;
+                        if let Some(mandate_reference_record) = connector_mandate_details.clone()
+                        .get_required_value("connector_mandate_details")
+                            .change_context(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
+                            .attach_printable("no eligible connector found for token-based MIT flow since there were no connector mandate details")?
+                            .get(merchant_connector_id)
+                        {
+                            let mandate_reference_id =
+                                Some(payments_api::MandateReferenceId::ConnectorMandateId(
+                                    payments_api::ConnectorMandateReferenceId {
+                                        connector_mandate_id: Some(
+                                            mandate_reference_record.connector_mandate_id.clone(),
+                                        ),
+                                        payment_method_id: Some(
+                                            payment_method_info.payment_method_id.clone(),
+                                        ),
+                                        update_history: None,
+                                    },
+                                ));
+                            payment_data.recurring_mandate_payment_data =
+                                Some(RecurringMandatePaymentData {
+                                    payment_method_type: mandate_reference_record
+                                        .payment_method_type,
+                                    original_payment_authorized_amount: mandate_reference_record
+                                        .original_payment_authorized_amount,
+                                    original_payment_authorized_currency: mandate_reference_record
+                                        .original_payment_authorized_currency,
+                                });
+
+                            connector_choice = Some((connector_data, mandate_reference_id.clone()));
+                            break;
+                        }
                     }
+                } else {
+                    continue;
                 }
             }
 
-            let (chosen_connector_data, mandate_reference_record) = connector_choice
+            let (chosen_connector_data, mandate_reference_id) = connector_choice
                 .get_required_value("connector_choice")
                 .change_context(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)
                 .attach_printable("no eligible connector found for token-based MIT payment")?;
@@ -3056,26 +3139,16 @@ pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
                 routing_data.merchant_connector_id =
                     chosen_connector_data.merchant_connector_id.clone();
             }
+            routing_data.routed_through = Some(chosen_connector_data.connector_name.to_string());
+            #[cfg(feature = "connector_choice_mca_id")]
+            {
+                routing_data.merchant_connector_id =
+                    chosen_connector_data.merchant_connector_id.clone();
+            }
 
             payment_data.mandate_id = Some(payments_api::MandateIds {
                 mandate_id: None,
-                mandate_reference_id: Some(payments_api::MandateReferenceId::ConnectorMandateId(
-                    payments_api::ConnectorMandateReferenceId {
-                        connector_mandate_id: Some(
-                            mandate_reference_record.connector_mandate_id.clone(),
-                        ),
-                        payment_method_id: Some(payment_method_info.payment_method_id.clone()),
-                        update_history: None,
-                    },
-                )),
-            });
-
-            payment_data.recurring_mandate_payment_data = Some(RecurringMandatePaymentData {
-                payment_method_type: mandate_reference_record.payment_method_type,
-                original_payment_authorized_amount: mandate_reference_record
-                    .original_payment_authorized_amount,
-                original_payment_authorized_currency: mandate_reference_record
-                    .original_payment_authorized_currency,
+                mandate_reference_id,
             });
 
             Ok(api::ConnectorCallType::PreDetermined(chosen_connector_data))
@@ -3098,6 +3171,23 @@ pub fn decide_multiplex_connector_for_normal_or_recurring_payment<F: Clone>(
     }
 }
 
+pub fn is_network_transaction_id_flow(
+    state: &AppState,
+    pg_agnostic: &String,
+    connector: enums::Connector,
+    payment_method_info: &storage::PaymentMethod,
+) -> bool {
+    let ntid_supported_connectors = &state
+        .conf
+        .network_transaction_id_supported_connectors
+        .connector_list;
+
+    pg_agnostic == "true"
+        && payment_method_info.payment_method == storage_enums::PaymentMethod::Card
+        && ntid_supported_connectors.contains(&connector)
+        && payment_method_info.network_transaction_id.is_some()
+}
+
 pub fn should_add_task_to_process_tracker<F: Clone>(payment_data: &PaymentData<F>) -> bool {
     let connector = payment_data.payment_attempt.connector.as_deref();
 
@@ -3315,10 +3405,12 @@ where
     match transaction_data {
         TransactionData::Payment(payment_data) => {
             decide_multiplex_connector_for_normal_or_recurring_payment(
+                state,
                 payment_data,
                 routing_data,
                 connector_data,
             )
+            .await
         }
 
         #[cfg(feature = "payouts")]
diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs
index 4eac9c1f4de..5214ce3f7a7 100644
--- a/crates/router/src/core/payments/operations/payment_confirm.rs
+++ b/crates/router/src/core/payments/operations/payment_confirm.rs
@@ -413,8 +413,11 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
 
         payment_attempt.payment_method = payment_method.or(payment_attempt.payment_method);
         payment_attempt.browser_info = browser_info;
-        payment_attempt.payment_method_type =
-            payment_method_type.or(payment_attempt.payment_method_type);
+        payment_attempt.payment_method_type = payment_method_type
+            .or(payment_attempt.payment_method_type)
+            .or(payment_method_info
+                .as_ref()
+                .and_then(|pm_info| pm_info.payment_method_type));
 
         payment_attempt.payment_experience = request
             .payment_experience
diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs
index f418b660380..70cf702311d 100644
--- a/crates/router/src/core/payments/operations/payment_response.rs
+++ b/crates/router/src/core/payments/operations/payment_response.rs
@@ -972,18 +972,46 @@ async fn update_payment_method_status_and_ntid<F: Clone>(
             .find_payment_method(id)
             .await
             .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
-        let network_transaction_id = payment_response
-            .map(|resp| match resp {
-                crate::types::PaymentsResponseData::TransactionResponse { network_txn_id, .. } => {
-                    network_txn_id.to_owned()
+
+        let pm_resp_network_transaction_id = payment_response
+            .map(|resp| if let types::PaymentsResponseData::TransactionResponse { network_txn_id: network_transaction_id, .. } = resp {
+                network_transaction_id
+    } else {None})
+    .map_err(|err| {
+        logger::error!(error=?err, "Failed to obtain the network_transaction_id from payment response");
+    })
+    .ok()
+    .flatten();
+
+        let network_transaction_id =
+            if let Some(network_transaction_id) = pm_resp_network_transaction_id {
+                let profile_id = payment_data
+                    .payment_intent
+                    .profile_id
+                    .as_ref()
+                    .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?;
+
+                let pg_agnostic = state
+                    .store
+                    .find_config_by_key_unwrap_or(
+                        &format!("pg_agnostic_mandate_{}", profile_id),
+                        Some("false".to_string()),
+                    )
+                    .await
+                    .change_context(errors::ApiErrorResponse::InternalServerError)
+                    .attach_printable("The pg_agnostic config was not found in the DB")?;
+
+                if &pg_agnostic.config == "true" {
+                    Some(network_transaction_id)
+                } else {
+                    logger::info!(
+                        "Skip storing network transaction id as pg_agnostic config is not enabled"
+                    );
+                    None
                 }
-                _ => None,
-            })
-            .map_err(|err| {
-                logger::error!(error=?err, "Failed to obtain the network_transaction_id from payment response");
-            })
-            .ok()
-            .flatten();
+            } else {
+                None
+            };
 
         let pm_update = if pm.status != common_enums::PaymentMethodStatus::Active
             && pm.status != attempt_status.into()

## 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 -->
During the MIT process, there needs to be validation to check the off_session field. If set to true, then check if the pg_agnostic_mit config is enabled. If it is, perform a lookup in the payment methods table with the payment_method_id in the MIT payment request for the NRID.

If the confirm call fails, retry across the retry-able connectors specified in the `pg_agnostic_mit` configuration.

If the NRID is not present for that particular payment_method_id, or if the `pg_agnostic_mit` flag is not enabled, call the initial connector with which the CIT was performed.

### 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 Merchant account
Create a mca for stripe, adyen or cybersource
Enable the pg_agnostic endpoint
```
curl --location 'https://sandbox.hyperswitch.io/routing/business_profile/:business_profile_id/configs/pg_agnostic_mit' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer JWT_Token' \
--data '{
    "enabled": true
}'
```
Create a mandate payment (Setup mandate)
<img width="1107" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/3649e1f4-0e19-4c89-b978-e11120a96b92">

```
{
    "amount": 0,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "singh",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "joseph Doe",
            "card_cvc": "123"
        }
    },
    "setup_future_usage": "off_session",
    "payment_type": "setup_mandate",
        "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "127.0.0.1",
                "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": "John",
            "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": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "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": {},
    "order_details": [
        {
            "product_name": "Apple iphone 15",
            "quantity": 1,
            "amount": 0,
            "account_name": "transaction_processing"
        }
    ]
}
```
DB screenshot where `network_transaction_id` is stored in the `payment_methods` table
![image](https://github.com/juspay/hyperswitch/assets/83439957/f0dee8b8-193f-4013-9ebf-c749dc24643a)
Create a payment with confirm false
<img width="972" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/fce68823-ce21-4353-9908-c19ca4f1f468">
List payment methods for customer
```
curl --location 'http://localhost:8080/customers/:customer_id/payment_methods' \
--header 'Accept: application/json' \
--header 'api-key: api-key'
```
<img width="956" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/c0ea7f9e-3468-41ea-8c13-ee4cba57a293">
Confirm the payment by passing the connector as stripe in routing

```
{
    "amount": 300,
    "currency": "USD",
    "confirm": false,
    "customer_id": "cyb111",
    "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",
    "off_session": true,
    "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"
        }
    },

    "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"
    }
}
```

```
curl --location 'http://localhost:8080/payments/{{payment_id}}/confirm' \
--header 'api-key: api-key' \
--header 'Content-Type: application/json' \
--data '{
    "payment_token": "payment_token",
    "client_secret": "client_secret",
    "payment_method": "card"
}'
```
<img width="1021" alt="image" src="https://github.com/juspay/hyperswitch/assets/83439957/d8e496ba-90e7-431e-8877-a0aef6c6da28">



## 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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
