feat(dynamic_routing): Decision engine config API integration (#8044)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sarthak Soni
2025-05-22 23:42:40 +05:30
committed by GitHub
parent d1fe2841c1
commit d41f65381a
9 changed files with 889 additions and 100 deletions

View File

@ -258,6 +258,26 @@ pub async fn create_merchant_account(
.await
.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?;
// Call to DE here
// Check if creation should be based on default profile
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
{
if state.conf.open_router.enabled {
merchant_account
.default_profile
.as_ref()
.async_map(|profile_id| {
routing::helpers::create_decision_engine_merchant(&state, profile_id)
})
.await
.transpose()
.map_err(|err| {
crate::logger::error!("Failed to create merchant in Decision Engine {err:?}");
})
.ok();
}
}
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
merchant_account.clone(),
key_store.clone(),
@ -1171,6 +1191,25 @@ pub async fn merchant_account_delete(
is_deleted = is_merchant_account_deleted && is_merchant_key_store_deleted;
}
// Call to DE here
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
{
if state.conf.open_router.enabled && is_deleted {
merchant_account
.default_profile
.as_ref()
.async_map(|profile_id| {
routing::helpers::delete_decision_engine_merchant(&state, profile_id)
})
.await
.transpose()
.map_err(|err| {
crate::logger::error!("Failed to delete merchant in Decision Engine {err:?}");
})
.ok();
}
}
let state = state.clone();
authentication::decision::spawn_tracked_job(
async move {

View File

@ -59,7 +59,9 @@ use crate::core::routing::transformers::OpenRouterDecideGatewayRequestExt;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::headers;
use crate::{
core::{errors, errors as oss_errors, payments::routing::utils::EuclidApiHandler, routing},
core::{
errors, errors as oss_errors, payments::routing::utils::DecisionEngineApiHandler, routing,
},
logger, services,
types::{
api::{self, routing as routing_types},
@ -1605,14 +1607,15 @@ pub async fn perform_open_routing_for_debit_routing(
Some(or_types::RankingAlgorithm::NtwBasedRouting),
);
let response: RoutingResult<DecidedGateway> = utils::EuclidApiClient::send_euclid_request(
state,
services::Method::Post,
"decide-gateway",
Some(open_router_req_body),
None,
)
.await;
let response: RoutingResult<DecidedGateway> =
utils::EuclidApiClient::send_decision_engine_request(
state,
services::Method::Post,
"decide-gateway",
Some(open_router_req_body),
None,
)
.await;
let output = match response {
Ok(decided_gateway) => {

View File

@ -18,8 +18,8 @@ use crate::{
// New Trait for handling Euclid API calls
#[async_trait]
pub trait EuclidApiHandler {
async fn send_euclid_request<Req, Res>(
pub trait DecisionEngineApiHandler {
async fn send_decision_engine_request<Req, Res>(
state: &SessionState,
http_method: services::Method,
path: &str,
@ -30,7 +30,7 @@ pub trait EuclidApiHandler {
Req: Serialize + Send + Sync + 'static,
Res: serde::de::DeserializeOwned + Send + 'static + std::fmt::Debug;
async fn send_euclid_request_without_response_parsing<Req>(
async fn send_decision_engine_request_without_response_parsing<Req>(
state: &SessionState,
http_method: services::Method,
path: &str,
@ -41,54 +41,54 @@ pub trait EuclidApiHandler {
Req: Serialize + Send + Sync + 'static;
}
// Struct to implement the EuclidApiHandler trait
// Struct to implement the DecisionEngineApiHandler trait
pub struct EuclidApiClient;
impl EuclidApiClient {
async fn build_and_send_euclid_http_request<Req>(
state: &SessionState,
http_method: services::Method,
path: &str,
request_body: Option<Req>,
timeout: Option<u64>,
context_message: &str,
) -> RoutingResult<reqwest::Response>
where
Req: Serialize + Send + Sync + 'static,
{
let euclid_base_url = &state.conf.open_router.url;
let url = format!("{}/{}", euclid_base_url, path);
logger::debug!(euclid_api_call_url = %url, euclid_request_path = %path, http_method = ?http_method, "decision_engine_euclid: Initiating Euclid API call ({})", context_message);
pub struct ConfigApiClient;
let mut request_builder = services::RequestBuilder::new()
.method(http_method)
.url(&url);
pub async fn build_and_send_decision_engine_http_request<Req>(
state: &SessionState,
http_method: services::Method,
path: &str,
request_body: Option<Req>,
timeout: Option<u64>,
context_message: &str,
) -> RoutingResult<reqwest::Response>
where
Req: Serialize + Send + Sync + 'static,
{
let decision_engine_base_url = &state.conf.open_router.url;
let url = format!("{}/{}", decision_engine_base_url, path);
logger::debug!(decision_engine_api_call_url = %url, decision_engine_request_path = %path, http_method = ?http_method, "decision_engine: Initiating decision_engine API call ({})", context_message);
if let Some(body_content) = request_body {
let body = common_utils::request::RequestContent::Json(Box::new(body_content));
request_builder = request_builder.set_body(body);
}
let mut request_builder = services::RequestBuilder::new()
.method(http_method)
.url(&url);
let http_request = request_builder.build();
logger::info!(?http_request, euclid_request_path = %path, "decision_engine_euclid: Constructed Euclid API request details ({})", context_message);
state
.api_client
.send_request(state, http_request, timeout, false)
.await
.change_context(errors::RoutingError::DslExecutionError)
.attach_printable_lazy(|| {
format!(
"Euclid API call to path '{}' unresponsive ({})",
path, context_message
)
})
if let Some(body_content) = request_body {
let body = common_utils::request::RequestContent::Json(Box::new(body_content));
request_builder = request_builder.set_body(body);
}
let http_request = request_builder.build();
logger::info!(?http_request, decision_engine_request_path = %path, "decision_engine: Constructed Decision Engine API request details ({})", context_message);
state
.api_client
.send_request(state, http_request, timeout, false)
.await
.change_context(errors::RoutingError::DslExecutionError)
.attach_printable_lazy(|| {
format!(
"Decision Engine API call to path '{}' unresponsive ({})",
path, context_message
)
})
}
#[async_trait]
impl EuclidApiHandler for EuclidApiClient {
async fn send_euclid_request<Req, Res>(
impl DecisionEngineApiHandler for EuclidApiClient {
async fn send_decision_engine_request<Req, Res>(
state: &SessionState,
http_method: services::Method,
path: &str,
@ -99,7 +99,7 @@ impl EuclidApiHandler for EuclidApiClient {
Req: Serialize + Send + Sync + 'static,
Res: serde::de::DeserializeOwned + Send + 'static + std::fmt::Debug,
{
let response = Self::build_and_send_euclid_http_request(
let response = build_and_send_decision_engine_http_request(
state,
http_method,
path,
@ -128,7 +128,7 @@ impl EuclidApiHandler for EuclidApiClient {
Ok(parsed_response)
}
async fn send_euclid_request_without_response_parsing<Req>(
async fn send_decision_engine_request_without_response_parsing<Req>(
state: &SessionState,
http_method: services::Method,
path: &str,
@ -138,7 +138,7 @@ impl EuclidApiHandler for EuclidApiClient {
where
Req: Serialize + Send + Sync + 'static,
{
let response = Self::build_and_send_euclid_http_request(
let response = build_and_send_decision_engine_http_request(
state,
http_method,
path,
@ -153,6 +153,73 @@ impl EuclidApiHandler for EuclidApiClient {
}
}
#[async_trait]
impl DecisionEngineApiHandler for ConfigApiClient {
async fn send_decision_engine_request<Req, Res>(
state: &SessionState,
http_method: services::Method,
path: &str,
request_body: Option<Req>,
timeout: Option<u64>,
) -> RoutingResult<Res>
where
Req: Serialize + Send + Sync + 'static,
Res: serde::de::DeserializeOwned + Send + 'static + std::fmt::Debug,
{
let response = build_and_send_decision_engine_http_request(
state,
http_method,
path,
request_body,
timeout,
"parsing response",
)
.await?;
logger::debug!(decision_engine_config_response = ?response, decision_engine_request_path = %path, "decision_engine_config: Received raw response from Decision Engine config API");
let parsed_response = response
.json::<Res>()
.await
.change_context(errors::RoutingError::GenericConversionError {
from: "ApiResponse".to_string(),
to: std::any::type_name::<Res>().to_string(),
})
.attach_printable_lazy(|| {
format!(
"Unable to parse response of type '{}' received from Decision Engine config API path: {}",
std::any::type_name::<Res>(),
path
)
})?;
logger::debug!(parsed_response = ?parsed_response, response_type = %std::any::type_name::<Res>(), decision_engine_request_path = %path, "decision_engine_config: Successfully parsed response from Decision Engine config API");
Ok(parsed_response)
}
async fn send_decision_engine_request_without_response_parsing<Req>(
state: &SessionState,
http_method: services::Method,
path: &str,
request_body: Option<Req>,
timeout: Option<u64>,
) -> RoutingResult<()>
where
Req: Serialize + Send + Sync + 'static,
{
let response = build_and_send_decision_engine_http_request(
state,
http_method,
path,
request_body,
timeout,
"not parsing response",
)
.await?;
logger::debug!(decision_engine_response = ?response, decision_engine_request_path = %path, "decision_engine_config: Received raw response from Decision Engine config API");
Ok(())
}
}
const EUCLID_API_TIMEOUT: u64 = 5;
pub async fn perform_decision_euclid_routing(
@ -164,7 +231,7 @@ pub async fn perform_decision_euclid_routing(
let routing_request = convert_backend_input_to_routing_eval(created_by, input)?;
let euclid_response: RoutingEvaluateResponse = EuclidApiClient::send_euclid_request(
let euclid_response: RoutingEvaluateResponse = EuclidApiClient::send_decision_engine_request(
state,
services::Method::Post,
"routing/evaluate",
@ -186,7 +253,7 @@ pub async fn create_de_euclid_routing_algo(
logger::debug!("decision_engine_euclid: create api call for euclid routing rule creation");
logger::debug!(decision_engine_euclid_request=?routing_request,"decision_engine_euclid");
let euclid_response: RoutingDictionaryRecord = EuclidApiClient::send_euclid_request(
let euclid_response: RoutingDictionaryRecord = EuclidApiClient::send_decision_engine_request(
state,
services::Method::Post,
"routing/create",
@ -205,7 +272,7 @@ pub async fn link_de_euclid_routing_algorithm(
) -> RoutingResult<()> {
logger::debug!("decision_engine_euclid: link api call for euclid routing algorithm");
EuclidApiClient::send_euclid_request_without_response_parsing(
EuclidApiClient::send_decision_engine_request_without_response_parsing(
state,
services::Method::Post,
"routing/activate",
@ -224,7 +291,7 @@ pub async fn list_de_euclid_routing_algorithms(
) -> RoutingResult<Vec<api_routing::RoutingDictionaryRecord>> {
logger::debug!("decision_engine_euclid: list api call for euclid routing algorithms");
let created_by = routing_list_request.created_by;
let response: Vec<RoutingAlgorithmRecord> = EuclidApiClient::send_euclid_request(
let response: Vec<RoutingAlgorithmRecord> = EuclidApiClient::send_decision_engine_request(
state,
services::Method::Post,
format!("routing/list/{created_by}").as_str(),

View File

@ -19,6 +19,8 @@ use external_services::grpc_client::dynamic_routing::{
elimination_based_client::EliminationBasedRouting,
success_rate_client::SuccessBasedDynamicRouting,
};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use helpers::update_decision_engine_dynamic_routing_setup;
use hyperswitch_domain_models::{mandates, payment_address};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use router_env::logger;
@ -564,6 +566,24 @@ pub async fn link_routing_config(
.enabled_feature,
routing_types::DynamicRoutingType::SuccessRateBasedRouting,
);
// Call to DE here to update SR configs
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
{
if state.conf.open_router.enabled {
update_decision_engine_dynamic_routing_setup(
&state,
business_profile.get_id(),
routing_algorithm.algorithm_data.clone(),
routing_types::DynamicRoutingType::SuccessRateBasedRouting,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed to update the success rate routing config in Decision Engine",
)?;
}
}
} else if routing_algorithm.name == helpers::ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM
{
dynamic_routing_ref.update_algorithm_id(
@ -578,6 +598,22 @@ pub async fn link_routing_config(
.enabled_feature,
routing_types::DynamicRoutingType::EliminationRouting,
);
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
{
if state.conf.open_router.enabled {
update_decision_engine_dynamic_routing_setup(
&state,
business_profile.get_id(),
routing_algorithm.algorithm_data.clone(),
routing_types::DynamicRoutingType::EliminationRouting,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed to update the elimination routing config in Decision Engine",
)?;
}
}
} else if routing_algorithm.name == helpers::CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM {
dynamic_routing_ref.update_algorithm_id(
algorithm_id,
@ -1516,7 +1552,7 @@ pub async fn success_based_routing_update_configs(
name: dynamic_routing_algo_to_update.name,
description: dynamic_routing_algo_to_update.description,
kind: dynamic_routing_algo_to_update.kind,
algorithm_data: serde_json::json!(config_to_update),
algorithm_data: serde_json::json!(config_to_update.clone()),
created_at: timestamp,
modified_at: timestamp,
algorithm_for: dynamic_routing_algo_to_update.algorithm_for,
@ -1551,23 +1587,25 @@ pub async fn success_based_routing_update_configs(
router_env::metric_attributes!(("profile_id", profile_id.clone())),
);
state
.grpc_client
.dynamic_routing
.success_rate_client
.as_ref()
.async_map(|sr_client| async {
sr_client
.invalidate_success_rate_routing_keys(
profile_id.get_string_repr().into(),
state.get_grpc_headers(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to invalidate the routing keys")
})
.await
.transpose()?;
if !state.conf.open_router.enabled {
state
.grpc_client
.dynamic_routing
.success_rate_client
.as_ref()
.async_map(|sr_client| async {
sr_client
.invalidate_success_rate_routing_keys(
profile_id.get_string_repr().into(),
state.get_grpc_headers(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to invalidate the routing keys")
})
.await
.transpose()?;
}
Ok(service_api::ApplicationResponse::Json(new_record))
}
@ -1653,23 +1691,25 @@ pub async fn elimination_routing_update_configs(
router_env::metric_attributes!(("profile_id", profile_id.clone())),
);
state
.grpc_client
.dynamic_routing
.elimination_based_client
.as_ref()
.async_map(|er_client| async {
er_client
.invalidate_elimination_bucket(
profile_id.get_string_repr().into(),
state.get_grpc_headers(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to invalidate the elimination routing keys")
})
.await
.transpose()?;
if !state.conf.open_router.enabled {
state
.grpc_client
.dynamic_routing
.elimination_based_client
.as_ref()
.async_map(|er_client| async {
er_client
.invalidate_elimination_bucket(
profile_id.get_string_repr().into(),
state.get_grpc_headers(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to invalidate the elimination routing keys")
})
.await
.transpose()?;
}
Ok(service_api::ApplicationResponse::Json(new_record))
}

View File

@ -52,7 +52,12 @@ use crate::{
};
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::{
core::{metrics as core_metrics, routing},
core::{
metrics as core_metrics,
payments::routing::utils::{self as routing_utils, DecisionEngineApiHandler},
routing,
},
services,
types::transformers::ForeignInto,
};
pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
@ -62,6 +67,12 @@ pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
pub const CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
"Contract based dynamic routing algorithm";
pub const DECISION_ENGINE_RULE_CREATE_ENDPOINT: &str = "rule/create";
pub const DECISION_ENGINE_RULE_UPDATE_ENDPOINT: &str = "rule/update";
pub const DECISION_ENGINE_RULE_DELETE_ENDPOINT: &str = "rule/delete";
pub const DECISION_ENGINE_MERCHANT_BASE_ENDPOINT: &str = "merchant-account";
pub const DECISION_ENGINE_MERCHANT_CREATE_ENDPOINT: &str = "merchant-account/create";
/// Provides us with all the configured configs of the Merchant in the ascending time configured
/// manner and chooses the first of them
pub async fn get_merchant_default_config(
@ -1625,6 +1636,18 @@ pub async fn disable_dynamic_routing_algorithm(
}
};
// Call to DE here
if state.conf.open_router.enabled {
disable_decision_engine_dynamic_routing_setup(
state,
business_profile.get_id(),
dynamic_routing_type,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to disable dynamic routing setup in decision engine")?;
}
// redact cache for dynamic routing config
let _ = cache::redact_from_redis_and_publish(
state.store.get_cache_store().as_ref(),
@ -1801,8 +1824,12 @@ pub async fn default_specific_dynamic_routing_setup(
let timestamp = common_utils::date_time::now();
let algo = match dynamic_routing_type {
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
let default_success_based_routing_config =
routing_types::SuccessBasedRoutingConfig::default();
let default_success_based_routing_config = if state.conf.open_router.enabled {
routing_types::SuccessBasedRoutingConfig::open_router_config_default()
} else {
routing_types::SuccessBasedRoutingConfig::default()
};
routing_algorithm::RoutingAlgorithm {
algorithm_id: algorithm_id.clone(),
profile_id: profile_id.clone(),
@ -1818,8 +1845,11 @@ pub async fn default_specific_dynamic_routing_setup(
}
}
routing_types::DynamicRoutingType::EliminationRouting => {
let default_elimination_routing_config =
routing_types::EliminationRoutingConfig::default();
let default_elimination_routing_config = if state.conf.open_router.enabled {
routing_types::EliminationRoutingConfig::open_router_config_default()
} else {
routing_types::EliminationRoutingConfig::default()
};
routing_algorithm::RoutingAlgorithm {
algorithm_id: algorithm_id.clone(),
profile_id: profile_id.clone(),
@ -1843,6 +1873,19 @@ pub async fn default_specific_dynamic_routing_setup(
}
};
// Call to DE here
// Need to map out the cases if this call should always be made or not
if state.conf.open_router.enabled {
enable_decision_engine_dynamic_routing_setup(
state,
business_profile.get_id(),
dynamic_routing_type,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to setup decision engine dynamic routing")?;
}
let record = db
.insert_routing_algorithm(algo)
.await
@ -1945,3 +1988,234 @@ impl DynamicRoutingConfigParamsInterpolator {
parts.join(":")
}
}
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[instrument(skip_all)]
pub async fn enable_decision_engine_dynamic_routing_setup(
state: &SessionState,
profile_id: &id_type::ProfileId,
dynamic_routing_type: routing_types::DynamicRoutingType,
) -> RouterResult<()> {
logger::debug!(
"performing call with open_router for profile {}",
profile_id.get_string_repr()
);
let default_engine_config_request = match dynamic_routing_type {
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
let default_success_based_routing_config =
routing_types::SuccessBasedRoutingConfig::open_router_config_default();
open_router::DecisionEngineConfigSetupRequest {
merchant_id: profile_id.get_string_repr().to_string(),
config: open_router::DecisionEngineConfigVariant::SuccessRate(
default_success_based_routing_config
.get_decision_engine_configs()
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Decision engine config not found".to_string(),
})
.attach_printable("Decision engine config not found")?,
),
}
}
routing_types::DynamicRoutingType::EliminationRouting => {
let default_elimination_based_routing_config =
routing_types::EliminationRoutingConfig::open_router_config_default();
open_router::DecisionEngineConfigSetupRequest {
merchant_id: profile_id.get_string_repr().to_string(),
config: open_router::DecisionEngineConfigVariant::Elimination(
default_elimination_based_routing_config
.get_decision_engine_configs()
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Decision engine config not found".to_string(),
})
.attach_printable("Decision engine config not found")?,
),
}
}
routing_types::DynamicRoutingType::ContractBasedRouting => {
return Err((errors::ApiErrorResponse::InvalidRequestData {
message: "Contract routing cannot be set as default".to_string(),
})
.into())
}
};
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
state,
services::Method::Post,
DECISION_ENGINE_RULE_CREATE_ENDPOINT,
Some(default_engine_config_request),
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to setup decision engine dynamic routing")?;
Ok(())
}
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[instrument(skip_all)]
pub async fn update_decision_engine_dynamic_routing_setup(
state: &SessionState,
profile_id: &id_type::ProfileId,
request: serde_json::Value,
dynamic_routing_type: routing_types::DynamicRoutingType,
) -> RouterResult<()> {
logger::debug!(
"performing call with open_router for profile {}",
profile_id.get_string_repr()
);
let decision_engine_request = match dynamic_routing_type {
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
let success_rate_config: routing_types::SuccessBasedRoutingConfig = request
.parse_value("SuccessBasedRoutingConfig")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to deserialize SuccessBasedRoutingConfig")?;
open_router::DecisionEngineConfigSetupRequest {
merchant_id: profile_id.get_string_repr().to_string(),
config: open_router::DecisionEngineConfigVariant::SuccessRate(
success_rate_config
.get_decision_engine_configs()
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Decision engine config not found".to_string(),
})
.attach_printable("Decision engine config not found")?,
),
}
}
routing_types::DynamicRoutingType::EliminationRouting => {
let elimination_config: routing_types::EliminationRoutingConfig = request
.parse_value("EliminationRoutingConfig")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to deserialize EliminationRoutingConfig")?;
open_router::DecisionEngineConfigSetupRequest {
merchant_id: profile_id.get_string_repr().to_string(),
config: open_router::DecisionEngineConfigVariant::Elimination(
elimination_config
.get_decision_engine_configs()
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Decision engine config not found".to_string(),
})
.attach_printable("Decision engine config not found")?,
),
}
}
routing_types::DynamicRoutingType::ContractBasedRouting => {
return Err((errors::ApiErrorResponse::InvalidRequestData {
message: "Contract routing cannot be set as default".to_string(),
})
.into())
}
};
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
state,
services::Method::Post,
DECISION_ENGINE_RULE_UPDATE_ENDPOINT,
Some(decision_engine_request),
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to update decision engine dynamic routing")?;
Ok(())
}
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[instrument(skip_all)]
pub async fn disable_decision_engine_dynamic_routing_setup(
state: &SessionState,
profile_id: &id_type::ProfileId,
dynamic_routing_type: routing_types::DynamicRoutingType,
) -> RouterResult<()> {
logger::debug!(
"performing call with open_router for profile {}",
profile_id.get_string_repr()
);
let decision_engine_request = open_router::FetchRoutingConfig {
merchant_id: profile_id.get_string_repr().to_string(),
algorithm: match dynamic_routing_type {
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
open_router::AlgorithmType::SuccessRate
}
routing_types::DynamicRoutingType::EliminationRouting => {
open_router::AlgorithmType::Elimination
}
routing_types::DynamicRoutingType::ContractBasedRouting => {
return Err((errors::ApiErrorResponse::InvalidRequestData {
message: "Contract routing is not enabled for decision engine".to_string(),
})
.into())
}
},
};
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
state,
services::Method::Post,
DECISION_ENGINE_RULE_DELETE_ENDPOINT,
Some(decision_engine_request),
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to disable decision engine dynamic routing")?;
Ok(())
}
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[instrument(skip_all)]
pub async fn create_decision_engine_merchant(
state: &SessionState,
profile_id: &id_type::ProfileId,
) -> RouterResult<()> {
let merchant_account_req = open_router::MerchantAccount {
merchant_id: profile_id.get_string_repr().to_string(),
gateway_success_rate_based_decider_input: None,
};
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
state,
services::Method::Post,
DECISION_ENGINE_MERCHANT_CREATE_ENDPOINT,
Some(merchant_account_req),
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to create merchant account on decision engine")?;
Ok(())
}
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[instrument(skip_all)]
pub async fn delete_decision_engine_merchant(
state: &SessionState,
profile_id: &id_type::ProfileId,
) -> RouterResult<()> {
let path = format!(
"{}/{}",
DECISION_ENGINE_MERCHANT_BASE_ENDPOINT,
profile_id.get_string_repr()
);
routing_utils::ConfigApiClient::send_decision_engine_request_without_response_parsing::<()>(
state,
services::Method::Delete,
&path,
None,
None,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to delete merchant account on decision engine")?;
Ok(())
}