Bug: [BUG] Analytics giving 5xx on forex crate returning no data



### Bug Description

Forex return error because there is no data in redis which is present for the currency exchange rate

### Expected Behavior

Forex create should always return data when queried for exchange rates

### Actual Behavior

Analytics API gives 5xx when forex return error because there is no data in redis which is present for the currency exchange rate

### Steps To Reproduce

On first API call for the forex crate, you get error because there is no redis key set for the service to return data.

### Context For The Bug

_No response_

### Environment

All environments

### Have you spent some time checking if this bug has been raised before?

- [x] I checked and didn't find a 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?

None
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index 0fce7048e69..d6872bcf06c 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -229,3 +229,6 @@ pub(crate) const PROTOCOL: &str = "ECv2";
 
 /// Sender ID for Google Pay Decryption
 pub(crate) const SENDER_ID: &[u8] = b"Google";
+
+/// Default value for the number of attempts to retry fetching forex rates
+pub const DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS: u64 = 3;
diff --git a/crates/router/src/core/currency.rs b/crates/router/src/core/currency.rs
index bc63dc878cc..43b63cd6e63 100644
--- a/crates/router/src/core/currency.rs
+++ b/crates/router/src/core/currency.rs
@@ -2,11 +2,13 @@ use analytics::errors::AnalyticsError;
 use common_utils::errors::CustomResult;
 use currency_conversion::types::ExchangeRates;
 use error_stack::ResultExt;
+use router_env::logger;
 
 use crate::{
+    consts::DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS,
     core::errors::ApiErrorResponse,
     services::ApplicationResponse,
-    utils::currency::{self, convert_currency, get_forex_rates},
+    utils::currency::{self, convert_currency, get_forex_rates, ForexError as ForexCacheError},
     SessionState,
 };
 
@@ -48,9 +50,45 @@ pub async fn get_forex_exchange_rates(
     state: SessionState,
 ) -> CustomResult<ExchangeRates, AnalyticsError> {
     let forex_api = state.conf.forex_api.get_inner();
-    let rates = get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds)
-        .await
-        .change_context(AnalyticsError::ForexFetchFailed)?;
+    let mut attempt = 1;
+
+    logger::info!("Starting forex exchange rates fetch");
+    loop {
+        logger::info!("Attempting to fetch forex rates - Attempt {attempt} of {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS}");
+
+        match get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds).await {
+            Ok(rates) => {
+                logger::info!("Successfully fetched forex rates");
+                return Ok((*rates.data).clone());
+            }
+            Err(error) => {
+                let is_retryable = matches!(
+                    error.current_context(),
+                    ForexCacheError::CouldNotAcquireLock
+                        | ForexCacheError::EntryNotFound
+                        | ForexCacheError::ForexDataUnavailable
+                        | ForexCacheError::LocalReadError
+                        | ForexCacheError::LocalWriteError
+                        | ForexCacheError::RedisConnectionError
+                        | ForexCacheError::RedisLockReleaseFailed
+                        | ForexCacheError::RedisWriteError
+                        | ForexCacheError::WriteLockNotAcquired
+                );
+
+                if !is_retryable {
+                    return Err(error.change_context(AnalyticsError::ForexFetchFailed));
+                }
 
-    Ok((*rates.data).clone())
+                if attempt >= DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS {
+                    logger::error!("Failed to fetch forex rates after {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS} attempts");
+                    return Err(error.change_context(AnalyticsError::ForexFetchFailed));
+                }
+                logger::warn!(
+                    "Forex rates fetch failed with retryable error, retrying in {attempt} seconds"
+                );
+                tokio::time::sleep(std::time::Duration::from_secs(attempt * 2)).await;
+                attempt += 1;
+            }
+        }
+    }
 }

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

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

## Description
<!-- Describe your changes in detail -->
Implementing a retry logic for forex crate call with linear time increment in retry counter

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

`crates/router/src/core/currency.rs`

## 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 was required as the Analytics API was returning 5xx when forex was returning error which would eventually resolve on retry 


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

As soon as you hit the API of analytics

`curl --location 'http://localhost:8080/analytics/v1/org/metrics/payments' \
--header 'Accept: */*' \
--header 'Accept-Language: en-US,en;q=0.9' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer token \
--data '[
    {
        "timeRange": {
            "startTime": "2024-09-11T18:30:00Z",
            "endTime": "2024-09-25T09:22:00Z"
        }, 
        "source": "BATCH",
        "metrics": [
            "payment_processed_amount"
        ],
        "timeSeries": {
            "granularity": "G_ONEDAY"
        },
        "delta": true
    }
]'`

There is no change is response but how the evaluation of the request to give out response would change


Added logs for the same are added below

![Screenshot 2025-02-17 at 12 26 37 PM](https://github.com/user-attachments/assets/cb8b3a61-7d87-4f7d-9e87-5a14de6d6263)


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
