mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
refactor(open_router): call elimination routing of open router if enabled instead of dynamo (#7961)
This commit is contained in:
@ -643,6 +643,17 @@ impl DynamicRoutingAlgorithmRef {
|
||||
self.dynamic_routing_volume_split = volume
|
||||
}
|
||||
|
||||
pub fn is_success_rate_routing_enabled(&self) -> bool {
|
||||
self.success_based_algorithm
|
||||
.as_ref()
|
||||
.map(|success_based_routing| {
|
||||
success_based_routing.enabled_feature
|
||||
== DynamicRoutingFeatures::DynamicConnectorSelection
|
||||
|| success_based_routing.enabled_feature == DynamicRoutingFeatures::Metrics
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_elimination_enabled(&self) -> bool {
|
||||
self.elimination_routing_algorithm
|
||||
.as_ref()
|
||||
|
||||
@ -419,8 +419,8 @@ pub enum RoutingError {
|
||||
ContractRoutingClientInitializationError,
|
||||
#[error("Invalid contract based connector label received from dynamic routing service: '{0}'")]
|
||||
InvalidContractBasedConnectorLabel(String),
|
||||
#[error("Failed to perform {algo} in open_router")]
|
||||
OpenRouterCallFailed { algo: String },
|
||||
#[error("Failed to perform routing in open_router")]
|
||||
OpenRouterCallFailed,
|
||||
#[error("Error from open_router: {0}")]
|
||||
OpenRouterError(String),
|
||||
}
|
||||
|
||||
@ -7243,7 +7243,7 @@ where
|
||||
|
||||
if routing_choice.routing_type.is_dynamic_routing() {
|
||||
if state.conf.open_router.enabled {
|
||||
routing::perform_open_routing(
|
||||
routing::perform_dynamic_routing_with_open_router(
|
||||
state,
|
||||
connectors.clone(),
|
||||
business_profile,
|
||||
@ -7285,7 +7285,7 @@ where
|
||||
.map(|card_isin| card_isin.to_string()),
|
||||
);
|
||||
|
||||
routing::perform_dynamic_routing(
|
||||
routing::perform_dynamic_routing_with_intelligent_router(
|
||||
state,
|
||||
connectors.clone(),
|
||||
business_profile,
|
||||
|
||||
@ -2148,6 +2148,19 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
);
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let should_route_to_open_router = state.conf.open_router.enabled;
|
||||
|
||||
if should_route_to_open_router {
|
||||
routing_helpers::update_gateway_score_helper_with_open_router(
|
||||
&state,
|
||||
&payment_attempt,
|
||||
&profile_id,
|
||||
dynamic_routing_algo_ref.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| logger::error!(open_router_update_gateway_score_err=?e))
|
||||
.ok();
|
||||
} else {
|
||||
routing_helpers::push_metrics_with_update_window_for_success_based_routing(
|
||||
&state,
|
||||
&payment_attempt,
|
||||
@ -2189,6 +2202,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
.map_err(|e| logger::error!(contract_based_routing_metrics_error=?e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
.in_current_span(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1491,7 +1491,7 @@ pub fn make_dsl_input_for_surcharge(
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
pub async fn perform_open_routing(
|
||||
pub async fn perform_dynamic_routing_with_open_router(
|
||||
state: &SessionState,
|
||||
routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
|
||||
profile: &domain::Profile,
|
||||
@ -1516,22 +1516,20 @@ pub async fn perform_open_routing(
|
||||
profile.get_id().get_string_repr()
|
||||
);
|
||||
|
||||
let is_success_rate_routing_enabled =
|
||||
dynamic_routing_algo_ref.is_success_rate_routing_enabled();
|
||||
let is_elimination_enabled = dynamic_routing_algo_ref.is_elimination_enabled();
|
||||
let connectors = dynamic_routing_algo_ref
|
||||
.success_based_algorithm
|
||||
.async_map(|algo| {
|
||||
perform_success_based_routing_with_open_router(
|
||||
|
||||
// Since success_based and elimination routing is being done in 1 api call, we call decide_gateway when either of it enabled
|
||||
let connectors = if is_success_rate_routing_enabled || is_elimination_enabled {
|
||||
let connectors = perform_decide_gateway_call_with_open_router(
|
||||
state,
|
||||
routable_connectors.clone(),
|
||||
profile.get_id(),
|
||||
algo,
|
||||
&payment_data,
|
||||
is_elimination_enabled,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.transpose()?
|
||||
.unwrap_or(routable_connectors);
|
||||
.await?;
|
||||
|
||||
if is_elimination_enabled {
|
||||
// This will initiate the elimination process for the connector.
|
||||
@ -1539,10 +1537,10 @@ pub async fn perform_open_routing(
|
||||
// Once the payment is made, we will update the score based on the payment status
|
||||
if let Some(connector) = connectors.first() {
|
||||
logger::debug!(
|
||||
"penalizing the elimination score of the gateway with id {} in open router for profile {}",
|
||||
"penalizing the elimination score of the gateway with id {} in open_router for profile {}",
|
||||
connector, profile.get_id().get_string_repr()
|
||||
);
|
||||
update_success_rate_score_with_open_router(
|
||||
update_gateway_score_with_open_router(
|
||||
state,
|
||||
connector.clone(),
|
||||
profile.get_id(),
|
||||
@ -1552,12 +1550,16 @@ pub async fn perform_open_routing(
|
||||
.await?
|
||||
}
|
||||
}
|
||||
connectors
|
||||
} else {
|
||||
routable_connectors
|
||||
};
|
||||
|
||||
Ok(connectors)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
pub async fn perform_dynamic_routing(
|
||||
pub async fn perform_dynamic_routing_with_intelligent_router(
|
||||
state: &SessionState,
|
||||
routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
|
||||
profile: &domain::Profile,
|
||||
@ -1648,19 +1650,15 @@ pub async fn perform_dynamic_routing(
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn perform_success_based_routing_with_open_router(
|
||||
pub async fn perform_decide_gateway_call_with_open_router(
|
||||
state: &SessionState,
|
||||
mut routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
|
||||
profile_id: &common_utils::id_type::ProfileId,
|
||||
success_based_algo_ref: api_routing::SuccessBasedAlgorithm,
|
||||
payment_attempt: &oss_storage::PaymentAttempt,
|
||||
is_elimination_enabled: bool,
|
||||
) -> RoutingResult<Vec<api_routing::RoutableConnectorChoice>> {
|
||||
if success_based_algo_ref.enabled_feature
|
||||
== api_routing::DynamicRoutingFeatures::DynamicConnectorSelection
|
||||
{
|
||||
logger::debug!(
|
||||
"performing success_based_routing with open_router for profile {}",
|
||||
"performing decide_gateway call with open_router for profile {}",
|
||||
profile_id.get_string_repr()
|
||||
);
|
||||
|
||||
@ -1682,11 +1680,9 @@ pub async fn perform_success_based_routing_with_open_router(
|
||||
open_router_req_body,
|
||||
)));
|
||||
|
||||
let response = services::call_connector_api(state, request, "open_router_sr_call")
|
||||
let response = services::call_connector_api(state, request, "open_router_decide_gateway_call")
|
||||
.await
|
||||
.change_context(errors::RoutingError::OpenRouterCallFailed {
|
||||
algo: "success_rate".into(),
|
||||
})?;
|
||||
.change_context(errors::RoutingError::OpenRouterCallFailed)?;
|
||||
|
||||
let sr_sorted_connectors = match response {
|
||||
Ok(resp) => {
|
||||
@ -1699,7 +1695,7 @@ pub async fn perform_success_based_routing_with_open_router(
|
||||
|
||||
if let Some(gateway_priority_map) = decided_gateway.gateway_priority_map {
|
||||
logger::debug!(
|
||||
"Open router gateway_priority_map response: {:?}",
|
||||
"open_router decide_gateway call response: {:?}",
|
||||
gateway_priority_map
|
||||
);
|
||||
routable_connectors.sort_by(|connector_choice_a, connector_choice_b| {
|
||||
@ -1727,20 +1723,17 @@ pub async fn perform_success_based_routing_with_open_router(
|
||||
))?;
|
||||
logger::error!("open_router_error_response: {:?}", err_resp);
|
||||
Err(errors::RoutingError::OpenRouterError(
|
||||
"Failed to perform success based routing in open router".into(),
|
||||
"Failed to perform decide_gateway call in open_router".into(),
|
||||
))
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(sr_sorted_connectors)
|
||||
} else {
|
||||
Ok(routable_connectors)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn update_success_rate_score_with_open_router(
|
||||
pub async fn update_gateway_score_with_open_router(
|
||||
state: &SessionState,
|
||||
payment_connector: api_routing::RoutableConnectorChoice,
|
||||
profile_id: &common_utils::id_type::ProfileId,
|
||||
@ -1768,9 +1761,7 @@ pub async fn update_success_rate_score_with_open_router(
|
||||
let response =
|
||||
services::call_connector_api(state, request, "open_router_update_gateway_score_call")
|
||||
.await
|
||||
.change_context(errors::RoutingError::OpenRouterCallFailed {
|
||||
algo: "success_rate".into(),
|
||||
})?;
|
||||
.change_context(errors::RoutingError::OpenRouterCallFailed)?;
|
||||
|
||||
match response {
|
||||
Ok(resp) => {
|
||||
@ -1781,7 +1772,7 @@ pub async fn update_success_rate_score_with_open_router(
|
||||
)?;
|
||||
|
||||
logger::debug!(
|
||||
"Open router update_gateway_score response for gateway with id {}: {:?}",
|
||||
"open_router update_gateway_score response for gateway with id {}: {:?}",
|
||||
payment_connector,
|
||||
update_score_resp
|
||||
);
|
||||
@ -1795,9 +1786,9 @@ pub async fn update_success_rate_score_with_open_router(
|
||||
.change_context(errors::RoutingError::OpenRouterError(
|
||||
"Failed to parse the response from open_router".into(),
|
||||
))?;
|
||||
logger::error!("open_router_error_response: {:?}", err_resp);
|
||||
logger::error!("open_router_update_gateway_score_error: {:?}", err_resp);
|
||||
Err(errors::RoutingError::OpenRouterError(
|
||||
"Failed to update gateway score for success based routing in open router".into(),
|
||||
"Failed to update gateway score in open_router".into(),
|
||||
))
|
||||
}
|
||||
}?;
|
||||
|
||||
@ -705,6 +705,53 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn update_gateway_score_helper_with_open_router(
|
||||
state: &SessionState,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
profile_id: &id_type::ProfileId,
|
||||
dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef,
|
||||
) -> RouterResult<()> {
|
||||
let is_success_rate_routing_enabled =
|
||||
dynamic_routing_algo_ref.is_success_rate_routing_enabled();
|
||||
let is_elimination_enabled = dynamic_routing_algo_ref.is_elimination_enabled();
|
||||
|
||||
if is_success_rate_routing_enabled || is_elimination_enabled {
|
||||
let payment_connector = &payment_attempt.connector.clone().ok_or(
|
||||
errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "unable to derive payment connector from payment attempt".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let routable_connector = routing_types::RoutableConnectorChoice {
|
||||
choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
|
||||
connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("unable to infer routable_connector from connector")?,
|
||||
merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
|
||||
};
|
||||
|
||||
logger::debug!(
|
||||
"performing update-gateway-score for gateway with id {} in open_router for profile: {}",
|
||||
routable_connector,
|
||||
profile_id.get_string_repr()
|
||||
);
|
||||
routing::payments_routing::update_gateway_score_with_open_router(
|
||||
state,
|
||||
routable_connector.clone(),
|
||||
profile_id,
|
||||
&payment_attempt.payment_id,
|
||||
payment_attempt.status,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to update gateway score in open_router service")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// metrics for success based dynamic routing
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
#[instrument(skip_all)]
|
||||
@ -741,27 +788,6 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
|
||||
merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
|
||||
};
|
||||
|
||||
let should_route_to_open_router = state.conf.open_router.enabled;
|
||||
|
||||
if should_route_to_open_router {
|
||||
logger::debug!(
|
||||
"performing update-gateway-score for gateway with id {} in open router for profile: {}",
|
||||
routable_connector, profile_id.get_string_repr()
|
||||
);
|
||||
routing::payments_routing::update_success_rate_score_with_open_router(
|
||||
state,
|
||||
routable_connector.clone(),
|
||||
profile_id,
|
||||
&payment_attempt.payment_id,
|
||||
payment_attempt.status,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("failed to update gateway score in open router service")?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let payment_status_attribute =
|
||||
get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user