Bug: feat(analytics): Analytics Request Validator and config driven forex feature



## Current behaviour

- Forex api calls are occuring on every analytics api call
- There is no analytics request validator.

## Proposed Changes

- Enable/Disable forex functionality based on a config flag.
- `request_validator()` to validate requests on various grounds and throw contextual and comprehensive `4xx` errors.
- `request_validator()` should also be able to discern whether metrics passed in a request, require `forex_api` call.
diff --git a/config/config.example.toml b/config/config.example.toml
index 54a3ab3827b..fbeea44774b 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -660,6 +660,7 @@ pm_auth_key = "Some_pm_auth_key"
 # Analytics configuration.
 [analytics]
 source = "sqlx" # The Analytics source/strategy to be used
+forex_enabled = false # Enable or disable forex conversion for analytics
 
 [analytics.clickhouse]
 username = ""      # Clickhouse username
diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml
index 5cadc66ddfc..9db2474989e 100644
--- a/config/deployments/env_specific.toml
+++ b/config/deployments/env_specific.toml
@@ -9,6 +9,7 @@ database_name = "clickhouse_db_name" # Clickhouse database name
 # Analytics configuration.
 [analytics]
 source = "sqlx" # The Analytics source/strategy to be used
+forex_enabled = false # Boolean to enable or disable forex conversion
 
 [analytics.sqlx]
 username = "db_user"      # Analytics DB Username
diff --git a/config/development.toml b/config/development.toml
index 4e3a9b09993..c4920b2c029 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -717,6 +717,7 @@ authentication_analytics_topic = "hyperswitch-authentication-events"
 
 [analytics]
 source = "sqlx"
+forex_enabled = false
 
 [analytics.clickhouse]
 username = "default"
diff --git a/crates/analytics/docs/README.md b/crates/analytics/docs/README.md
index eb5c26a6ba3..e24dc6c5af7 100644
--- a/crates/analytics/docs/README.md
+++ b/crates/analytics/docs/README.md
@@ -104,6 +104,12 @@ To use Forex services, you need to sign up and get your API keys from the follow
    - It will be in dashboard, labeled as `access key`.
 
 ### Configuring Forex APIs
+To enable Forex functionality, update the `config/development.toml` or `config/docker_compose.toml` file:
+
+```toml
+[analytics]
+forex_enabled = true # default set to false 
+```
 
 To configure the Forex APIs, update the `config/development.toml` or `config/docker_compose.toml` file with your API keys:
 
diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs
index 6d694caadf5..0ad8886b820 100644
--- a/crates/analytics/src/lib.rs
+++ b/crates/analytics/src/lib.rs
@@ -969,21 +969,25 @@ impl AnalyticsProvider {
         tenant: &dyn storage_impl::config::TenantConfig,
     ) -> Self {
         match config {
-            AnalyticsConfig::Sqlx { sqlx } => {
+            AnalyticsConfig::Sqlx { sqlx, .. } => {
                 Self::Sqlx(SqlxClient::from_conf(sqlx, tenant.get_schema()).await)
             }
-            AnalyticsConfig::Clickhouse { clickhouse } => Self::Clickhouse(ClickhouseClient {
+            AnalyticsConfig::Clickhouse { clickhouse, .. } => Self::Clickhouse(ClickhouseClient {
                 config: Arc::new(clickhouse.clone()),
                 database: tenant.get_clickhouse_database().to_string(),
             }),
-            AnalyticsConfig::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh(
+            AnalyticsConfig::CombinedCkh {
+                sqlx, clickhouse, ..
+            } => Self::CombinedCkh(
                 SqlxClient::from_conf(sqlx, tenant.get_schema()).await,
                 ClickhouseClient {
                     config: Arc::new(clickhouse.clone()),
                     database: tenant.get_clickhouse_database().to_string(),
                 },
             ),
-            AnalyticsConfig::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx(
+            AnalyticsConfig::CombinedSqlx {
+                sqlx, clickhouse, ..
+            } => Self::CombinedSqlx(
                 SqlxClient::from_conf(sqlx, tenant.get_schema()).await,
                 ClickhouseClient {
                     config: Arc::new(clickhouse.clone()),
@@ -1000,20 +1004,35 @@ impl AnalyticsProvider {
 pub enum AnalyticsConfig {
     Sqlx {
         sqlx: Database,
+        forex_enabled: bool,
     },
     Clickhouse {
         clickhouse: ClickhouseConfig,
+        forex_enabled: bool,
     },
     CombinedCkh {
         sqlx: Database,
         clickhouse: ClickhouseConfig,
+        forex_enabled: bool,
     },
     CombinedSqlx {
         sqlx: Database,
         clickhouse: ClickhouseConfig,
+        forex_enabled: bool,
     },
 }
 
+impl AnalyticsConfig {
+    pub fn get_forex_enabled(&self) -> bool {
+        match self {
+            Self::Sqlx { forex_enabled, .. }
+            | Self::Clickhouse { forex_enabled, .. }
+            | Self::CombinedCkh { forex_enabled, .. }
+            | Self::CombinedSqlx { forex_enabled, .. } => *forex_enabled,
+        }
+    }
+}
+
 #[async_trait::async_trait]
 impl SecretsHandler for AnalyticsConfig {
     async fn convert_to_raw_secret(
@@ -1024,7 +1043,7 @@ impl SecretsHandler for AnalyticsConfig {
         let decrypted_password = match analytics_config {
             // Todo: Perform kms decryption of clickhouse password
             Self::Clickhouse { .. } => masking::Secret::new(String::default()),
-            Self::Sqlx { sqlx }
+            Self::Sqlx { sqlx, .. }
             | Self::CombinedCkh { sqlx, .. }
             | Self::CombinedSqlx { sqlx, .. } => {
                 secret_management_client
@@ -1034,26 +1053,46 @@ impl SecretsHandler for AnalyticsConfig {
         };
 
         Ok(value.transition_state(|conf| match conf {
-            Self::Sqlx { sqlx } => Self::Sqlx {
+            Self::Sqlx {
+                sqlx,
+                forex_enabled,
+            } => Self::Sqlx {
                 sqlx: Database {
                     password: decrypted_password,
                     ..sqlx
                 },
+                forex_enabled,
             },
-            Self::Clickhouse { clickhouse } => Self::Clickhouse { clickhouse },
-            Self::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh {
+            Self::Clickhouse {
+                clickhouse,
+                forex_enabled,
+            } => Self::Clickhouse {
+                clickhouse,
+                forex_enabled,
+            },
+            Self::CombinedCkh {
+                sqlx,
+                clickhouse,
+                forex_enabled,
+            } => Self::CombinedCkh {
                 sqlx: Database {
                     password: decrypted_password,
                     ..sqlx
                 },
                 clickhouse,
+                forex_enabled,
             },
-            Self::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx {
+            Self::CombinedSqlx {
+                sqlx,
+                clickhouse,
+                forex_enabled,
+            } => Self::CombinedSqlx {
                 sqlx: Database {
                     password: decrypted_password,
                     ..sqlx
                 },
                 clickhouse,
+                forex_enabled,
             },
         }))
     }
@@ -1063,6 +1102,7 @@ impl Default for AnalyticsConfig {
     fn default() -> Self {
         Self::Sqlx {
             sqlx: Database::default(),
+            forex_enabled: false,
         }
     }
 }
diff --git a/crates/analytics/src/payment_intents/core.rs b/crates/analytics/src/payment_intents/core.rs
index 80cfc563071..b5761cd8779 100644
--- a/crates/analytics/src/payment_intents/core.rs
+++ b/crates/analytics/src/payment_intents/core.rs
@@ -69,7 +69,7 @@ pub async fn get_sankey(
 #[instrument(skip_all)]
 pub async fn get_metrics(
     pool: &AnalyticsProvider,
-    ex_rates: &ExchangeRates,
+    ex_rates: &Option<ExchangeRates>,
     auth: &AuthInfo,
     req: GetPaymentIntentMetricRequest,
 ) -> AnalyticsResult<PaymentIntentsMetricsResponse<MetricsBucketResponse>> {
@@ -202,22 +202,25 @@ pub async fn get_metrics(
                 total += total_count;
             }
             if let Some(retried_amount) = collected_values.smart_retried_amount {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(retried_amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(retried_amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.smart_retried_amount_in_usd = amount_in_usd;
                 total_smart_retried_amount += retried_amount;
                 total_smart_retried_amount_in_usd += amount_in_usd.unwrap_or(0);
@@ -225,44 +228,50 @@ pub async fn get_metrics(
             if let Some(retried_amount) =
                 collected_values.smart_retried_amount_without_smart_retries
             {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(retried_amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(retried_amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.smart_retried_amount_without_smart_retries_in_usd = amount_in_usd;
                 total_smart_retried_amount_without_smart_retries += retried_amount;
                 total_smart_retried_amount_without_smart_retries_in_usd +=
                     amount_in_usd.unwrap_or(0);
             }
             if let Some(amount) = collected_values.payment_processed_amount {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.payment_processed_amount_in_usd = amount_in_usd;
                 total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
                 total_payment_processed_amount += amount;
@@ -271,22 +280,25 @@ pub async fn get_metrics(
                 total_payment_processed_count += count;
             }
             if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.payment_processed_amount_without_smart_retries_in_usd =
                     amount_in_usd;
                 total_payment_processed_amount_without_smart_retries_in_usd +=
@@ -323,14 +335,26 @@ pub async fn get_metrics(
             total_payment_processed_amount_without_smart_retries: Some(
                 total_payment_processed_amount_without_smart_retries,
             ),
-            total_smart_retried_amount_in_usd: Some(total_smart_retried_amount_in_usd),
-            total_smart_retried_amount_without_smart_retries_in_usd: Some(
-                total_smart_retried_amount_without_smart_retries_in_usd,
-            ),
-            total_payment_processed_amount_in_usd: Some(total_payment_processed_amount_in_usd),
-            total_payment_processed_amount_without_smart_retries_in_usd: Some(
-                total_payment_processed_amount_without_smart_retries_in_usd,
-            ),
+            total_smart_retried_amount_in_usd: if ex_rates.is_some() {
+                Some(total_smart_retried_amount_in_usd)
+            } else {
+                None
+            },
+            total_smart_retried_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
+                Some(total_smart_retried_amount_without_smart_retries_in_usd)
+            } else {
+                None
+            },
+            total_payment_processed_amount_in_usd: if ex_rates.is_some() {
+                Some(total_payment_processed_amount_in_usd)
+            } else {
+                None
+            },
+            total_payment_processed_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
+                Some(total_payment_processed_amount_without_smart_retries_in_usd)
+            } else {
+                None
+            },
             total_payment_processed_count: Some(total_payment_processed_count),
             total_payment_processed_count_without_smart_retries: Some(
                 total_payment_processed_count_without_smart_retries,
diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs
index c6f0276c47f..5cb32cbec37 100644
--- a/crates/analytics/src/payments/core.rs
+++ b/crates/analytics/src/payments/core.rs
@@ -49,7 +49,7 @@ pub enum TaskType {
 #[instrument(skip_all)]
 pub async fn get_metrics(
     pool: &AnalyticsProvider,
-    ex_rates: &ExchangeRates,
+    ex_rates: &Option<ExchangeRates>,
     auth: &AuthInfo,
     req: GetPaymentMetricRequest,
 ) -> AnalyticsResult<PaymentsMetricsResponse<MetricsBucketResponse>> {
@@ -235,22 +235,25 @@ pub async fn get_metrics(
         .map(|(id, val)| {
             let mut collected_values = val.collect();
             if let Some(amount) = collected_values.payment_processed_amount {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.payment_processed_amount_in_usd = amount_in_usd;
                 total_payment_processed_amount += amount;
                 total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
@@ -259,22 +262,25 @@ pub async fn get_metrics(
                 total_payment_processed_count += count;
             }
             if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.payment_processed_amount_without_smart_retries_usd = amount_in_usd;
                 total_payment_processed_amount_without_smart_retries += amount;
                 total_payment_processed_amount_without_smart_retries_usd +=
@@ -299,13 +305,19 @@ pub async fn get_metrics(
         query_data,
         meta_data: [PaymentsAnalyticsMetadata {
             total_payment_processed_amount: Some(total_payment_processed_amount),
-            total_payment_processed_amount_in_usd: Some(total_payment_processed_amount_in_usd),
+            total_payment_processed_amount_in_usd: if ex_rates.is_some() {
+                Some(total_payment_processed_amount_in_usd)
+            } else {
+                None
+            },
             total_payment_processed_amount_without_smart_retries: Some(
                 total_payment_processed_amount_without_smart_retries,
             ),
-            total_payment_processed_amount_without_smart_retries_usd: Some(
-                total_payment_processed_amount_without_smart_retries_usd,
-            ),
+            total_payment_processed_amount_without_smart_retries_usd: if ex_rates.is_some() {
+                Some(total_payment_processed_amount_without_smart_retries_usd)
+            } else {
+                None
+            },
             total_payment_processed_count: Some(total_payment_processed_count),
             total_payment_processed_count_without_smart_retries: Some(
                 total_payment_processed_count_without_smart_retries,
diff --git a/crates/analytics/src/refunds/core.rs b/crates/analytics/src/refunds/core.rs
index ca72c9003a6..f0ad21de080 100644
--- a/crates/analytics/src/refunds/core.rs
+++ b/crates/analytics/src/refunds/core.rs
@@ -48,7 +48,7 @@ pub enum TaskType {
 
 pub async fn get_metrics(
     pool: &AnalyticsProvider,
-    ex_rates: &ExchangeRates,
+    ex_rates: &Option<ExchangeRates>,
     auth: &AuthInfo,
     req: GetRefundMetricRequest,
 ) -> AnalyticsResult<RefundsMetricsResponse<RefundMetricsBucketResponse>> {
@@ -218,22 +218,25 @@ pub async fn get_metrics(
                 total += total_count;
             }
             if let Some(amount) = collected_values.refund_processed_amount {
-                let amount_in_usd = id
-                    .currency
-                    .and_then(|currency| {
-                        i64::try_from(amount)
-                            .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
-                            .ok()
-                            .and_then(|amount_i64| {
-                                convert(ex_rates, currency, Currency::USD, amount_i64)
-                                    .inspect_err(|e| {
-                                        logger::error!("Currency conversion error: {:?}", e)
-                                    })
-                                    .ok()
-                            })
-                    })
-                    .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
-                    .unwrap_or_default();
+                let amount_in_usd = if let Some(ex_rates) = ex_rates {
+                    id.currency
+                        .and_then(|currency| {
+                            i64::try_from(amount)
+                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
+                                .ok()
+                                .and_then(|amount_i64| {
+                                    convert(ex_rates, currency, Currency::USD, amount_i64)
+                                        .inspect_err(|e| {
+                                            logger::error!("Currency conversion error: {:?}", e)
+                                        })
+                                        .ok()
+                                })
+                        })
+                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
+                        .unwrap_or_default()
+                } else {
+                    None
+                };
                 collected_values.refund_processed_amount_in_usd = amount_in_usd;
                 total_refund_processed_amount += amount;
                 total_refund_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
@@ -262,7 +265,11 @@ pub async fn get_metrics(
         meta_data: [RefundsAnalyticsMetadata {
             total_refund_success_rate,
             total_refund_processed_amount: Some(total_refund_processed_amount),
-            total_refund_processed_amount_in_usd: Some(total_refund_processed_amount_in_usd),
+            total_refund_processed_amount_in_usd: if ex_rates.is_some() {
+                Some(total_refund_processed_amount_in_usd)
+            } else {
+                None
+            },
             total_refund_processed_count: Some(total_refund_processed_count),
             total_refund_reason_count: Some(total_refund_reason_count),
             total_refund_error_message_count: Some(total_refund_error_message_count),
diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs
index 806eb4b6e0e..a8db3a3cb0c 100644
--- a/crates/api_models/src/analytics.rs
+++ b/crates/api_models/src/analytics.rs
@@ -62,7 +62,42 @@ pub enum Granularity {
     #[serde(rename = "G_ONEDAY")]
     OneDay,
 }
-
+pub trait ForexMetric {
+    fn is_forex_metric(&self) -> bool;
+}
+
+#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AnalyticsRequest {
+    pub payment_intent: Option<GetPaymentIntentMetricRequest>,
+    pub payment_attempt: Option<GetPaymentMetricRequest>,
+    pub refund: Option<GetRefundMetricRequest>,
+    pub dispute: Option<GetDisputeMetricRequest>,
+}
+
+impl AnalyticsRequest {
+    pub fn requires_forex_functionality(&self) -> bool {
+        self.payment_attempt
+            .as_ref()
+            .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric()))
+            .unwrap_or_default()
+            || self
+                .payment_intent
+                .as_ref()
+                .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric()))
+                .unwrap_or_default()
+            || self
+                .refund
+                .as_ref()
+                .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric()))
+                .unwrap_or_default()
+            || self
+                .dispute
+                .as_ref()
+                .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric()))
+                .unwrap_or_default()
+    }
+}
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct GetPaymentMetricRequest {
diff --git a/crates/api_models/src/analytics/disputes.rs b/crates/api_models/src/analytics/disputes.rs
index 2509d83e132..e373704b87c 100644
--- a/crates/api_models/src/analytics/disputes.rs
+++ b/crates/api_models/src/analytics/disputes.rs
@@ -3,7 +3,7 @@ use std::{
     hash::{Hash, Hasher},
 };
 
-use super::{NameDescription, TimeRange};
+use super::{ForexMetric, NameDescription, TimeRange};
 use crate::enums::DisputeStage;
 
 #[derive(
@@ -28,6 +28,14 @@ pub enum DisputeMetrics {
     SessionizedTotalAmountDisputed,
     SessionizedTotalDisputeLostAmount,
 }
+impl ForexMetric for DisputeMetrics {
+    fn is_forex_metric(&self) -> bool {
+        matches!(
+            self,
+            Self::TotalAmountDisputed | Self::TotalDisputeLostAmount
+        )
+    }
+}
 
 #[derive(
     Debug,
diff --git a/crates/api_models/src/analytics/payment_intents.rs b/crates/api_models/src/analytics/payment_intents.rs
index 365abd71edc..15c107dfb66 100644
--- a/crates/api_models/src/analytics/payment_intents.rs
+++ b/crates/api_models/src/analytics/payment_intents.rs
@@ -5,7 +5,7 @@ use std::{
 
 use common_utils::id_type;
 
-use super::{NameDescription, TimeRange};
+use super::{ForexMetric, NameDescription, TimeRange};
 use crate::enums::{
     AuthenticationType, Connector, Currency, IntentStatus, PaymentMethod, PaymentMethodType,
 };
@@ -106,6 +106,17 @@ pub enum PaymentIntentMetrics {
     SessionizedPaymentProcessedAmount,
     SessionizedPaymentsDistribution,
 }
+impl ForexMetric for PaymentIntentMetrics {
+    fn is_forex_metric(&self) -> bool {
+        matches!(
+            self,
+            Self::PaymentProcessedAmount
+                | Self::SmartRetriedAmount
+                | Self::SessionizedPaymentProcessedAmount
+                | Self::SessionizedSmartRetriedAmount
+        )
+    }
+}
 
 #[derive(Debug, Default, serde::Serialize)]
 pub struct ErrorResult {
diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs
index 8fd24f15124..691e827043f 100644
--- a/crates/api_models/src/analytics/payments.rs
+++ b/crates/api_models/src/analytics/payments.rs
@@ -5,7 +5,7 @@ use std::{
 
 use common_utils::id_type;
 
-use super::{NameDescription, TimeRange};
+use super::{ForexMetric, NameDescription, TimeRange};
 use crate::enums::{
     AttemptStatus, AuthenticationType, CardNetwork, Connector, Currency, PaymentMethod,
     PaymentMethodType,
@@ -119,6 +119,17 @@ pub enum PaymentMetrics {
     FailureReasons,
 }
 
+impl ForexMetric for PaymentMetrics {
+    fn is_forex_metric(&self) -> bool {
+        matches!(
+            self,
+            Self::PaymentProcessedAmount
+                | Self::AvgTicketSize
+                | Self::SessionizedPaymentProcessedAmount
+                | Self::SessionizedAvgTicketSize
+        )
+    }
+}
 #[derive(Debug, Default, serde::Serialize)]
 pub struct ErrorResult {
     pub reason: String,
diff --git a/crates/api_models/src/analytics/refunds.rs b/crates/api_models/src/analytics/refunds.rs
index 0afca6c1ef6..84954e70910 100644
--- a/crates/api_models/src/analytics/refunds.rs
+++ b/crates/api_models/src/analytics/refunds.rs
@@ -30,7 +30,7 @@ pub enum RefundType {
     RetryRefund,
 }
 
-use super::{NameDescription, TimeRange};
+use super::{ForexMetric, NameDescription, TimeRange};
 #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
 pub struct RefundFilters {
     #[serde(default)]
@@ -137,6 +137,14 @@ pub enum RefundDistributions {
     #[strum(serialize = "refund_error_message")]
     SessionizedRefundErrorMessage,
 }
+impl ForexMetric for RefundMetrics {
+    fn is_forex_metric(&self) -> bool {
+        matches!(
+            self,
+            Self::RefundProcessedAmount | Self::SessionizedRefundProcessedAmount
+        )
+    }
+}
 
 pub mod metric_behaviour {
     pub struct RefundSuccessRate;
diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs
index d957c3071ff..62af479b23a 100644
--- a/crates/router/src/analytics.rs
+++ b/crates/router/src/analytics.rs
@@ -18,12 +18,12 @@ pub mod routes {
         search::{
             GetGlobalSearchRequest, GetSearchRequest, GetSearchRequestWithIndex, SearchIndex,
         },
-        GenerateReportRequest, GetActivePaymentsMetricRequest, GetApiEventFiltersRequest,
-        GetApiEventMetricRequest, GetAuthEventMetricRequest, GetDisputeMetricRequest,
-        GetFrmFilterRequest, GetFrmMetricRequest, GetPaymentFiltersRequest,
-        GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, GetPaymentMetricRequest,
-        GetRefundFilterRequest, GetRefundMetricRequest, GetSdkEventFiltersRequest,
-        GetSdkEventMetricRequest, ReportRequest,
+        AnalyticsRequest, GenerateReportRequest, GetActivePaymentsMetricRequest,
+        GetApiEventFiltersRequest, GetApiEventMetricRequest, GetAuthEventMetricRequest,
+        GetDisputeMetricRequest, GetFrmFilterRequest, GetFrmMetricRequest,
+        GetPaymentFiltersRequest, GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest,
+        GetPaymentMetricRequest, GetRefundFilterRequest, GetRefundMetricRequest,
+        GetSdkEventFiltersRequest, GetSdkEventMetricRequest, ReportRequest,
     };
     use common_enums::EntityType;
     use common_utils::types::TimeRange;
@@ -31,11 +31,9 @@ pub mod routes {
     use futures::{stream::FuturesUnordered, StreamExt};
 
     use crate::{
+        analytics_validator::request_validator,
         consts::opensearch::SEARCH_INDEXES,
-        core::{
-            api_locking, currency::get_forex_exchange_rates, errors::user::UserErrors,
-            verification::utils,
-        },
+        core::{api_locking, errors::user::UserErrors, verification::utils},
         db::{user::UserInterface, user_role::ListUserRolesByUserIdPayload},
         routes::AppState,
         services::{
@@ -405,7 +403,15 @@ pub mod routes {
                     org_id: org_id.clone(),
                     merchant_ids: vec![merchant_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_attempt: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -444,7 +450,16 @@ pub mod routes {
                 let auth: AuthInfo = AuthInfo::OrgLevel {
                     org_id: org_id.clone(),
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_attempt: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -491,7 +506,16 @@ pub mod routes {
                     merchant_id: merchant_id.clone(),
                     profile_ids: vec![profile_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_attempt: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -532,7 +556,16 @@ pub mod routes {
                     org_id: org_id.clone(),
                     merchant_ids: vec![merchant_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_intent: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -571,7 +604,16 @@ pub mod routes {
                 let auth: AuthInfo = AuthInfo::OrgLevel {
                     org_id: org_id.clone(),
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_intent: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -618,7 +660,16 @@ pub mod routes {
                     merchant_id: merchant_id.clone(),
                     profile_ids: vec![profile_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        payment_intent: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -659,7 +710,16 @@ pub mod routes {
                     org_id: org_id.clone(),
                     merchant_ids: vec![merchant_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        refund: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -698,7 +758,16 @@ pub mod routes {
                 let auth: AuthInfo = AuthInfo::OrgLevel {
                     org_id: org_id.clone(),
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        refund: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
@@ -745,7 +814,16 @@ pub mod routes {
                     merchant_id: merchant_id.clone(),
                     profile_ids: vec![profile_id.clone()],
                 };
-                let ex_rates = get_forex_exchange_rates(state.clone()).await?;
+
+                let validator_response = request_validator(
+                    AnalyticsRequest {
+                        refund: Some(req.clone()),
+                        ..Default::default()
+                    },
+                    &state,
+                )
+                .await?;
+                let ex_rates = validator_response;
                 analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req)
                     .await
                     .map(ApplicationResponse::Json)
diff --git a/crates/router/src/analytics_validator.rs b/crates/router/src/analytics_validator.rs
new file mode 100644
index 00000000000..d50308cc848
--- /dev/null
+++ b/crates/router/src/analytics_validator.rs
@@ -0,0 +1,24 @@
+use analytics::errors::AnalyticsError;
+use api_models::analytics::AnalyticsRequest;
+use common_utils::errors::CustomResult;
+use currency_conversion::types::ExchangeRates;
+use router_env::logger;
+
+use crate::core::currency::get_forex_exchange_rates;
+
+pub async fn request_validator(
+    req_type: AnalyticsRequest,
+    state: &crate::routes::SessionState,
+) -> CustomResult<Option<ExchangeRates>, AnalyticsError> {
+    let forex_enabled = state.conf.analytics.get_inner().get_forex_enabled();
+    let require_forex_functionality = req_type.requires_forex_functionality();
+
+    let ex_rates = if forex_enabled && require_forex_functionality {
+        logger::info!("Fetching forex exchange rates");
+        Some(get_forex_exchange_rates(state.clone()).await?)
+    } else {
+        None
+    };
+
+    Ok(ex_rates)
+}
diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs
index 839dd472423..d43fa054313 100644
--- a/crates/router/src/lib.rs
+++ b/crates/router/src/lib.rs
@@ -16,6 +16,7 @@ pub mod workflows;
 
 #[cfg(feature = "olap")]
 pub mod analytics;
+pub mod analytics_validator;
 pub mod events;
 pub mod middleware;
 pub mod services;

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

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

## Fixes #6731 

## Description
<!-- Describe your changes in detail -->
### Config driven forex functionality
- Created a `forex_enabled` flag in config settings under analytics config.
- Instead of passing `ExchangeRates` data structure now we are passing `Option<ExchangeRates>`
- In amount related fields of response of any metric, we now get `null` if `forex_enabled = false`

### Request Validator 
- Created an encapsulating `AnalyticsRequest` for all analytics request types
- implemented method `requires_forex_functionality` to give additional check, as to whether forex api call is required for the given set of metrics or not.
- Most importantly, it will serve as an extensible validator and provide contextual and comprehensive `4xx` errors in future.
### 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`
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).
-->

### Config Driven Forex Functionality  
The introduction of the `forex_enabled` flag allows greater flexibility in analytics operations by enabling or disabling forex functionality dynamically. This change minimizes unnecessary computations and API calls when forex functionality is not required, optimizing performance. By transitioning to passing `Option<ExchangeRates>`, the implementation better represents cases where forex data may not be applicable, (for e.g merchants that operate with single currency) aligning the API's behavior with user expectations. Setting `null` for amount-related fields in responses when forex is disabled ensures consistent and clear communication to the user about the absence of forex adjustments.  

### Request Validator  
The encapsulating `AnalyticsRequest` structure improves the overall request validation framework by providing a unified approach to handling diverse analytics request types. The `requires_forex_functionality` method enhances efficiency by explicitly determining when forex operations are necessary, further reducing redundant forex API calls. Additionally, the extensibility of this validator sets the foundation for robust error handling, allowing the system to provide detailed and actionable `4xx` error responses in the future.

## 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)?
-->
### Prerequisites
```toml
[analytics]
source = "sqlx"
forex_enabled = false # update this to enable or disable
```
### Testing config driven forex api call
```bash
curl --location 'http://localhost:8080/analytics/v1/org/metrics/payments' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:9000' \
--header 'Referer: http://localhost:9000/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: same-site' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
--header 'api-key: dev_r3kVZ4URj3fMiHhJJuhHOVqteaXwKyEMGCaFo6PiCS6S9vZsb0ErV4kCVqogM60H' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMDZlOGRmZWYtMzI0Zi00Yjc0LTg2MTItYzdkZjNhOGZmZTcyIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzI3NDM0NTkzIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTczMzI5Njk3MSwib3JnX2lkIjoib3JnXzl3Y0kxQ2hTOGVEdnRRQmRXak9IIiwicHJvZmlsZV9pZCI6InByb19FNW5lek43YjZUbVB1WlUzbEU1VSIsInRlbmFudF9pZCI6InB1YmxpYyJ9.fuAh7MTL96PeEizddbU3fOjOyHaXli5CXPrXMFjc-wU' \
--header 'sec-ch-ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--data '[
    {
        "timeRange": {
            "startTime": "2024-11-01T18:30:00Z",
            "endTime": "2024-11-11T08:24:00Z"
        },
        "source": "BATCH",
        "metrics": [
            "payment_processed_amount"
        ],
        "delta": true
    }
]'
```
#### when `forex_enabled=true`
```json
{
    "queryData": [
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 371940,
            "payment_processed_amount_in_usd": 4392,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": 0,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "currency": "INR",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": null,
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "time_range": {
                "start_time": "2024-11-01T18:30:00.000Z",
                "end_time": "2024-11-11T08:24:00.000Z"
            },
            "time_bucket": "2024-11-01 18:30:00"
        },
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 200000,
            "payment_processed_amount_in_usd": 200000,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": 0,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "currency": "USD",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": null,
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "time_range": {
                "start_time": "2024-11-01T18:30:00.000Z",
                "end_time": "2024-11-11T08:24:00.000Z"
            },
            "time_bucket": "2024-11-01 18:30:00"
        }
    ],
    "metaData": [
        {
            "total_payment_processed_amount": 571940,
            "total_payment_processed_amount_in_usd": 204392,
            "total_payment_processed_amount_without_smart_retries": 0,
            "total_payment_processed_amount_without_smart_retries_usd": 0,
            "total_payment_processed_count": 0,
            "total_payment_processed_count_without_smart_retries": 0,
            "total_failure_reasons_count": 0,
            "total_failure_reasons_count_without_smart_retries": 0
        }
    ]
}
```

#### When `forex_enabled=false`
```json
{
    "queryData": [
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 371940,
            "payment_processed_amount_in_usd": null,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": null,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "currency": "INR",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": null,
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "time_range": {
                "start_time": "2024-11-01T18:30:00.000Z",
                "end_time": "2024-11-11T08:24:00.000Z"
            },
            "time_bucket": "2024-11-01 18:30:00"
        },
        {
            "payment_success_rate": null,
            "payment_count": null,
            "payment_success_count": null,
            "payment_processed_amount": 200000,
            "payment_processed_amount_in_usd": null,
            "payment_processed_count": null,
            "payment_processed_amount_without_smart_retries": 0,
            "payment_processed_amount_without_smart_retries_usd": null,
            "payment_processed_count_without_smart_retries": null,
            "avg_ticket_size": null,
            "payment_error_message": null,
            "retries_count": null,
            "retries_amount_processed": 0,
            "connector_success_rate": null,
            "payments_success_rate_distribution": null,
            "payments_success_rate_distribution_without_smart_retries": null,
            "payments_success_rate_distribution_with_only_retries": null,
            "payments_failure_rate_distribution": null,
            "payments_failure_rate_distribution_without_smart_retries": null,
            "payments_failure_rate_distribution_with_only_retries": null,
            "failure_reason_count": 0,
            "failure_reason_count_without_smart_retries": 0,
            "currency": "USD",
            "status": null,
            "connector": null,
            "authentication_type": null,
            "payment_method": null,
            "payment_method_type": null,
            "client_source": null,
            "client_version": null,
            "profile_id": null,
            "card_network": null,
            "merchant_id": null,
            "card_last_4": null,
            "card_issuer": null,
            "error_reason": null,
            "time_range": {
                "start_time": "2024-11-01T18:30:00.000Z",
                "end_time": "2024-11-11T08:24:00.000Z"
            },
            "time_bucket": "2024-11-01 18:30:00"
        }
    ],
    "metaData": [
        {
            "total_payment_processed_amount": 571940,
            "total_payment_processed_amount_in_usd": null,
            "total_payment_processed_amount_without_smart_retries": 0,
            "total_payment_processed_amount_without_smart_retries_usd": null,
            "total_payment_processed_count": 0,
            "total_payment_processed_count_without_smart_retries": 0,
            "total_failure_reasons_count": 0,
            "total_failure_reasons_count_without_smart_retries": 0
        }
    ]
}
```

### Testing `request_validator()`

```bash
curl --location 'http://localhost:8080/analytics/v1/org/metrics/payments' \
--header 'Accept: */*' \
--header 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'Origin: http://localhost:9000' \
--header 'Referer: http://localhost:9000/' \
--header 'Sec-Fetch-Dest: empty' \
--header 'Sec-Fetch-Mode: cors' \
--header 'Sec-Fetch-Site: same-site' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
--header 'api-key: dev_r3kVZ4URj3fMiHhJJuhHOVqteaXwKyEMGCaFo6PiCS6S9vZsb0ErV4kCVqogM60H' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMDZlOGRmZWYtMzI0Zi00Yjc0LTg2MTItYzdkZjNhOGZmZTcyIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzI3NDM0NTkzIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTczMzI5Njk3MSwib3JnX2lkIjoib3JnXzl3Y0kxQ2hTOGVEdnRRQmRXak9IIiwicHJvZmlsZV9pZCI6InByb19FNW5lek43YjZUbVB1WlUzbEU1VSIsInRlbmFudF9pZCI6InB1YmxpYyJ9.fuAh7MTL96PeEizddbU3fOjOyHaXli5CXPrXMFjc-wU' \
--header 'sec-ch-ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--data '[
    {
        "timeRange": {
            "startTime": "2024-11-01T18:30:00Z",
            "endTime": "2024-11-11T08:24:00Z"
        },
        "source": "BATCH",
        "metrics": [
            "payment_success_rate"
        ],
        "delta": true
    }
]'
```

#### Check the logs, there won't be forex api call, and the response time also is lesser

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