mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
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:
@ -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 {
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user