Bug: Add support to call decision engine PL Routing from hyperswitch after API key authentication




diff --git a/crates/router/src/core/payments/routing/utils.rs b/crates/router/src/core/payments/routing/utils.rs
index d217cc04360..d373b4188b1 100644
--- a/crates/router/src/core/payments/routing/utils.rs
+++ b/crates/router/src/core/payments/routing/utils.rs
@@ -690,6 +690,7 @@ pub struct RoutingEvaluateRequest {
     pub parameters: HashMap<String, Option<ValueType>>,
     pub fallback_output: Vec<DeRoutableConnectorChoice>,
 }
+impl common_utils::events::ApiEventMetric for RoutingEvaluateRequest {}
 
 #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
 pub struct RoutingEvaluateResponse {
@@ -700,7 +701,7 @@ pub struct RoutingEvaluateResponse {
     #[serde(deserialize_with = "deserialize_connector_choices")]
     pub eligible_connectors: Vec<RoutableConnectorChoice>,
 }
-
+impl common_utils::events::ApiEventMetric for RoutingEvaluateResponse {}
 /// Routable Connector chosen for a payment
 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 pub struct DeRoutableConnectorChoice {
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 9935ab5fd9f..e3070077e94 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1093,6 +1093,10 @@ impl Routing {
                         routing::routing_link_config(state, req, path, payload, None)
                     },
                 )),
+            )
+            .service(
+                web::resource("/rule/evaluate")
+                    .route(web::post().to(routing::evaluate_routing_rule)),
             );
         route
     }
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index fa616a3da3e..8fb15df6fab 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -80,6 +80,7 @@ impl From<Flow> for ApiIdentifier {
             | Flow::ToggleDynamicRouting
             | Flow::UpdateDynamicRoutingConfigs
             | Flow::DecisionManagerUpsertConfig
+            | Flow::RoutingEvaluateRule
             | Flow::DecisionEngineRuleMigration
             | Flow::VolumeSplitOnRoutingType => Self::Routing,
 
diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs
index d247a8af6ea..024d4c0cfbe 100644
--- a/crates/router/src/routes/routing.rs
+++ b/crates/router/src/routes/routing.rs
@@ -8,6 +8,7 @@ use api_models::{
     enums,
     routing::{self as routing_types, RoutingRetrieveQuery},
 };
+use error_stack::ResultExt;
 use hyperswitch_domain_models::merchant_context::MerchantKeyStore;
 use payment_methods::core::errors::ApiErrorResponse;
 use router_env::{
@@ -16,9 +17,17 @@ use router_env::{
 };
 
 use crate::{
-    core::{api_locking, conditional_config, routing, surcharge_decision_config},
+    core::{
+        api_locking, conditional_config,
+        payments::routing::utils::{
+            DecisionEngineApiHandler, EuclidApiClient, RoutingEvaluateRequest,
+            RoutingEvaluateResponse,
+        },
+        routing, surcharge_decision_config,
+    },
     db::errors::StorageErrorExt,
     routes::AppState,
+    services,
     services::{api as oss_api, authentication as auth, authorization::permissions::Permission},
     types::domain,
 };
@@ -1564,6 +1573,47 @@ pub async fn get_dynamic_routing_volume_split(
     ))
     .await
 }
+const EUCLID_API_TIMEOUT: u64 = 5;
+#[cfg(all(feature = "olap", feature = "v1"))]
+#[instrument(skip_all)]
+pub async fn evaluate_routing_rule(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<RoutingEvaluateRequest>,
+) -> impl Responder {
+    let json_payload = json_payload.into_inner();
+    let flow = Flow::RoutingEvaluateRule;
+    Box::pin(oss_api::server_wrap(
+        flow,
+        state,
+        &req,
+        json_payload.clone(),
+        |state, _auth: auth::AuthenticationData, payload, _| async move {
+            let euclid_response: RoutingEvaluateResponse =
+                EuclidApiClient::send_decision_engine_request(
+                    &state,
+                    services::Method::Post,
+                    "routing/evaluate",
+                    Some(payload),
+                    Some(EUCLID_API_TIMEOUT),
+                    None,
+                )
+                .await
+                .change_context(ApiErrorResponse::InternalServerError)?
+                .response
+                .ok_or(ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to evaluate routing rule")?;
+
+            Ok(services::ApplicationResponse::Json(euclid_response))
+        },
+        &auth::ApiKeyAuth {
+            is_connected_allowed: false,
+            is_platform_allowed: false,
+        },
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
 
 use actix_web::HttpResponse;
 #[instrument(skip_all, fields(flow = ?Flow::DecisionEngineRuleMigration))]
@@ -1591,7 +1641,7 @@ pub async fn migrate_routing_rules_for_profile(
                 query_params,
             ))
             .await?;
-            Ok(crate::services::ApplicationResponse::Json(res))
+            Ok(services::ApplicationResponse::Json(res))
         },
         &auth::AdminApiAuth,
         api_locking::LockAction::NotApplicable,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index d395cd1f6da..20ae48484d5 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -550,6 +550,8 @@ pub enum Flow {
     PaymentStartRedirection,
     /// Volume split on the routing type
     VolumeSplitOnRoutingType,
+    /// Routing evaluate rule flow
+    RoutingEvaluateRule,
     /// Relay flow
     Relay,
     /// Relay retrieve flow

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

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

## Description
<!-- Describe your changes in detail -->
This PR adds the routing rule evaluation endpoint to Hyperswitch, which calls the decision engine
Added `evaluate_routing_rule` function, which calls `send_decision_engine_request` with `ApiKeyAuth` auth
### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Curl
```
curl --location 'http://localhost:8080/routing/rule/evaluate' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_Zgz3AVVEqf4l1IZtWu******tLXfe0CcbUmdQ3Ol91gfvt0aytHG5C2FXmCxc' \
--data '{
  "created_by":"merchant_1752591820",
  "parameters": {
    "payment_method": { "type": "enum_variant", "value": "upi"},
    "amount": { "type": "number","value": 100}
  },
  "fallback_output": [
    {
      "gateway_name": "cybersource",
      "gateway_id": "mca_51nE*****0cuknbQRaWz"
    }
  ]
}'
```
Response
```
{
    "status": "default_selection",
    "output": {
        "type": "priority",
        "connectors": [
            {
                "gateway_name": "stripe",
                "gateway_id": "mca_111"
            },
            {
                "gateway_name": "adyen",
                "gateway_id": "mca_112"
            },
            {
                "gateway_name": "checkout",
                "gateway_id": "mca_113"
            }
        ]
    },
    "evaluated_output": [
        {
            "gateway_name": "stripe",
            "gateway_id": "mca_111"
        }
    ],
    "eligible_connectors": []
}
```

**Logs at  Decision-engine**

<img width="1944" height="838" alt="image" src="https://github.com/user-attachments/assets/e9347077-916a-4dc7-9a9e-a0765067f2b4" />

**Logs at Hyperswitch**

<img width="1789" height="791" alt="image" src="https://github.com/user-attachments/assets/1fdab0fe-36ab-4ef8-be16-d2b2c261b864" />


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