Bug: [FEATURE] Use proxy exclusion instead of a separate proxied client



### Feature Description

Currently we provide the option for proxying all external requests made to Connectors. We also use reqwest clients to make API calls to basilisk/locker or other utilities.

Recently we started reusing the `reqwest::Client` (via once_cell) for performance reasons
Our current approach involves maintaining 2 clients:
- Proxied Client for any requests going outside our system
- Non-Proxied client for internal services

Currently we need to maintain a list of the services that should bypass proxy & choose a client accordingly. This adds a bit of a boilerplate whenever we try to make requests.


### Possible Implementation

Expected behavior:

Instead we want to push this behavior down to the reqwest library…
we believe the [no_proxy](https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.no_proxy) function should help us avoid this maintenance.

We would ideally accept a list of `proxy_bypass_urls` as part of our config which would then be used to create a single `reqwest::Client`  which can be used for internal + external services

### Have you spent some time to check if this feature request has been raised before?

- [X] I checked and didn't find similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

No, I don't have time to work on this right now
diff --git a/config/config.example.toml b/config/config.example.toml
index eb30435d5f8..821e854c9fa 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -23,11 +23,10 @@ certificate = "/path/to/certificate.pem"
 # Proxy server configuration for connecting to payment gateways.
 # Don't define the fields if a Proxy isn't needed. Empty strings will cause failure.
 [proxy]
-# http_url = "http proxy url"   # Proxy all HTTP traffic via this proxy
-# https_url = "https proxy url" # Proxy all HTTPS traffic via this proxy
-idle_pool_connection_timeout = 90 # Timeout for idle pool connections (defaults to 90s)
-bypass_proxy_urls = []            # A list of URLs that should bypass the proxy
-
+# http_url = "http proxy url"                   # Proxy all HTTP traffic via this proxy
+# https_url = "https proxy url"                 # Proxy all HTTPS traffic via this proxy
+idle_pool_connection_timeout = 90               # Timeout for idle pool connections (defaults to 90s)
+bypass_proxy_hosts = "localhost, cluster.local" # A comma-separated list of domains or IP addresses that should not use the proxy. Whitespace between entries would be ignored.
 
 # Configuration for the Key Manager Service
 [key_manager]
@@ -468,8 +467,8 @@ bank_redirect.giropay = { connector_list = "adyen,globalpay" }
 
 
 [mandates.update_mandate_supported]
-card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card 
-card.debit = { connector_list = "cybersource" }  # Update Mandate supported payment method type and connector for card 
+card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card
+card.debit = { connector_list = "cybersource" }  # Update Mandate supported payment method type and connector for card
 
 # Required fields info used while listing the payment_method_data
 [required_fields.pay_later] # payment_method = "pay_later"
diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml
index 809edf1bac6..84550922d4a 100644
--- a/config/deployments/env_specific.toml
+++ b/config/deployments/env_specific.toml
@@ -195,9 +195,9 @@ pm_auth_key = "pm_auth_key" # Payment method auth key used for authorization
 redis_expiry = 900          # Redis expiry time in milliseconds
 
 [proxy]
-http_url = "http://proxy_http_url"    # Outgoing proxy http URL to proxy the HTTP traffic
-https_url = "https://proxy_https_url" # Outgoing proxy https URL to proxy the HTTPS traffic
-bypass_proxy_urls = []                # A list of URLs that should bypass the proxy
+http_url = "http://proxy_http_url"              # Proxy all HTTP traffic via this proxy
+https_url = "https://proxy_https_url"           # Proxy all HTTPS traffic via this proxy
+bypass_proxy_hosts = "localhost, cluster.local" # A comma-separated list of domains or IP addresses that should not use the proxy. Whitespace between entries would be ignored.
 
 # Redis credentials
 [redis]
@@ -306,8 +306,8 @@ enabled = false
 global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
 
 [multitenancy.tenants.public]
-base_url = "http://localhost:8080" 
-schema = "public"     
+base_url = "http://localhost:8080"
+schema = "public"
 redis_key_prefix = ""
 clickhouse_database = "default"
 
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 0833ab37e3f..7a3ff787497 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -63,6 +63,8 @@ pub enum ApiClientError {
     #[error("Unexpected state reached/Invariants conflicted")]
     UnexpectedState,
 
+    #[error("Failed to parse URL")]
+    UrlParsingFailed,
     #[error("URL encoding of request payload failed")]
     UrlEncodingFailed,
     #[error("Failed to send request to connector {0}")]
diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs
index e18ed318be1..63dc9a58632 100644
--- a/crates/router/src/bin/scheduler.rs
+++ b/crates/router/src/bin/scheduler.rs
@@ -35,15 +35,8 @@ async fn main() -> CustomResult<(), ProcessTrackerError> {
     let conf = Settings::with_config_path(cmd_line.config_path)
         .expect("Unable to construct application configuration");
     let api_client = Box::new(
-        services::ProxyClient::new(
-            conf.proxy.clone(),
-            services::proxy_bypass_urls(
-                conf.key_manager.get_inner(),
-                &conf.locker,
-                &conf.proxy.bypass_proxy_urls,
-            ),
-        )
-        .change_context(ProcessTrackerError::ConfigurationError)?,
+        services::ProxyClient::new(&conf.proxy)
+            .change_context(ProcessTrackerError::ConfigurationError)?,
     );
     // channel for listening to redis disconnect events
     let (redis_shutdown_signal_tx, redis_shutdown_signal_rx) = oneshot::channel();
diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs
index 3c38fa2a775..8912d551f91 100644
--- a/crates/router/src/configs/defaults.rs
+++ b/crates/router/src/configs/defaults.rs
@@ -59,7 +59,7 @@ impl Default for super::settings::Proxy {
             http_url: Default::default(),
             https_url: Default::default(),
             idle_pool_connection_timeout: Some(90),
-            bypass_proxy_urls: Vec::new(),
+            bypass_proxy_hosts: Default::default(),
         }
     }
 }
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 9d606a9e607..577b29b0534 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -638,7 +638,7 @@ pub struct Proxy {
     pub http_url: Option<String>,
     pub https_url: Option<String>,
     pub idle_pool_connection_timeout: Option<u64>,
-    pub bypass_proxy_urls: Vec<String>,
+    pub bypass_proxy_hosts: Option<String>,
 }
 
 #[derive(Debug, Deserialize, Clone)]
@@ -828,7 +828,6 @@ impl Settings<SecuredSecret> {
                     .with_list_parse_key("log.telemetry.route_to_trace")
                     .with_list_parse_key("redis.cluster_urls")
                     .with_list_parse_key("events.kafka.brokers")
-                    .with_list_parse_key("proxy.bypass_proxy_urls")
                     .with_list_parse_key("connectors.supported.wallets")
                     .with_list_parse_key("connector_request_reference_id_config.merchant_ids_send_payment_id_as_connector_request_id"),
 
diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs
index 8c2bca5a82e..9febfa5e469 100644
--- a/crates/router/src/lib.rs
+++ b/crates/router/src/lib.rs
@@ -237,19 +237,9 @@ pub async fn start_server(conf: settings::Settings<SecuredSecret>) -> Applicatio
     logger::debug!(startup_config=?conf);
     let server = conf.server.clone();
     let (tx, rx) = oneshot::channel();
-    let api_client = Box::new(
-        services::ProxyClient::new(
-            conf.proxy.clone(),
-            services::proxy_bypass_urls(
-                conf.key_manager.get_inner(),
-                &conf.locker,
-                &conf.proxy.bypass_proxy_urls,
-            ),
-        )
-        .map_err(|error| {
-            errors::ApplicationError::ApiClientError(error.current_context().clone())
-        })?,
-    );
+    let api_client = Box::new(services::ProxyClient::new(&conf.proxy).map_err(|error| {
+        errors::ApplicationError::ApiClientError(error.current_context().clone())
+    })?);
     let state = Box::pin(AppState::new(conf, tx, api_client)).await;
     let request_body_limit = server.request_body_limit;
 
diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs
index 97801c1b4a4..0a56ff83f98 100644
--- a/crates/router/src/services/api.rs
+++ b/crates/router/src/services/api.rs
@@ -17,7 +17,7 @@ use actix_web::{
     http::header::{HeaderName, HeaderValue},
     web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
 };
-pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient};
+pub use client::{ApiClient, MockApiClient, ProxyClient};
 pub use common_enums::enums::PaymentAction;
 pub use common_utils::request::{ContentType, Method, Request, RequestBuilder};
 use common_utils::{
@@ -416,29 +416,11 @@ pub async fn send_request(
 ) -> CustomResult<reqwest::Response, errors::ApiClientError> {
     logger::info!(method=?request.method, headers=?request.headers, payload=?request.body, ?request);
 
-    let url = reqwest::Url::parse(&request.url)
-        .change_context(errors::ApiClientError::UrlEncodingFailed)?;
-
-    #[cfg(feature = "dummy_connector")]
-    let should_bypass_proxy = url
-        .as_str()
-        .starts_with(&state.conf.connectors.dummyconnector.base_url)
-        || proxy_bypass_urls(
-            state.conf.key_manager.get_inner(),
-            &state.conf.locker,
-            &state.conf.proxy.bypass_proxy_urls,
-        )
-        .contains(&url.to_string());
-    #[cfg(not(feature = "dummy_connector"))]
-    let should_bypass_proxy = proxy_bypass_urls(
-        &state.conf.key_manager.get_inner(),
-        &state.conf.locker,
-        &state.conf.proxy.bypass_proxy_urls,
-    )
-    .contains(&url.to_string());
+    let url =
+        url::Url::parse(&request.url).change_context(errors::ApiClientError::UrlParsingFailed)?;
+
     let client = client::create_client(
         &state.conf.proxy,
-        should_bypass_proxy,
         request.certificate,
         request.certificate_key,
     )?;
diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs
index 9a496c18d9a..096c5ed45d1 100644
--- a/crates/router/src/services/api/client.rs
+++ b/crates/router/src/services/api/client.rs
@@ -10,18 +10,16 @@ use router_env::tracing_actix_web::RequestId;
 
 use super::{request::Maskable, Request};
 use crate::{
-    configs::settings::{Locker, Proxy},
-    consts::{BASE64_ENGINE, LOCKER_HEALTH_CALL_PATH},
+    configs::settings::Proxy,
+    consts::BASE64_ENGINE,
     core::errors::{ApiClientError, CustomResult},
-    routes::{app::settings::KeyManagerConfig, SessionState},
+    routes::SessionState,
 };
 
-static NON_PROXIED_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();
-static PROXIED_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();
+static DEFAULT_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();
 
 fn get_client_builder(
     proxy_config: &Proxy,
-    should_bypass_proxy: bool,
 ) -> CustomResult<reqwest::ClientBuilder, ApiClientError> {
     let mut client_builder = reqwest::Client::builder()
         .redirect(reqwest::redirect::Policy::none())
@@ -31,16 +29,16 @@ fn get_client_builder(
                 .unwrap_or_default(),
         ));
 
-    if should_bypass_proxy {
-        return Ok(client_builder);
-    }
+    let proxy_exclusion_config =
+        reqwest::NoProxy::from_string(&proxy_config.bypass_proxy_hosts.clone().unwrap_or_default());
 
     // Proxy all HTTPS traffic through the configured HTTPS proxy
     if let Some(url) = proxy_config.https_url.as_ref() {
         client_builder = client_builder.proxy(
             reqwest::Proxy::https(url)
                 .change_context(ApiClientError::InvalidProxyConfiguration)
-                .attach_printable("HTTPS proxy configuration error")?,
+                .attach_printable("HTTPS proxy configuration error")?
+                .no_proxy(proxy_exclusion_config.clone()),
         );
     }
 
@@ -49,44 +47,35 @@ fn get_client_builder(
         client_builder = client_builder.proxy(
             reqwest::Proxy::http(url)
                 .change_context(ApiClientError::InvalidProxyConfiguration)
-                .attach_printable("HTTP proxy configuration error")?,
+                .attach_printable("HTTP proxy configuration error")?
+                .no_proxy(proxy_exclusion_config),
         );
     }
 
     Ok(client_builder)
 }
 
-fn get_base_client(
-    proxy_config: &Proxy,
-    should_bypass_proxy: bool,
-) -> CustomResult<reqwest::Client, ApiClientError> {
-    Ok(if should_bypass_proxy
-        || (proxy_config.http_url.is_none() && proxy_config.https_url.is_none())
-    {
-        &NON_PROXIED_CLIENT
-    } else {
-        &PROXIED_CLIENT
-    }
-    .get_or_try_init(|| {
-        get_client_builder(proxy_config, should_bypass_proxy)?
-            .build()
-            .change_context(ApiClientError::ClientConstructionFailed)
-            .attach_printable("Failed to construct base client")
-    })?
-    .clone())
+fn get_base_client(proxy_config: &Proxy) -> CustomResult<reqwest::Client, ApiClientError> {
+    Ok(DEFAULT_CLIENT
+        .get_or_try_init(|| {
+            get_client_builder(proxy_config)?
+                .build()
+                .change_context(ApiClientError::ClientConstructionFailed)
+                .attach_printable("Failed to construct base client")
+        })?
+        .clone())
 }
 
 // We may need to use outbound proxy to connect to external world.
 // Precedence will be the environment variables, followed by the config.
 pub fn create_client(
     proxy_config: &Proxy,
-    should_bypass_proxy: bool,
     client_certificate: Option<masking::Secret<String>>,
     client_certificate_key: Option<masking::Secret<String>>,
 ) -> CustomResult<reqwest::Client, ApiClientError> {
     match (client_certificate, client_certificate_key) {
         (Some(encoded_certificate), Some(encoded_certificate_key)) => {
-            let client_builder = get_client_builder(proxy_config, should_bypass_proxy)?;
+            let client_builder = get_client_builder(proxy_config)?;
 
             let identity = create_identity_from_certificate_and_key(
                 encoded_certificate.clone(),
@@ -105,7 +94,7 @@ pub fn create_client(
                 .change_context(ApiClientError::ClientConstructionFailed)
                 .attach_printable("Failed to construct client with certificate and certificate key")
         }
-        _ => get_base_client(proxy_config, should_bypass_proxy),
+        _ => get_base_client(proxy_config),
     }
 }
 
@@ -145,35 +134,6 @@ pub fn create_certificate(
         .change_context(ApiClientError::CertificateDecodeFailed)
 }
 
-pub fn proxy_bypass_urls(
-    key_manager: &KeyManagerConfig,
-    locker: &Locker,
-    config_whitelist: &[String],
-) -> Vec<String> {
-    let key_manager_host = key_manager.url.to_owned();
-    let locker_host = locker.host.to_owned();
-    let locker_host_rs = locker.host_rs.to_owned();
-
-    let proxy_list = [
-        format!("{locker_host}/cards/add"),
-        format!("{locker_host}/cards/fingerprint"),
-        format!("{locker_host}/cards/retrieve"),
-        format!("{locker_host}/cards/delete"),
-        format!("{locker_host_rs}/cards/add"),
-        format!("{locker_host_rs}/cards/retrieve"),
-        format!("{locker_host_rs}/cards/delete"),
-        format!("{locker_host_rs}{}", LOCKER_HEALTH_CALL_PATH),
-        format!("{locker_host}/card/addCard"),
-        format!("{locker_host}/card/getCard"),
-        format!("{locker_host}/card/deleteCard"),
-        format!("{key_manager_host}/data/encrypt"),
-        format!("{key_manager_host}/data/decrypt"),
-        format!("{key_manager_host}/key/create"),
-        format!("{key_manager_host}/key/rotate"),
-    ];
-    [&proxy_list, config_whitelist].concat().to_vec()
-}
-
 pub trait RequestBuilder: Send + Sync {
     fn json(&mut self, body: serde_json::Value);
     fn url_encoded_form(&mut self, body: serde_json::Value);
@@ -201,6 +161,7 @@ where
         method: Method,
         url: String,
     ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError>;
+
     fn request_with_certificate(
         &self,
         method: Method,
@@ -218,7 +179,9 @@ where
     ) -> CustomResult<reqwest::Response, ApiClientError>;
 
     fn add_request_id(&mut self, request_id: RequestId);
+
     fn get_request_id(&self) -> Option<String>;
+
     fn add_flow_name(&mut self, flow_name: String);
 }
 
@@ -226,60 +189,31 @@ dyn_clone::clone_trait_object!(ApiClient);
 
 #[derive(Clone)]
 pub struct ProxyClient {
-    proxy_client: reqwest::Client,
-    non_proxy_client: reqwest::Client,
-    whitelisted_urls: Vec<String>,
+    proxy_config: Proxy,
+    client: reqwest::Client,
     request_id: Option<String>,
 }
 
 impl ProxyClient {
-    pub fn new(
-        proxy_config: Proxy,
-        whitelisted_urls: Vec<String>,
-    ) -> CustomResult<Self, ApiClientError> {
-        let non_proxy_client = reqwest::Client::builder()
-            .redirect(reqwest::redirect::Policy::none())
-            .build()
-            .change_context(ApiClientError::ClientConstructionFailed)?;
-
-        let mut proxy_builder =
-            reqwest::Client::builder().redirect(reqwest::redirect::Policy::none());
-
-        if let Some(url) = proxy_config.https_url.as_ref() {
-            proxy_builder = proxy_builder.proxy(
-                reqwest::Proxy::https(url)
-                    .change_context(ApiClientError::InvalidProxyConfiguration)?,
-            );
-        }
-
-        if let Some(url) = proxy_config.http_url.as_ref() {
-            proxy_builder = proxy_builder.proxy(
-                reqwest::Proxy::http(url)
-                    .change_context(ApiClientError::InvalidProxyConfiguration)?,
-            );
-        }
-
-        let proxy_client = proxy_builder
+    pub fn new(proxy_config: &Proxy) -> CustomResult<Self, ApiClientError> {
+        let client = get_client_builder(proxy_config)?
             .build()
             .change_context(ApiClientError::InvalidProxyConfiguration)?;
         Ok(Self {
-            proxy_client,
-            non_proxy_client,
-            whitelisted_urls,
+            proxy_config: proxy_config.clone(),
+            client,
             request_id: None,
         })
     }
 
     pub fn get_reqwest_client(
         &self,
-        base_url: String,
         client_certificate: Option<masking::Secret<String>>,
         client_certificate_key: Option<masking::Secret<String>>,
     ) -> CustomResult<reqwest::Client, ApiClientError> {
         match (client_certificate, client_certificate_key) {
             (Some(certificate), Some(certificate_key)) => {
-                let client_builder =
-                    reqwest::Client::builder().redirect(reqwest::redirect::Policy::none());
+                let client_builder = get_client_builder(&self.proxy_config)?;
                 let identity =
                     create_identity_from_certificate_and_key(certificate, certificate_key)?;
                 Ok(client_builder
@@ -290,13 +224,7 @@ impl ProxyClient {
                         "Failed to construct client with certificate and certificate key",
                     )?)
             }
-            (_, _) => {
-                if self.whitelisted_urls.contains(&base_url) {
-                    Ok(self.non_proxy_client.clone())
-                } else {
-                    Ok(self.proxy_client.clone())
-                }
-            }
+            (_, _) => Ok(self.client.clone()),
         }
     }
 }
@@ -355,8 +283,6 @@ impl RequestBuilder for RouterRequestBuilder {
     }
 }
 
-// TODO: remove this when integrating this trait
-#[allow(dead_code)]
 #[async_trait::async_trait]
 impl ApiClient for ProxyClient {
     fn request(
@@ -375,7 +301,7 @@ impl ApiClient for ProxyClient {
         certificate_key: Option<masking::Secret<String>>,
     ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
         let client_builder = self
-            .get_reqwest_client(url.clone(), certificate, certificate_key)
+            .get_reqwest_client(certificate, certificate_key)
             .change_context(ApiClientError::ClientConstructionFailed)?;
         Ok(Box::new(RouterRequestBuilder {
             inner: Some(client_builder.request(method, url)),
diff --git a/crates/router/src/services/openidconnect.rs b/crates/router/src/services/openidconnect.rs
index 4f5056f3273..0c0f86431a1 100644
--- a/crates/router/src/services/openidconnect.rs
+++ b/crates/router/src/services/openidconnect.rs
@@ -155,7 +155,7 @@ async fn get_oidc_reqwest_client(
     state: &SessionState,
     request: oidc::HttpRequest,
 ) -> Result<oidc::HttpResponse, ApiClientError> {
-    let client = client::create_client(&state.conf.proxy, false, None, None)
+    let client = client::create_client(&state.conf.proxy, None, None)
         .map_err(|e| e.current_context().to_owned())?;
 
     let mut request_builder = client

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

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

## Description
<!-- Describe your changes in detail -->

This PR refactors the logic for excluding specific HTTP / HTTPS traffic from being proxied via the outgoing proxy. Specifically, the changes included are:

1. We no longer need two clients (a proxied client and a non-proxied client), we can have the same client handle traffic as required.
2. We need not specify complete URLs for proxy exclusion, we can just specify the domain names or IP addresses of the hosts to be excluded. This also handles excluding traffic from a subdomain if a parent domain is excluded.

This is achieved with [`reqwest::Proxy::no_proxy()`](https://docs.rs/reqwest/0.12.11/reqwest/struct.Proxy.html#method.no_proxy) and [`reqwest::NoProxy::from_string()`](https://docs.rs/reqwest/0.12.11/reqwest/struct.NoProxy.html#method.from_string).

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [x] 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`
4. `crates/router/src/configs`
5. `loadtest/config`
-->

This PR introduces a config option `bypass_proxy_hosts` instead of `bypass_proxy_urls` under the `[proxy]` config section.

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

This would help significantly reduce maintenance efforts on our side with respect to which URLs we wanted to be excluded from being proxied, since we previously had some of these URLs being specified in code. The PR helps ensure that all the domains to be excluded would be specified via configuration alone, while also allowing us to easily exclude subdomains (for example, excluding `cluster.local` would exclude all traffic to services within a Kubernetes cluster from being proxied).

Closes #1039.

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

Locally, by setting up [`mitmproxy`](https://mitmproxy.org) as a proxy for outgoing HTTP and HTTPS traffic. I also set up our card vault locally to simulate an internal service running close to the application.

I ran `mitmproxy` using the command:

```shell
mitmweb --listen-port 8090 --web-port 8091
```

Application was configured in the `development.toml` file to use the proxy:

```toml
[proxy]
http_url = "http://localhost:8090"
https_url = "http://localhost:8090"
bypass_proxy_hosts = "localhost, cluster.local"
```

1. Creating a payment via Postman:

   ![Screenshot of 2 requests being proxied](https://github.com/user-attachments/assets/4725232e-c513-4b2d-960f-7bf063b8d51a)

   Two requests are listed on the `mitmproxy` web interface, one to `api.stripe.com` and one to `webhook.site`, indicating that they were proxied. In this case, all HTTPS traffic is being proxied.

2. Saving a payment method in the locker:

   The locker host is initially configured as:

   ```toml
   [locker]
   host = "http://127.0.0.1:3001"
   mock_locker = false
   ```

   Since the locker host was `127.0.0.1` and only `localhost` was excluded from being proxied, the locker request is also being proxied:

   ![Screenshot of card vault request being proxied](https://github.com/user-attachments/assets/e272180a-a0fc-4d46-9cba-460e7f8f1e42)

   On updating the locker host to `http://localhost:3001` in the `development.toml` file, the locker request is no longer proxied, the `mitmproxy` web interface is blank:

   ![Screenshot of a blank mitmproxy web interface](https://github.com/user-attachments/assets/bca62207-4074-4ef1-9ac0-4ae20fb99da0)

   As another confirmation that the locker call actually happened, I see logs emitted by the locker application with the same request ID as that returned by the `router` in its response headers.

   One more thing to note here is that we did not have to include the port number (`localhost:3001`) in the `bypass_proxy_hosts` list, only `localhost` was sufficient.

3. Excluding `stripe.com` from being proxied and creating a payment:

   I updated the `development.toml` file to include `stripe.com` in `bypass_proxy_hosts`:

   ```toml
   bypass_proxy_hosts = "localhost, cluster.local, stripe.com"
   ```

   Even though we only excluded `stripe.com` and did not explicitly exclude `api.stripe.com`, the request to `api.stripe.com` was excluded as well. This can be compared with the screenshot in (1), the request to `api.stripe.com` is not listed:

   ![Screenshot of a request being proxied](https://github.com/user-attachments/assets/daa865d9-b768-4c42-9da3-c054cb3342b5)

(2) and (3) indicate that proxy exclusion is happening as expected.

### Testing on our hosted environments

In case of our hosted environments, we would have to ensure that nothing with respect to outgoing traffic from the `router` application remains affected: if an outgoing proxy has been configured (correctly), all of the external HTTP(S) traffic (to connectors, etc.) must be proxied, while traffic to our internal services such as our locker or encryption service must be excluded from being proxied.

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