From 7b214174ae478aa0c66765adec3c9978ac8ec469 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Wed, 27 Nov 2024 12:52:08 +0530 Subject: [PATCH 01/27] build theinterface for elimination routing : --- crates/api_models/src/routing.rs | 21 ++ .../src/grpc_client/dynamic_routing.rs | 204 +++--------------- 2 files changed, 45 insertions(+), 180 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 389af3dab7bf..b0483a545f84 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -680,3 +680,24 @@ impl CurrentBlockThreshold { } } } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct RoutableConnectorChoiceWithBucketName { + pub routable_connector_choice: RoutableConnectorChoice, + pub bucket_name: String, +} + +impl RoutableConnectorChoiceWithBucketName { + pub fn new(routable_connector_choice: RoutableConnectorChoice, bucket_name: String) -> Self { + Self { + routable_connector_choice, + bucket_name, + } + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct EliminationAnalyserConfig { + pub bucket_size: u64, + pub bucket_ttl_in_mins: u64, +} diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index 0546d05ba7c9..8f68d15c3264 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -1,32 +1,18 @@ use std::fmt::Debug; -use api_models::routing::{ - CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, - SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, -}; -use common_utils::{errors::CustomResult, ext_traits::OptionExt, transformers::ForeignTryFrom}; -use error_stack::ResultExt; +use common_utils::errors::CustomResult; use http_body_util::combinators::UnsyncBoxBody; use hyper::body::Bytes; use hyper_util::client::legacy::connect::HttpConnector; use router_env::logger; use serde; -use success_rate::{ - success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, - CalSuccessRateRequest, CalSuccessRateResponse, - CurrentBlockThreshold as DynamicCurrentThreshold, LabelWithStatus, - UpdateSuccessRateWindowConfig, UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, -}; use tonic::Status; -#[allow( - missing_docs, - unused_qualifications, - clippy::unwrap_used, - clippy::as_conversions -)] -pub mod success_rate { - tonic::include_proto!("success_rate"); -} +pub mod elimination_rate; +pub mod success_rate; + +pub use elimination_rate::EliminationAnalyserClient; +pub use success_rate::SuccessRateCalculatorClient; + /// Result type for Dynamic Routing pub type DynamicRoutingResult = CustomResult; @@ -39,9 +25,12 @@ pub enum DynamicRoutingError { /// The required field name field: String, }, - /// Error from Dynamic Routing Server - #[error("Error from Dynamic Routing Server : {0}")] + /// Error from Dynamic Routing Server while perfrming success_rate analysis + #[error("Error from Dynamic Routing Server while perfrming success_rate analysis : {0}")] SuccessRateBasedRoutingFailure(String), + /// Error from Dynamic Routing Server while perfrming elimination + #[error("Error from Dynamic Routing Server while perfrming elimination : {0}")] + EliminationRateRoutingFailure(String), } type Client = hyper_util::client::legacy::Client>; @@ -51,6 +40,8 @@ type Client = hyper_util::client::legacy::Client>, + /// elimination service for Dynamic Routing + pub elimination_rate_client: Option>, } /// Contains the Dynamic Routing Client Config @@ -78,170 +69,23 @@ impl DynamicRoutingClientConfig { hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .http2_only(true) .build_http(); - let success_rate_client = match self { + let (success_rate_client, elimination_rate_client) = match self { Self::Enabled { host, port } => { let uri = format!("http://{}:{}", host, port).parse::()?; logger::info!("Connection established with dynamic routing gRPC Server"); - Some(SuccessRateCalculatorClient::with_origin(client, uri)) + ( + Some(SuccessRateCalculatorClient::with_origin( + client.clone(), + uri.clone(), + )), + Some(EliminationAnalyserClient::with_origin(client, uri)), + ) } - Self::Disabled => None, + Self::Disabled => (None, None), }; Ok(RoutingStrategy { success_rate_client, - }) - } -} - -/// The trait Success Based Dynamic Routing would have the functions required to support the calculation and updation window -#[async_trait::async_trait] -pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { - /// To calculate the success rate for the list of chosen connectors - async fn calculate_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - params: String, - label_input: Vec, - ) -> DynamicRoutingResult; - /// To update the success rate with the given label - async fn update_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - params: String, - response: Vec, - ) -> DynamicRoutingResult; -} - -#[async_trait::async_trait] -impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { - async fn calculate_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - params: String, - label_input: Vec, - ) -> DynamicRoutingResult { - let labels = label_input - .into_iter() - .map(|conn_choice| conn_choice.to_string()) - .collect::>(); - - let config = success_rate_based_config - .config - .map(ForeignTryFrom::foreign_try_from) - .transpose()?; - - let request = tonic::Request::new(CalSuccessRateRequest { - id, - params, - labels, - config, - }); - - let mut client = self.clone(); - - let response = client - .fetch_success_rate(request) - .await - .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( - "Failed to fetch the success rate".to_string(), - ))? - .into_inner(); - - Ok(response) - } - - async fn update_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - params: String, - label_input: Vec, - ) -> DynamicRoutingResult { - let config = success_rate_based_config - .config - .map(ForeignTryFrom::foreign_try_from) - .transpose()?; - - let labels_with_status = label_input - .into_iter() - .map(|conn_choice| LabelWithStatus { - label: conn_choice.routable_connector_choice.to_string(), - status: conn_choice.status, - }) - .collect(); - - let request = tonic::Request::new(UpdateSuccessRateWindowRequest { - id, - params, - labels_with_status, - config, - }); - - let mut client = self.clone(); - - let response = client - .update_success_rate_window(request) - .await - .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( - "Failed to update the success rate window".to_string(), - ))? - .into_inner(); - - Ok(response) - } -} - -impl ForeignTryFrom for DynamicCurrentThreshold { - type Error = error_stack::Report; - fn foreign_try_from(current_threshold: CurrentBlockThreshold) -> Result { - Ok(Self { - duration_in_mins: current_threshold.duration_in_mins, - max_total_count: current_threshold - .max_total_count - .get_required_value("max_total_count") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "max_total_count".to_string(), - })?, - }) - } -} - -impl ForeignTryFrom for UpdateSuccessRateWindowConfig { - type Error = error_stack::Report; - fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { - Ok(Self { - max_aggregates_size: config - .max_aggregates_size - .get_required_value("max_aggregate_size") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "max_aggregates_size".to_string(), - })?, - current_block_threshold: config - .current_block_threshold - .map(ForeignTryFrom::foreign_try_from) - .transpose()?, - }) - } -} - -impl ForeignTryFrom for CalSuccessRateConfig { - type Error = error_stack::Report; - fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { - Ok(Self { - min_aggregates_size: config - .min_aggregates_size - .get_required_value("min_aggregate_size") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "min_aggregates_size".to_string(), - })?, - default_success_rate: config - .default_success_rate - .get_required_value("default_success_rate") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "default_success_rate".to_string(), - })?, + elimination_rate_client, }) } } From 096bdf88918940dab48938a19d966162bc4894ec Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Wed, 27 Nov 2024 16:24:45 +0530 Subject: [PATCH 02/27] resolve conflicts add invalidation window --- .../dynamic_routing/elimination_rate.rs | 157 ++++++++++++++ .../dynamic_routing/success_rate.rs | 196 ++++++++++++++++++ proto/elimination_rate.proto | 63 ++++++ 3 files changed, 416 insertions(+) create mode 100644 crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs create mode 100644 crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs create mode 100644 proto/elimination_rate.proto diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs new file mode 100644 index 000000000000..0cafc8a5a396 --- /dev/null +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs @@ -0,0 +1,157 @@ +use api_models::routing::{ + EliminationAnalyserConfig as EliminationConfig, RoutableConnectorChoice, + RoutableConnectorChoiceWithBucketName, +}; +use common_utils::transformers::ForeignTryFrom; +pub use elimination_rate::{ + elimination_analyser_client::EliminationAnalyserClient, EliminationAnalyserConfig, + EliminationRequest, EliminationResponse, InvalidateBucketRequest, InvalidateBucketResponse, + LabelWithBucketName, UpdateEliminationBucketConfig, UpdateEliminationBucketRequest, + UpdateEliminationBucketResponse, +}; +use error_stack::ResultExt; +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions +)] +pub mod elimination_rate { + tonic::include_proto!("elimination"); +} + +use super::{Client, DynamicRoutingError, DynamicRoutingResult}; + +/// The trait Elimination Based Routing would have the functions required to support performance, calculation and invalidation bucket +#[async_trait::async_trait] +pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync { + /// To perform the elimination based routing for the list of connectors + async fn perform_elimination_routing( + &self, + id: String, + params: String, + labels: Vec, + configs: Option, + ) -> DynamicRoutingResult; + /// To update the bucket size and ttl for list of connectors with its respective bucket name + async fn update_elimination_bucket_config( + &self, + id: String, + params: String, + report: Vec, + config: Option, + ) -> DynamicRoutingResult; + /// To invalidate the previous id's bucket + async fn invalidate_elimination_bucket( + &self, + id: String, + ) -> DynamicRoutingResult; +} + +#[async_trait::async_trait] +impl EliminationBasedRouting for EliminationAnalyserClient { + async fn perform_elimination_routing( + &self, + id: String, + params: String, + label_input: Vec, + configs: Option, + ) -> DynamicRoutingResult { + let labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?; + + let request = tonic::Request::new(EliminationRequest { + id, + params, + labels, + config, + }); + + let response = self + .clone() + .perform_elimination(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to perform the elimination analysis".to_string(), + ))? + .into_inner(); + + Ok(response) + } + + async fn update_elimination_bucket_config( + &self, + id: String, + params: String, + report: Vec, + configs: Option, + ) -> DynamicRoutingResult { + let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?; + + let labels_with_bucket_name = report + .into_iter() + .map(|conn_choice_with_bucket| LabelWithBucketName { + label: conn_choice_with_bucket + .routable_connector_choice + .to_string(), + bucket_name: conn_choice_with_bucket.bucket_name, + }) + .collect::>(); + + let request = tonic::Request::new(UpdateEliminationBucketRequest { + id, + params, + labels_with_bucket_name, + config, + }); + + let response = self + .clone() + .update_elimination_bucket(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to update the elimination bucket".to_string(), + ))? + .into_inner(); + Ok(response) + } + async fn invalidate_elimination_bucket( + &self, + id: String, + ) -> DynamicRoutingResult { + let request = tonic::Request::new(InvalidateBucketRequest { id }); + + let response = self + .clone() + .invalidate_bucket(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to invalidate the elimination bucket".to_string(), + ))? + .into_inner(); + Ok(response) + } +} + +impl ForeignTryFrom for EliminationAnalyserConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: EliminationConfig) -> Result { + Ok(Self { + bucket_size: config.bucket_size, + bucket_ttl_in_mins: config.bucket_ttl_in_mins, + }) + } +} +impl ForeignTryFrom for UpdateEliminationBucketConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: EliminationConfig) -> Result { + Ok(Self { + bucket_size: config.bucket_size, + bucket_ttl_in_mins: config.bucket_ttl_in_mins, + }) + } +} diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs new file mode 100644 index 000000000000..2fe909d335ea --- /dev/null +++ b/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs @@ -0,0 +1,196 @@ +use api_models::routing::{ + CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, + SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, +}; +use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; +use error_stack::ResultExt; + +pub use success_rate::{ + success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, + CalSuccessRateRequest, CalSuccessRateResponse, + CurrentBlockThreshold as DynamicCurrentThreshold, InvalidateWindowsRequest, + InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig, + UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, +}; +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions +)] +pub mod success_rate { + tonic::include_proto!("success_rate"); +} +use super::{Client, DynamicRoutingError, DynamicRoutingResult}; +/// The trait Success Based Dynamic Routing would have the functions required to support the calculation and updation window +#[async_trait::async_trait] +pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { + /// To calculate the success rate for the list of chosen connectors + async fn calculate_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + ) -> DynamicRoutingResult; + /// To update the success rate with the given label + async fn update_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + response: Vec, + ) -> DynamicRoutingResult; + /// To invalidates the success rate routing keys + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + ) -> DynamicRoutingResult; +} + +#[async_trait::async_trait] +impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { + async fn calculate_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + ) -> DynamicRoutingResult { + let labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let config = success_rate_based_config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let request = tonic::Request::new(CalSuccessRateRequest { + id, + params, + labels, + config, + }); + + let response = self + .clone() + .fetch_success_rate(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to fetch the success rate".to_string(), + ))? + .into_inner(); + + Ok(response) + } + + async fn update_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + ) -> DynamicRoutingResult { + let config = success_rate_based_config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let labels_with_status = label_input + .into_iter() + .map(|conn_choice| LabelWithStatus { + label: conn_choice.routable_connector_choice.to_string(), + status: conn_choice.status, + }) + .collect(); + + let request = tonic::Request::new(UpdateSuccessRateWindowRequest { + id, + params, + labels_with_status, + config, + }); + + let response = self + .clone() + .update_success_rate_window(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to update the success rate window".to_string(), + ))? + .into_inner(); + + Ok(response) + } + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + ) -> DynamicRoutingResult { + let request = tonic::Request::new(InvalidateWindowsRequest { id }); + + let response = self + .clone() + .invalidate_windows(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to invalidate the success rate routing keys".to_string(), + ))? + .into_inner(); + Ok(response) + } +} + +impl ForeignTryFrom for DynamicCurrentThreshold { + type Error = error_stack::Report; + fn foreign_try_from(current_threshold: CurrentBlockThreshold) -> Result { + Ok(Self { + duration_in_mins: current_threshold.duration_in_mins, + max_total_count: current_threshold + .max_total_count + .get_required_value("max_total_count") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "max_total_count".to_string(), + })?, + }) + } +} + +impl ForeignTryFrom for UpdateSuccessRateWindowConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { + Ok(Self { + max_aggregates_size: config + .max_aggregates_size + .get_required_value("max_aggregate_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "max_aggregates_size".to_string(), + })?, + current_block_threshold: config + .current_block_threshold + .map(ForeignTryFrom::foreign_try_from) + .transpose()?, + }) + } +} + +impl ForeignTryFrom for CalSuccessRateConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { + Ok(Self { + min_aggregates_size: config + .min_aggregates_size + .get_required_value("min_aggregate_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "min_aggregates_size".to_string(), + })?, + default_success_rate: config + .default_success_rate + .get_required_value("default_success_rate") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "default_success_rate".to_string(), + })?, + }) + } +} diff --git a/proto/elimination_rate.proto b/proto/elimination_rate.proto new file mode 100644 index 000000000000..b5f629705118 --- /dev/null +++ b/proto/elimination_rate.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package elimination; + +service EliminationAnalyser { + rpc PerformElimination (EliminationRequest) returns (EliminationResponse); + + rpc UpdateEliminationBucket (UpdateEliminationBucketRequest) returns (UpdateEliminationBucketResponse); + + rpc InvalidateBucket (InvalidateBucketRequest) returns (InvalidateBucketResponse); +} + +// API-1 types +message EliminationRequest { + string id = 1; + string params = 2; + repeated string labels = 3; + EliminationAnalyserConfig config = 4; +} + +message EliminationAnalyserConfig { + uint64 bucket_size = 1; + uint64 bucket_ttl_in_mins = 2; +} + +message EliminationResponse { + repeated LabelWithStatus labels_with_status = 1; +} + +message LabelWithStatus { + bool is_eliminated = 1; + string label = 2; +} + +// API-2 types +message UpdateEliminationBucketRequest { + string id = 1; + string params = 2; + repeated LabelWithBucketName labels_with_bucket_name = 3; + UpdateEliminationBucketConfig config = 4; +} + +message LabelWithBucketName { + string label = 1; + string bucket_name = 2; +} + +message UpdateEliminationBucketConfig { + uint64 bucket_size = 1; + uint64 bucket_ttl_in_mins = 2; +} + +message UpdateEliminationBucketResponse { + string message = 1; +} + +// API-3 types +message InvalidateBucketRequest { + string id = 1; +} + +message InvalidateBucketResponse { + string message = 1; +} \ No newline at end of file From 537c59a42815730647759fdefeea757f983abb14 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:59:24 +0000 Subject: [PATCH 03/27] chore: run formatter --- crates/external_services/src/grpc_client/dynamic_routing.rs | 3 ++- .../src/grpc_client/dynamic_routing/success_rate.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index c69677b33659..9e2f28c6035b 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -8,9 +8,10 @@ pub mod elimination_rate; /// Success Routing Client Interface Implementation pub mod success_rate; -use super::Client; pub use elimination_rate::EliminationAnalyserClient; pub use success_rate::SuccessRateCalculatorClient; + +use super::Client; /// Result type for Dynamic Routing pub type DynamicRoutingResult = CustomResult; diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs index 2fe909d335ea..f6d3efb88769 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs @@ -4,7 +4,6 @@ use api_models::routing::{ }; use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; use error_stack::ResultExt; - pub use success_rate::{ success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, CalSuccessRateRequest, CalSuccessRateResponse, From b91d6401c71e6da4ebb1e92c2c85b0afd3ff2ef8 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Wed, 27 Nov 2024 17:18:47 +0530 Subject: [PATCH 04/27] rename files --- .../external_services/src/grpc_client/dynamic_routing.rs | 8 ++++---- .../{elimination_rate.rs => elimination_rate_client.rs} | 0 .../{success_rate.rs => success_rate_client.rs} | 0 crates/router/src/core/payments/routing.rs | 2 +- crates/router/src/core/routing.rs | 2 +- crates/router/src/core/routing/helpers.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename crates/external_services/src/grpc_client/dynamic_routing/{elimination_rate.rs => elimination_rate_client.rs} (100%) rename crates/external_services/src/grpc_client/dynamic_routing/{success_rate.rs => success_rate_client.rs} (100%) diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index c69677b33659..764773015fe3 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -4,13 +4,13 @@ use common_utils::errors::CustomResult; use router_env::logger; use serde; /// Elimination Routing Client Interface Implementation -pub mod elimination_rate; +pub mod elimination_rate_client; /// Success Routing Client Interface Implementation -pub mod success_rate; +pub mod success_rate_client; use super::Client; -pub use elimination_rate::EliminationAnalyserClient; -pub use success_rate::SuccessRateCalculatorClient; +pub use elimination_rate_client::EliminationAnalyserClient; +pub use success_rate_client::SuccessRateCalculatorClient; /// Result type for Dynamic Routing pub type DynamicRoutingResult = CustomResult; diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs similarity index 100% rename from crates/external_services/src/grpc_client/dynamic_routing/elimination_rate.rs rename to crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs similarity index 100% rename from crates/external_services/src/grpc_client/dynamic_routing/success_rate.rs rename to crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index f8ae9d0dbacf..f7206442a3f4 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -24,7 +24,7 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use external_services::grpc_client::dynamic_routing::success_rate::{ +use external_services::grpc_client::dynamic_routing::success_rate_client::{ CalSuccessRateResponse, SuccessBasedDynamicRouting, }; use kgraph_utils::{ diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 392261ca793e..6d1de0e729b7 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -12,7 +12,7 @@ use common_utils::ext_traits::AsyncExt; use diesel_models::routing_algorithm::RoutingAlgorithm; use error_stack::ResultExt; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use external_services::grpc_client::dynamic_routing::success_rate::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::success_rate_client::SuccessBasedDynamicRouting; use hyperswitch_domain_models::{mandates, payment_address}; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use router_env::{logger, metrics::add_attributes}; diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index e2625f42a878..fa47e347a8a6 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -16,7 +16,7 @@ use diesel_models::configs; use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use external_services::grpc_client::dynamic_routing::success_rate::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::success_rate_client::SuccessBasedDynamicRouting; #[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] From 03ab2fb2000a24a14440f4e9ecbf62d3af967919 Mon Sep 17 00:00:00 2001 From: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:05:18 +0530 Subject: [PATCH 05/27] Update crates/external_services/src/grpc_client/dynamic_routing.rs Co-authored-by: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> --- crates/external_services/src/grpc_client/dynamic_routing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index 9e2f28c6035b..197cc75c64e1 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -24,7 +24,7 @@ pub enum DynamicRoutingError { /// The required field name field: String, }, - /// Error from Dynamic Routing Server while perfrming success_rate analysis + /// Error from Dynamic Routing Server while performing success_rate analysis #[error("Error from Dynamic Routing Server while perfrming success_rate analysis : {0}")] SuccessRateBasedRoutingFailure(String), /// Error from Dynamic Routing Server while perfrming elimination From a4b61cad4d6d5a5853f32a4fa1e33d103a53784d Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:50:52 +0000 Subject: [PATCH 06/27] chore: run formatter --- crates/external_services/src/grpc_client/dynamic_routing.rs | 3 ++- .../src/grpc_client/dynamic_routing/success_rate_client.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index b93b71818194..e5050227fa89 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -8,9 +8,10 @@ pub mod elimination_rate_client; /// Success Routing Client Interface Implementation pub mod success_rate_client; -use super::Client; pub use elimination_rate_client::EliminationAnalyserClient; pub use success_rate_client::SuccessRateCalculatorClient; + +use super::Client; /// Result type for Dynamic Routing pub type DynamicRoutingResult = CustomResult; diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs index 2fe909d335ea..f6d3efb88769 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs @@ -4,7 +4,6 @@ use api_models::routing::{ }; use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; use error_stack::ResultExt; - pub use success_rate::{ success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, CalSuccessRateRequest, CalSuccessRateResponse, From 3199e570679c80ea4a8d2089eea57ec33b78c729 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Tue, 3 Dec 2024 11:21:27 +0530 Subject: [PATCH 07/27] resolve conflicts --- crates/api_models/src/routing.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 6ef886ff5309..f0293258d28b 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -840,9 +840,3 @@ impl RoutableConnectorChoiceWithBucketName { } } } - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct EliminationAnalyserConfig { - pub bucket_size: u64, - pub bucket_ttl_in_mins: u64, -} From 7488944d6bd79b2eb6611c69a995b2a8f6970461 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Tue, 3 Dec 2024 11:37:42 +0530 Subject: [PATCH 08/27] add external fields --- .../elimination_rate_client.rs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs index 0cafc8a5a396..270913900575 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -2,7 +2,7 @@ use api_models::routing::{ EliminationAnalyserConfig as EliminationConfig, RoutableConnectorChoice, RoutableConnectorChoiceWithBucketName, }; -use common_utils::transformers::ForeignTryFrom; +use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; pub use elimination_rate::{ elimination_analyser_client::EliminationAnalyserClient, EliminationAnalyserConfig, EliminationRequest, EliminationResponse, InvalidateBucketRequest, InvalidateBucketResponse, @@ -141,8 +141,18 @@ impl ForeignTryFrom for EliminationAnalyserConfig { type Error = error_stack::Report; fn foreign_try_from(config: EliminationConfig) -> Result { Ok(Self { - bucket_size: config.bucket_size, - bucket_ttl_in_mins: config.bucket_ttl_in_mins, + bucket_size: config + .bucket_size + .get_required_value("bucket_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_size".to_string(), + })?, + bucket_ttl_in_mins: config + .bucket_ttl_in_mins + .get_required_value("bucket_ttl_in_mins") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_ttl_in_mins".to_string(), + })?, }) } } @@ -150,8 +160,18 @@ impl ForeignTryFrom for UpdateEliminationBucketConfig { type Error = error_stack::Report; fn foreign_try_from(config: EliminationConfig) -> Result { Ok(Self { - bucket_size: config.bucket_size, - bucket_ttl_in_mins: config.bucket_ttl_in_mins, + bucket_size: config + .bucket_size + .get_required_value("bucket_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_size".to_string(), + })?, + bucket_ttl_in_mins: config + .bucket_ttl_in_mins + .get_required_value("bucket_ttl_in_mins") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_ttl_in_mins".to_string(), + })?, }) } } From 03530136b794b0eac56698603e591e00e5cdc3ac Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Tue, 3 Dec 2024 15:30:17 +0530 Subject: [PATCH 09/27] change the value types --- crates/api_models/src/routing.rs | 7 +++---- .../grpc_client/dynamic_routing/elimination_rate_client.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index f0293258d28b..d985e3f758e0 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -705,14 +705,13 @@ pub struct ToggleDynamicRoutingPath { #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct EliminationRoutingConfig { pub params: Option>, - // pub labels: Option>, pub elimination_analyser_config: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct EliminationAnalyserConfig { - pub bucket_size: Option, - pub bucket_ttl_in_mins: Option, + pub bucket_size: Option, + pub bucket_ttl_in_mins: Option, } impl Default for EliminationRoutingConfig { @@ -721,7 +720,7 @@ impl Default for EliminationRoutingConfig { params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), elimination_analyser_config: Some(EliminationAnalyserConfig { bucket_size: Some(5), - bucket_ttl_in_mins: Some(2.0), + bucket_ttl_in_mins: Some(2), }), } } diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs index 270913900575..ce3db4b79da3 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -103,7 +103,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient { .collect::>(); let request = tonic::Request::new(UpdateEliminationBucketRequest { - id, + id params, labels_with_bucket_name, config, From 2003e6c1585441010b4e94b950736d6028ae2368 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Tue, 3 Dec 2024 15:59:09 +0530 Subject: [PATCH 10/27] minor changes --- .../src/grpc_client/dynamic_routing/elimination_rate_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs index ce3db4b79da3..270913900575 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -103,7 +103,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient { .collect::>(); let request = tonic::Request::new(UpdateEliminationBucketRequest { - id + id, params, labels_with_bucket_name, config, From a06e7c82a45cee10c81b5c51b6bbee89711a504b Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Wed, 4 Dec 2024 19:12:10 +0530 Subject: [PATCH 11/27] proto update --- .../elimination_rate_client.rs | 33 ++++--------------- proto/elimination_rate.proto | 32 ++++++++++-------- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs index 270913900575..62e6a909900d 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -4,17 +4,17 @@ use api_models::routing::{ }; use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; pub use elimination_rate::{ - elimination_analyser_client::EliminationAnalyserClient, EliminationAnalyserConfig, + elimination_analyser_client::EliminationAnalyserClient, EliminationBucketConfig, EliminationRequest, EliminationResponse, InvalidateBucketRequest, InvalidateBucketResponse, - LabelWithBucketName, UpdateEliminationBucketConfig, UpdateEliminationBucketRequest, - UpdateEliminationBucketResponse, + LabelWithBucketName, UpdateEliminationBucketRequest, UpdateEliminationBucketResponse, }; use error_stack::ResultExt; #[allow( missing_docs, unused_qualifications, clippy::unwrap_used, - clippy::as_conversions + clippy::as_conversions, + clippy::use_self )] pub mod elimination_rate { tonic::include_proto!("elimination"); @@ -73,7 +73,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient { let response = self .clone() - .perform_elimination(request) + .get_elimination_status(request) .await .change_context(DynamicRoutingError::EliminationRateRoutingFailure( "Failed to perform the elimination analysis".to_string(), @@ -137,7 +137,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient { } } -impl ForeignTryFrom for EliminationAnalyserConfig { +impl ForeignTryFrom for EliminationBucketConfig { type Error = error_stack::Report; fn foreign_try_from(config: EliminationConfig) -> Result { Ok(Self { @@ -147,26 +147,7 @@ impl ForeignTryFrom for EliminationAnalyserConfig { .change_context(DynamicRoutingError::MissingRequiredField { field: "bucket_size".to_string(), })?, - bucket_ttl_in_mins: config - .bucket_ttl_in_mins - .get_required_value("bucket_ttl_in_mins") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "bucket_ttl_in_mins".to_string(), - })?, - }) - } -} -impl ForeignTryFrom for UpdateEliminationBucketConfig { - type Error = error_stack::Report; - fn foreign_try_from(config: EliminationConfig) -> Result { - Ok(Self { - bucket_size: config - .bucket_size - .get_required_value("bucket_size") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "bucket_size".to_string(), - })?, - bucket_ttl_in_mins: config + bucket_leak_interval_in_secs: config .bucket_ttl_in_mins .get_required_value("bucket_ttl_in_mins") .change_context(DynamicRoutingError::MissingRequiredField { diff --git a/proto/elimination_rate.proto b/proto/elimination_rate.proto index b5f629705118..c5f10597ade2 100644 --- a/proto/elimination_rate.proto +++ b/proto/elimination_rate.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package elimination; service EliminationAnalyser { - rpc PerformElimination (EliminationRequest) returns (EliminationResponse); + rpc GetEliminationStatus (EliminationRequest) returns (EliminationResponse); rpc UpdateEliminationBucket (UpdateEliminationBucketRequest) returns (UpdateEliminationBucketResponse); @@ -14,12 +14,12 @@ message EliminationRequest { string id = 1; string params = 2; repeated string labels = 3; - EliminationAnalyserConfig config = 4; + EliminationBucketConfig config = 4; } -message EliminationAnalyserConfig { +message EliminationBucketConfig { uint64 bucket_size = 1; - uint64 bucket_ttl_in_mins = 2; + uint64 bucket_leak_interval_in_secs = 2; } message EliminationResponse { @@ -27,8 +27,9 @@ message EliminationResponse { } message LabelWithStatus { - bool is_eliminated = 1; - string label = 2; + string label = 1; + bool is_eliminated = 2; + string bucket_name = 3; } // API-2 types @@ -36,7 +37,7 @@ message UpdateEliminationBucketRequest { string id = 1; string params = 2; repeated LabelWithBucketName labels_with_bucket_name = 3; - UpdateEliminationBucketConfig config = 4; + EliminationBucketConfig config = 4; } message LabelWithBucketName { @@ -44,13 +45,12 @@ message LabelWithBucketName { string bucket_name = 2; } -message UpdateEliminationBucketConfig { - uint64 bucket_size = 1; - uint64 bucket_ttl_in_mins = 2; -} - message UpdateEliminationBucketResponse { - string message = 1; + enum UpdationStatus { + BUCKET_UPDATION_SUCCEEDED = 0; + BUCKET_UPDATION_FAILED = 1; + } + UpdationStatus status = 1; } // API-3 types @@ -59,5 +59,9 @@ message InvalidateBucketRequest { } message InvalidateBucketResponse { - string message = 1; + enum InvalidationStatus { + BUCKET_INVALIDATION_SUCCEEDED = 0; + BUCKET_INVALIDATION_FAILED = 1; + } + InvalidationStatus status = 1; } \ No newline at end of file From 651973c648d69defbcbd375c7800a06698f54280 Mon Sep 17 00:00:00 2001 From: Aprabhat19 Date: Fri, 6 Dec 2024 14:13:03 +0530 Subject: [PATCH 12/27] address naming changes --- crates/api_models/src/routing.rs | 4 ++-- .../grpc_client/dynamic_routing/elimination_rate_client.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 1ac77274c4e9..2e570816ab4c 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -746,7 +746,7 @@ pub struct EliminationRoutingConfig { #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct EliminationAnalyserConfig { pub bucket_size: Option, - pub bucket_ttl_in_mins: Option, + pub bucket_leak_interval_in_secs: Option, } impl Default for EliminationRoutingConfig { @@ -755,7 +755,7 @@ impl Default for EliminationRoutingConfig { params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), elimination_analyser_config: Some(EliminationAnalyserConfig { bucket_size: Some(5), - bucket_ttl_in_mins: Some(2), + bucket_leak_interval_in_secs: Some(2), }), } } diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs index 62e6a909900d..6587b7941f4c 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -148,10 +148,10 @@ impl ForeignTryFrom for EliminationBucketConfig { field: "bucket_size".to_string(), })?, bucket_leak_interval_in_secs: config - .bucket_ttl_in_mins - .get_required_value("bucket_ttl_in_mins") + .bucket_leak_interval_in_secs + .get_required_value("bucket_leak_interval_in_secs") .change_context(DynamicRoutingError::MissingRequiredField { - field: "bucket_ttl_in_mins".to_string(), + field: "bucket_leak_interval_in_secs".to_string(), })?, }) } From 6388c20df2608ec8f4c48146b7cedcabc627f334 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Thu, 12 Dec 2024 10:19:36 +0530 Subject: [PATCH 13/27] init for er in core --- crates/router/Cargo.toml | 2 +- crates/router/src/core/payments/routing.rs | 123 ++++++++++++++++++++- crates/router/src/core/routing/helpers.rs | 20 ++-- 3 files changed, 133 insertions(+), 12 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 58815ad92493..b3c960cf7fae 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [features] default = ["common_default", "v1"] -common_default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] +common_default = ["dynamic_routing", "kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] olap = ["hyperswitch_domain_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] tls = ["actix-web/rustls-0_22"] email = ["external_services/email", "scheduler/email", "olap"] diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index b3be47a87438..05004aad515b 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1307,7 +1307,7 @@ pub async fn perform_success_based_routing( .ok_or(errors::RoutingError::SuccessRateClientInitializationError) .attach_printable("success_rate gRPC client not found")?; - let success_based_routing_configs = routing::helpers::fetch_success_based_routing_configs( + let success_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs( state, business_profile, success_based_algo_ref @@ -1384,3 +1384,124 @@ pub async fn perform_success_based_routing( Ok(routable_connectors) } } + +/// elimination dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn perform_elimination_routing( + state: &SessionState, + routable_connectors: Vec, + business_profile: &domain::Profile, + elimination_routing_configs_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, +) -> RoutingResult> { + let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::RoutingError::DeserializationError { + from: "JSON".to_string(), + to: "DynamicRoutingAlgorithmRef".to_string(), + }) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? + .unwrap_or_default(); + + let elimination_algo_ref = dynamic_routing_algo_ref + .elimination_routing_algorithm + .ok_or(errors::RoutingError::GenericNotFoundError { field: "elimintaion_algorithm".to_string() }) + .attach_printable( + "elimintaion_algorithm not found in dynamic_routing_algorithm from business_profile table", + )?; + + if elimination_algo_ref.enabled_feature + == api_routing::DynamicRoutingFeatures::DynamicConnectorSelection + { + logger::debug!( + "performing elimintaion_routing for profile {}", + business_profile.get_id().get_string_repr() + ); + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::RoutingError::SuccessRateClientInitializationError) + .attach_printable("success_rate gRPC client not found")?; + + let elimination_routing_config = routing::helpers::fetch_dynamic_routing_configs( + state, + business_profile, + elimination_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::RoutingError::GenericNotFoundError { + field: "elimination_routing_algorithm_id".to_string(), + }) + .attach_printable( + "elimintaion_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) + .attach_printable("unable to fetch elimination dynamic routing configs")?; + + let elimination_routing_config_params = elimination_routing_configs_params_interpolator + .get_string_val( + elimination_routing_config + .params + .as_ref() + .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)?, + ); + + let tenant_business_profile_id = routing::helpers::generate_tenant_business_profile_id( + &state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr(), + ); + + let elimination_based_connectors: CalSuccessRateResponse = client + .calculate_success_rate( + tenant_business_profile_id, + elimination_routing_config, + elimination_routing_config_params, + routable_connectors, + ) + .await + .change_context(errors::RoutingError::SuccessRateCalculationError) + .attach_printable( + "unable to calculate/fetch success rate from dynamic routing service", + )?; + + let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); + for label_with_score in elimination_based_connectors.labels_with_score { + let (connector, merchant_connector_id) = label_with_score.label + .split_once(':') + .ok_or(errors::RoutingError::InvalidSuccessBasedConnectorLabel(label_with_score.label.to_string())) + .attach_printable( + "unable to split connector_name and mca_id from the label obtained by the dynamic routing service", + )?; + connectors.push(api_routing::RoutableConnectorChoice { + choice_kind: api_routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str(connector) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "RoutableConnectors".to_string(), + }) + .attach_printable("unable to convert String to RoutableConnectors")?, + merchant_connector_id: Some( + common_utils::id_type::MerchantConnectorAccountId::wrap( + merchant_connector_id.to_string(), + ) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "MerchantConnectorAccountId".to_string(), + }) + .attach_printable("unable to convert MerchantConnectorAccountId from string")?, + ), + }); + } + logger::debug!(success_based_routing_connectors=?connectors); + Ok(connectors) + } else { + Ok(routable_connectors) + } +} diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 6a5e3bd0264b..de0b66edc81d 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -603,15 +603,15 @@ pub async fn refresh_success_based_routing_cache( /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] -pub async fn fetch_success_based_routing_configs( +pub async fn fetch_dynamic_routing_configs( state: &SessionState, business_profile: &domain::Profile, - success_based_routing_id: id_type::RoutingId, + dynamic_routing_id: id_type::RoutingId, ) -> RouterResult { let key = format!( "{}_{}", business_profile.get_id().get_string_repr(), - success_based_routing_id.get_string_repr() + dynamic_routing_id.get_string_repr() ); if let Some(config) = @@ -619,25 +619,25 @@ pub async fn fetch_success_based_routing_configs( { Ok(config.as_ref().clone()) } else { - let success_rate_algorithm = state + let dynamic_algorithm = state .store .find_routing_algorithm_by_profile_id_algorithm_id( business_profile.get_id(), - &success_based_routing_id, + &dynamic_routing_id, ) .await .change_context(errors::ApiErrorResponse::ResourceIdNotFound) - .attach_printable("unable to retrieve success_rate_algorithm for profile from db")?; + .attach_printable("unable to retrieve dynamic algorithm for profile from db")?; - let success_rate_config = success_rate_algorithm + let dynamic_config = dynamic_algorithm .algorithm_data .parse_value::("SuccessBasedRoutingConfig") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to parse success_based_routing_config struct")?; - refresh_success_based_routing_cache(state, key.as_str(), success_rate_config.clone()).await; + refresh_success_based_routing_cache(state, key.as_str(), dynamic_config.clone()).await; - Ok(success_rate_config) + Ok(dynamic_config) } } @@ -682,7 +682,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( }, )?; - let success_based_routing_configs = fetch_success_based_routing_configs( + let success_based_routing_configs = fetch_dynamic_routing_configs( state, business_profile, success_based_algo_ref From 3ff2e0041c75116393ea26b3841ec39887b718df Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 04:53:34 +0000 Subject: [PATCH 14/27] chore: run formatter --- crates/router/src/core/payments/routing.rs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 05004aad515b..b30138ac607b 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1393,18 +1393,17 @@ pub async fn perform_elimination_routing( business_profile: &domain::Profile, elimination_routing_configs_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, ) -> RoutingResult> { - let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::RoutingError::DeserializationError { - from: "JSON".to_string(), - to: "DynamicRoutingAlgorithmRef".to_string(), - }) - .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? - .unwrap_or_default(); + let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::RoutingError::DeserializationError { + from: "JSON".to_string(), + to: "DynamicRoutingAlgorithmRef".to_string(), + }) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? + .unwrap_or_default(); let elimination_algo_ref = dynamic_routing_algo_ref .elimination_routing_algorithm @@ -1471,7 +1470,8 @@ pub async fn perform_elimination_routing( "unable to calculate/fetch success rate from dynamic routing service", )?; - let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); + let mut connectors = + Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); for label_with_score in elimination_based_connectors.labels_with_score { let (connector, merchant_connector_id) = label_with_score.label .split_once(':') From d50175377460ed25a8cf5728bb0fd93dd2510569 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Thu, 12 Dec 2024 11:15:12 +0530 Subject: [PATCH 15/27] add the cache functions --- crates/router/src/core/errors.rs | 4 + crates/router/src/core/payments/routing.rs | 99 +++++++++++----------- crates/router/src/core/routing/helpers.rs | 77 +++++++++++++++++ 3 files changed, 131 insertions(+), 49 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 96321d097946..36968c66a010 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -336,6 +336,10 @@ pub enum RoutingError { SuccessRateCalculationError, #[error("Success rate client from dynamic routing gRPC service not initialized")] SuccessRateClientInitializationError, + #[error("Elimintaion client from dynamic routing gRPC service not initialized")] + ElimintaionClientInitializationError, + #[error("Unable to analyze elimintaion routing config from dynamic routing service")] + ElimintaionRoutingCalculationError, #[error("Unable to convert from '{from}' to '{to}'")] GenericConversionError { from: String, to: String }, #[error("Invalid success based connector label received from dynamic routing service: '{0}'")] diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 26aaa4238ebb..ba6d289641ac 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1393,18 +1393,19 @@ pub async fn perform_elimination_routing( business_profile: &domain::Profile, elimination_routing_configs_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, ) -> RoutingResult> { - let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::RoutingError::DeserializationError { - from: "JSON".to_string(), - to: "DynamicRoutingAlgorithmRef".to_string(), - }) - .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? - .unwrap_or_default(); + use external_services::grpc_client::dynamic_routing::elimination_rate_client::EliminationBasedRouting; + + let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::RoutingError::DeserializationError { + from: "JSON".to_string(), + to: "DynamicRoutingAlgorithmRef".to_string(), + }) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? + .unwrap_or_default(); let elimination_algo_ref = dynamic_routing_algo_ref .elimination_routing_algorithm @@ -1423,10 +1424,10 @@ pub async fn perform_elimination_routing( let client = state .grpc_client .dynamic_routing - .success_rate_client + .elimination_rate_client .as_ref() - .ok_or(errors::RoutingError::SuccessRateClientInitializationError) - .attach_printable("success_rate gRPC client not found")?; + .ok_or(errors::RoutingError::ElimintaionClientInitializationError) + .attach_printable("elimintaion routing's gRPC client not found")?; let elimination_routing_config = routing::helpers::fetch_dynamic_routing_configs( state, @@ -1459,48 +1460,48 @@ pub async fn perform_elimination_routing( ); let elimination_based_connectors: CalSuccessRateResponse = client - .calculate_success_rate( + .perform_elimination_routing( tenant_business_profile_id, - elimination_routing_config, elimination_routing_config_params, routable_connectors, + elimination_routing_config, ) .await - .change_context(errors::RoutingError::SuccessRateCalculationError) + .change_context(errors::RoutingError::ElimintaionRoutingCalculationError) .attach_printable( - "unable to calculate/fetch success rate from dynamic routing service", + "unable to analyze/fetch elimintaion routing from dynamic routing service", )?; - let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); - for label_with_score in elimination_based_connectors.labels_with_score { - let (connector, merchant_connector_id) = label_with_score.label - .split_once(':') - .ok_or(errors::RoutingError::InvalidSuccessBasedConnectorLabel(label_with_score.label.to_string())) - .attach_printable( - "unable to split connector_name and mca_id from the label obtained by the dynamic routing service", - )?; - connectors.push(api_routing::RoutableConnectorChoice { - choice_kind: api_routing::RoutableChoiceKind::FullStruct, - connector: common_enums::RoutableConnectors::from_str(connector) - .change_context(errors::RoutingError::GenericConversionError { - from: "String".to_string(), - to: "RoutableConnectors".to_string(), - }) - .attach_printable("unable to convert String to RoutableConnectors")?, - merchant_connector_id: Some( - common_utils::id_type::MerchantConnectorAccountId::wrap( - merchant_connector_id.to_string(), - ) - .change_context(errors::RoutingError::GenericConversionError { - from: "String".to_string(), - to: "MerchantConnectorAccountId".to_string(), - }) - .attach_printable("unable to convert MerchantConnectorAccountId from string")?, - ), - }); - } - logger::debug!(success_based_routing_connectors=?connectors); - Ok(connectors) + // let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); + // for label_with_score in elimination_based_connectors.labels_with_score { + // let (connector, merchant_connector_id) = label_with_score.label + // .split_once(':') + // .ok_or(errors::RoutingError::InvalidSuccessBasedConnectorLabel(label_with_score.label.to_string())) + // .attach_printable( + // "unable to split connector_name and mca_id from the label obtained by the dynamic routing service", + // )?; + // connectors.push(api_routing::RoutableConnectorChoice { + // choice_kind: api_routing::RoutableChoiceKind::FullStruct, + // connector: common_enums::RoutableConnectors::from_str(connector) + // .change_context(errors::RoutingError::GenericConversionError { + // from: "String".to_string(), + // to: "RoutableConnectors".to_string(), + // }) + // .attach_printable("unable to convert String to RoutableConnectors")?, + // merchant_connector_id: Some( + // common_utils::id_type::MerchantConnectorAccountId::wrap( + // merchant_connector_id.to_string(), + // ) + // .change_context(errors::RoutingError::GenericConversionError { + // from: "String".to_string(), + // to: "MerchantConnectorAccountId".to_string(), + // }) + // .attach_printable("unable to convert MerchantConnectorAccountId from string")?, + // ), + // }); + // } + // logger::debug!(success_based_routing_connectors=?connectors); + Ok(routable_connectors) } else { Ok(routable_connectors) } diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index b3971d0d6444..70b191b6eddf 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -600,6 +600,83 @@ pub async fn refresh_success_based_routing_cache( config } +/// Retrieves cached elimination routing configs specific to tenant and profile +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn get_cached_elimination_routing_config_for_profile<'a>( + state: &SessionState, + key: &str, +) -> Option> { + cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .get_val::>(cache::CacheKey { + key: key.to_string(), + prefix: state.tenant.redis_key_prefix.clone(), + }) + .await +} + +/// Refreshes the cached success_based routing configs specific to tenant and profile +#[cfg(feature = "v1")] +pub async fn refresh_elimination_routing_cache( + state: &SessionState, + key: &str, + elimination_routing_config: routing_types::EliminationRoutingConfig, +) -> Arc { + let config = Arc::new(elimination_routing_config); + cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .push( + cache::CacheKey { + key: key.to_string(), + prefix: state.tenant.redis_key_prefix.clone(), + }, + config.clone(), + ) + .await; + config +} + +/// Checked fetch of success based routing configs +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn fetch_elimintaion_routing_configs( + state: &SessionState, + business_profile: &domain::Profile, + elimination_routing_id: id_type::RoutingId, +) -> RouterResult { + let key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + elimination_routing_id.get_string_repr() + ); + + if let Some(config) = + get_cached_elimination_routing_config_for_profile(state, key.as_str()).await + { + Ok(config.as_ref().clone()) + } else { + let elimination_algorithm = state + .store + .find_routing_algorithm_by_profile_id_algorithm_id( + business_profile.get_id(), + &elimination_routing_id, + ) + .await + .change_context(errors::ApiErrorResponse::ResourceIdNotFound) + .attach_printable( + "unable to retrieve elimination routing algorithm for profile from db", + )?; + + let elimination_config = elimination_algorithm + .algorithm_data + .parse_value::("EliminationRoutingConfig") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to parse EliminationRoutingConfig struct")?; + + refresh_elimination_routing_cache(state, key.as_str(), elimination_config.clone()).await; + + Ok(elimination_config) + } +} + /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] From 057f48ca94603337b146bafa46da3f6148d5413a Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Tue, 17 Dec 2024 21:45:08 +0530 Subject: [PATCH 16/27] add the er in core flows --- crates/router/src/core/errors.rs | 2 ++ crates/router/src/core/payments.rs | 3 ++- .../payments/operations/payment_response.rs | 2 +- crates/router/src/core/payments/routing.rs | 26 +++++++++---------- crates/router/src/core/routing/helpers.rs | 22 ++++++++-------- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 36968c66a010..a56eb2f56039 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -340,6 +340,8 @@ pub enum RoutingError { ElimintaionClientInitializationError, #[error("Unable to analyze elimintaion routing config from dynamic routing service")] ElimintaionRoutingCalculationError, + #[error("Unable to retrieve elimination based routing config")] + EliminationRoutingConfigError, #[error("Unable to convert from '{from}' to '{to}'")] GenericConversionError { from: String, to: String }, #[error("Invalid success based connector label received from dynamic routing service: '{0}'")] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 99b0a6354006..fd875c3f7afc 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6057,7 +6057,7 @@ where if routing_choice.routing_type.is_dynamic_routing() { let success_based_routing_config_params_interpolator = - routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new( + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( payment_data.get_payment_attempt().payment_method, payment_data.get_payment_attempt().payment_method_type, payment_data.get_payment_attempt().authentication_type, @@ -6087,6 +6087,7 @@ where .and_then(|card_isin| card_isin.as_str()) .map(|card_isin| card_isin.to_string()), ); + if dynamic_routing_config.success routing::perform_success_based_routing( state, connectors.clone(), diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index d77236c8a2bc..8e2f1ea1ae61 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1974,7 +1974,7 @@ async fn payment_response_update_tracker( let business_profile = business_profile.clone(); let payment_attempt = payment_attempt.clone(); let success_based_routing_config_params_interpolator = - routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new( + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( payment_attempt.payment_method, payment_attempt.payment_method_type, payment_attempt.authentication_type, diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 2ac192249542..e579c6258433 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -23,9 +23,11 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use external_services::grpc_client::dynamic_routing::success_rate_client::{ +use external_services::grpc_client::dynamic_routing::{ + elimination_rate_client::EliminationResponse, + success_rate_client::{ CalSuccessRateResponse, SuccessBasedDynamicRouting, -}; +}}; use hyperswitch_domain_models::address::Address; use kgraph_utils::{ mca as mca_graph, @@ -1270,7 +1272,7 @@ pub async fn perform_success_based_routing( state: &SessionState, routable_connectors: Vec, business_profile: &domain::Profile, - success_based_routing_config_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, + success_based_routing_config_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, ) -> RoutingResult> { let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile @@ -1307,7 +1309,7 @@ pub async fn perform_success_based_routing( .ok_or(errors::RoutingError::SuccessRateClientInitializationError) .attach_printable("success_rate gRPC client not found")?; - let success_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs( + let success_based_routing_configs = routing::helpers::fetch_success_based_routing_config( state, business_profile, success_based_algo_ref @@ -1391,13 +1393,10 @@ pub async fn perform_elimination_routing( state: &SessionState, routable_connectors: Vec, business_profile: &domain::Profile, - elimination_routing_configs_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, + elimination_routing_configs_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, ) -> RoutingResult> { -<<<<<<< HEAD use external_services::grpc_client::dynamic_routing::elimination_rate_client::EliminationBasedRouting; -======= ->>>>>>> 3ff2e0041c75116393ea26b3841ec39887b718df let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile .dynamic_routing_algorithm .clone() @@ -1432,7 +1431,7 @@ pub async fn perform_elimination_routing( .ok_or(errors::RoutingError::ElimintaionClientInitializationError) .attach_printable("elimintaion routing's gRPC client not found")?; - let elimination_routing_config = routing::helpers::fetch_dynamic_routing_configs( + let elimination_routing_config = routing::helpers::fetch_elimintaion_routing_configs( state, business_profile, elimination_algo_ref @@ -1446,7 +1445,7 @@ pub async fn perform_elimination_routing( )?, ) .await - .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) + .change_context(errors::RoutingError::EliminationRoutingConfigError) .attach_printable("unable to fetch elimination dynamic routing configs")?; let elimination_routing_config_params = elimination_routing_configs_params_interpolator @@ -1462,12 +1461,12 @@ pub async fn perform_elimination_routing( business_profile.get_id().get_string_repr(), ); - let elimination_based_connectors: CalSuccessRateResponse = client + let elimination_based_connectors: EliminationResponse = client .perform_elimination_routing( tenant_business_profile_id, elimination_routing_config_params, - routable_connectors, - elimination_routing_config, + routable_connectors.clone(), + elimination_routing_config.elimination_analyser_config, ) .await .change_context(errors::RoutingError::ElimintaionRoutingCalculationError) @@ -1475,6 +1474,7 @@ pub async fn perform_elimination_routing( "unable to analyze/fetch elimintaion routing from dynamic routing service", )?; + println!(">>>>>>>>>>>>>>>>> Elim connectors {:?}", elimination_based_connectors); // let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); // for label_with_score in elimination_based_connectors.labels_with_score { // let (connector, merchant_connector_id) = label_with_score.label diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 70b191b6eddf..6427bc9b7eea 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -680,15 +680,15 @@ pub async fn fetch_elimintaion_routing_configs( /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] -pub async fn fetch_dynamic_routing_configs( +pub async fn fetch_success_based_routing_config( state: &SessionState, business_profile: &domain::Profile, - dynamic_routing_id: id_type::RoutingId, + success_based_routing_id: id_type::RoutingId, ) -> RouterResult { let key = format!( "{}_{}", business_profile.get_id().get_string_repr(), - dynamic_routing_id.get_string_repr() + success_based_routing_id.get_string_repr() ); if let Some(config) = @@ -700,21 +700,21 @@ pub async fn fetch_dynamic_routing_configs( .store .find_routing_algorithm_by_profile_id_algorithm_id( business_profile.get_id(), - &dynamic_routing_id, + &success_based_routing_id, ) .await .change_context(errors::ApiErrorResponse::ResourceIdNotFound) .attach_printable("unable to retrieve dynamic algorithm for profile from db")?; - let dynamic_config = dynamic_algorithm + let success_based_routing_config = dynamic_algorithm .algorithm_data .parse_value::("SuccessBasedRoutingConfig") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to parse success_based_routing_config struct")?; - refresh_success_based_routing_cache(state, key.as_str(), dynamic_config.clone()).await; + refresh_success_based_routing_cache(state, key.as_str(), success_based_routing_config.clone()).await; - Ok(dynamic_config) + Ok(success_based_routing_config) } } @@ -726,7 +726,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( payment_attempt: &storage::PaymentAttempt, routable_connectors: Vec, business_profile: &domain::Profile, - success_based_routing_config_params_interpolator: SuccessBasedRoutingConfigParamsInterpolator, + success_based_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = business_profile @@ -759,7 +759,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( }, )?; - let success_based_routing_configs = fetch_dynamic_routing_configs( + let success_based_routing_configs = fetch_success_based_routing_config( state, business_profile, success_based_algo_ref @@ -1326,7 +1326,7 @@ pub async fn default_specific_dynamic_routing_setup( Ok(ApplicationResponse::Json(new_record)) } -pub struct SuccessBasedRoutingConfigParamsInterpolator { +pub struct DynamicRoutingConfigParamsInterpolator { pub payment_method: Option, pub payment_method_type: Option, pub authentication_type: Option, @@ -1336,7 +1336,7 @@ pub struct SuccessBasedRoutingConfigParamsInterpolator { pub card_bin: Option, } -impl SuccessBasedRoutingConfigParamsInterpolator { +impl DynamicRoutingConfigParamsInterpolator { pub fn new( payment_method: Option, payment_method_type: Option, From 9f1137a04e2fd0eb60bc920f7c37bace2d70ad88 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Tue, 17 Dec 2024 23:00:33 +0530 Subject: [PATCH 17/27] complete core flow --- crates/router/src/core/errors.rs | 4 + crates/router/src/core/payments.rs | 142 +++++++++++++-------- crates/router/src/core/payments/routing.rs | 84 +++++++----- crates/router/src/core/routing/helpers.rs | 8 +- 4 files changed, 148 insertions(+), 90 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index a56eb2f56039..883aec7cbd97 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -342,6 +342,10 @@ pub enum RoutingError { ElimintaionRoutingCalculationError, #[error("Unable to retrieve elimination based routing config")] EliminationRoutingConfigError, + #[error( + "Invalid elimination based connector label received from dynamic routing service: '{0}'" + )] + InvalidEliminationBasedConnectorLabel(String), #[error("Unable to convert from '{from}' to '{to}'")] GenericConversionError { from: String, to: String }, #[error("Invalid success based connector label received from dynamic routing service: '{0}'")] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 583ad80c3946..68dd2b9b03ad 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6040,71 +6040,103 @@ where .parse_value("DynamicRoutingAlgorithmRef") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")?; - let dynamic_split = api_models::routing::RoutingVolumeSplit { - routing_type: api_models::routing::RoutingType::Dynamic, - split: dynamic_routing_config - .dynamic_routing_volume_split - .unwrap_or_default(), - }; - let static_split: api_models::routing::RoutingVolumeSplit = - api_models::routing::RoutingVolumeSplit { - routing_type: api_models::routing::RoutingType::Static, - split: crate::consts::DYNAMIC_ROUTING_MAX_VOLUME - - dynamic_routing_config - .dynamic_routing_volume_split - .unwrap_or_default(), - }; - let volume_split_vec = vec![dynamic_split, static_split]; - let routing_choice = - routing::perform_dynamic_routing_volume_split(volume_split_vec, None) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("failed to perform volume split on routing type")?; - - if routing_choice.routing_type.is_dynamic_routing() { - let success_based_routing_config_params_interpolator = - routing_helpers::DynamicRoutingConfigParamsInterpolator::new( - payment_data.get_payment_attempt().payment_method, - payment_data.get_payment_attempt().payment_method_type, - payment_data.get_payment_attempt().authentication_type, - payment_data.get_payment_attempt().currency, - payment_data - .get_billing_address() - .and_then(|address| address.address) - .and_then(|address| address.country), - payment_data - .get_payment_attempt() - .payment_method_data - .as_ref() - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card")) - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card_network")) - .and_then(|network| network.as_str()) - .map(|network| network.to_string()), - payment_data - .get_payment_attempt() - .payment_method_data - .as_ref() - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card")) - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card_isin")) - .and_then(|card_isin| card_isin.as_str()) - .map(|card_isin| card_isin.to_string()), - ); - if dynamic_routing_config.success + // let dynamic_split = api_models::routing::RoutingVolumeSplit { + // routing_type: api_models::routing::RoutingType::Dynamic, + // split: dynamic_routing_config + // .dynamic_routing_volume_split + // .unwrap_or_default(), + // }; + // let static_split: api_models::routing::RoutingVolumeSplit = + // api_models::routing::RoutingVolumeSplit { + // routing_type: api_models::routing::RoutingType::Static, + // split: crate::consts::DYNAMIC_ROUTING_MAX_VOLUME + // - dynamic_routing_config + // .dynamic_routing_volume_split + // .unwrap_or_default(), + // }; + // let volume_split_vec = vec![dynamic_split, static_split]; + // let routing_choice = + // routing::perform_dynamic_routing_volume_split(volume_split_vec, None) + // .change_context(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("failed to perform volume split on routing type")?; + // + // if routing_choice.routing_type.is_dynamic_routing() { + let dynamic_routing_config_params_interpolator = + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( + payment_data.get_payment_attempt().payment_method, + payment_data.get_payment_attempt().payment_method_type, + payment_data.get_payment_attempt().authentication_type, + payment_data.get_payment_attempt().currency, + payment_data + .get_billing_address() + .and_then(|address| address.address) + .and_then(|address| address.country), + payment_data + .get_payment_attempt() + .payment_method_data + .as_ref() + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card")) + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card_network")) + .and_then(|network| network.as_str()) + .map(|network| network.to_string()), + payment_data + .get_payment_attempt() + .payment_method_data + .as_ref() + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card")) + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card_isin")) + .and_then(|card_isin| card_isin.as_str()) + .map(|card_isin| card_isin.to_string()), + ); + let mut connectors = if dynamic_routing_config.success_based_algorithm.is_some_and( + |success_based_algo| { + success_based_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }, + ) { routing::perform_success_based_routing( state, connectors.clone(), business_profile, - success_based_routing_config_params_interpolator, + dynamic_routing_config_params_interpolator.clone(), ) .await .map_err(|e| logger::error!(success_rate_routing_error=?e)) .unwrap_or(connectors) } else { connectors - } + }; + + connectors = if dynamic_routing_config + .elimination_routing_algorithm + .is_some_and(|elimination_routing_algo| { + elimination_routing_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }) { + routing::perform_elimination_routing( + state, + connectors.clone(), + business_profile, + dynamic_routing_config_params_interpolator, + ) + .await + .map_err(|e| logger::error!(success_rate_routing_error=?e)) + .unwrap_or(connectors) + } else { + connectors + }; + connectors + // } else { + // connectors + // } } else { connectors } diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index e579c6258433..3f818742352e 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -25,9 +25,8 @@ use euclid::{ #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ elimination_rate_client::EliminationResponse, - success_rate_client::{ - CalSuccessRateResponse, SuccessBasedDynamicRouting, -}}; + success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, +}; use hyperswitch_domain_models::address::Address; use kgraph_utils::{ mca as mca_graph, @@ -1474,37 +1473,54 @@ pub async fn perform_elimination_routing( "unable to analyze/fetch elimintaion routing from dynamic routing service", )?; - println!(">>>>>>>>>>>>>>>>> Elim connectors {:?}", elimination_based_connectors); - // let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_score.len()); - // for label_with_score in elimination_based_connectors.labels_with_score { - // let (connector, merchant_connector_id) = label_with_score.label - // .split_once(':') - // .ok_or(errors::RoutingError::InvalidSuccessBasedConnectorLabel(label_with_score.label.to_string())) - // .attach_printable( - // "unable to split connector_name and mca_id from the label obtained by the dynamic routing service", - // )?; - // connectors.push(api_routing::RoutableConnectorChoice { - // choice_kind: api_routing::RoutableChoiceKind::FullStruct, - // connector: common_enums::RoutableConnectors::from_str(connector) - // .change_context(errors::RoutingError::GenericConversionError { - // from: "String".to_string(), - // to: "RoutableConnectors".to_string(), - // }) - // .attach_printable("unable to convert String to RoutableConnectors")?, - // merchant_connector_id: Some( - // common_utils::id_type::MerchantConnectorAccountId::wrap( - // merchant_connector_id.to_string(), - // ) - // .change_context(errors::RoutingError::GenericConversionError { - // from: "String".to_string(), - // to: "MerchantConnectorAccountId".to_string(), - // }) - // .attach_printable("unable to convert MerchantConnectorAccountId from string")?, - // ), - // }); - // } - // logger::debug!(success_based_routing_connectors=?connectors); - Ok(routable_connectors) + println!( + ">>>>>>>>>>>>>>>>> Elim connectors {:?}", + elimination_based_connectors + ); + let mut connectors = + Vec::with_capacity(elimination_based_connectors.labels_with_status.len()); + let mut eliminated_connectors = + Vec::with_capacity(elimination_based_connectors.labels_with_status.len()); + let mut non_eliminated_connectors = + Vec::with_capacity(elimination_based_connectors.labels_with_status.len()); + for labels_with_status in elimination_based_connectors.labels_with_status { + let (connector, merchant_connector_id) = labels_with_status.label + .split_once(':') + .ok_or(errors::RoutingError::InvalidEliminationBasedConnectorLabel(labels_with_status.label.to_string())) + .attach_printable( + "unable to split connector_name and mca_id from the label obtained by the elimination based dynamic routing service", + )?; + + let routable_connector = api_routing::RoutableConnectorChoice { + choice_kind: api_routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str(connector) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "RoutableConnectors".to_string(), + }) + .attach_printable("unable to convert String to RoutableConnectors")?, + merchant_connector_id: Some( + common_utils::id_type::MerchantConnectorAccountId::wrap( + merchant_connector_id.to_string(), + ) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "MerchantConnectorAccountId".to_string(), + }) + .attach_printable("unable to convert MerchantConnectorAccountId from string")?, + ), + }; + + if labels_with_status.is_eliminated { + eliminated_connectors.push(routable_connector); + } else { + non_eliminated_connectors.push(routable_connector); + } + connectors.extend(non_eliminated_connectors.clone()); + connectors.extend(eliminated_connectors.clone()); + } + logger::debug!(elimination_based_routing_connectors=?connectors); + Ok(connecyors) } else { Ok(routable_connectors) } diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 0229372c9e2b..c93e3ddd7d1a 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -714,7 +714,12 @@ pub async fn fetch_success_based_routing_config( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to parse success_based_routing_config struct")?; - refresh_success_based_routing_cache(state, key.as_str(), success_based_routing_config.clone()).await; + refresh_success_based_routing_cache( + state, + key.as_str(), + success_based_routing_config.clone(), + ) + .await; Ok(success_based_routing_config) } @@ -1344,6 +1349,7 @@ pub async fn default_specific_dynamic_routing_setup( Ok(ApplicationResponse::Json(new_record)) } +#[derive(Clone, Debug)] pub struct DynamicRoutingConfigParamsInterpolator { pub payment_method: Option, pub payment_method_type: Option, From bd7ac21b38fe5f78fc45382cb406fac68c8f2a40 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Tue, 17 Dec 2024 23:42:48 +0530 Subject: [PATCH 18/27] minor refactors --- crates/router/src/core/payments/routing.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 3f818742352e..61b0b7d067d5 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -24,7 +24,7 @@ use euclid::{ }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ - elimination_rate_client::EliminationResponse, + elimination_rate_client::EliminationResponse, EliminationBasedRouting, success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, }; use hyperswitch_domain_models::address::Address; @@ -1394,8 +1394,6 @@ pub async fn perform_elimination_routing( business_profile: &domain::Profile, elimination_routing_configs_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, ) -> RoutingResult> { - use external_services::grpc_client::dynamic_routing::elimination_rate_client::EliminationBasedRouting; - let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile .dynamic_routing_algorithm .clone() @@ -1472,11 +1470,6 @@ pub async fn perform_elimination_routing( .attach_printable( "unable to analyze/fetch elimintaion routing from dynamic routing service", )?; - - println!( - ">>>>>>>>>>>>>>>>> Elim connectors {:?}", - elimination_based_connectors - ); let mut connectors = Vec::with_capacity(elimination_based_connectors.labels_with_status.len()); let mut eliminated_connectors = @@ -1519,8 +1512,9 @@ pub async fn perform_elimination_routing( connectors.extend(non_eliminated_connectors.clone()); connectors.extend(eliminated_connectors.clone()); } + logger::debug!(eliminated_connectors=?eliminated_connectors); logger::debug!(elimination_based_routing_connectors=?connectors); - Ok(connecyors) + Ok(connectors) } else { Ok(routable_connectors) } From 2a4116683deaf7bcad30142911784ddb1b6217bd Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:15:56 +0000 Subject: [PATCH 19/27] chore: run formatter --- crates/router/src/core/payments/routing.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 61b0b7d067d5..a716120c4c88 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -24,8 +24,9 @@ use euclid::{ }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ - elimination_rate_client::EliminationResponse, EliminationBasedRouting, + elimination_rate_client::EliminationResponse, success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, + EliminationBasedRouting, }; use hyperswitch_domain_models::address::Address; use kgraph_utils::{ From e38df11b7653e1fad17887cebbca2b1a8a23dfe1 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Wed, 18 Dec 2024 01:03:11 +0530 Subject: [PATCH 20/27] template for update window equivalent for elimination routing servicce --- crates/router/src/core/errors.rs | 2 + .../payments/operations/payment_response.rs | 49 ++++++-- crates/router/src/core/payments/routing.rs | 4 +- crates/router/src/core/routing/helpers.rs | 112 +++++++++++++++++- 4 files changed, 152 insertions(+), 15 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 883aec7cbd97..fcd06d3bfd2e 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -340,6 +340,8 @@ pub enum RoutingError { ElimintaionClientInitializationError, #[error("Unable to analyze elimintaion routing config from dynamic routing service")] ElimintaionRoutingCalculationError, + #[error("Params not found in elimination based routing config")] + EliminationBasedRoutingParamsNotFoundError, #[error("Unable to retrieve elimination based routing config")] EliminationRoutingConfigError, #[error( diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 37b51c1ae00a..3746cd6458f3 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1969,11 +1969,15 @@ async fn payment_response_update_tracker( #[cfg(all(feature = "v1", feature = "dynamic_routing"))] { - if business_profile.dynamic_routing_algorithm.is_some() { + if let Some(algo) = business_profile.dynamic_routing_algorithm.clone() { + let dynamic_routing_config: api_models::routing::DynamicRoutingAlgorithmRef = algo + .parse_value("DynamicRoutingAlgorithmRef") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")?; let state = state.clone(); let business_profile = business_profile.clone(); let payment_attempt = payment_attempt.clone(); - let success_based_routing_config_params_interpolator = + let dynamic_routing_config_params_interpolator = routing_helpers::DynamicRoutingConfigParamsInterpolator::new( payment_attempt.payment_method, payment_attempt.payment_method_type, @@ -2005,16 +2009,37 @@ async fn payment_response_update_tracker( ); tokio::spawn( async move { - routing_helpers::push_metrics_with_update_window_for_success_based_routing( - &state, - &payment_attempt, - routable_connectors, - &business_profile, - success_based_routing_config_params_interpolator, - ) - .await - .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) - .ok(); + if dynamic_routing_config.success_based_algorithm.is_some_and( + |success_based_algo| { + success_based_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }, + ) { + routing_helpers::push_metrics_with_update_window_for_success_based_routing( + &state, + &payment_attempt, + routable_connectors, + &business_profile, + dynamic_routing_config_params_interpolator, + ) + .await + .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) + .ok(); + }; + if dynamic_routing_config + .elimination_routing_algorithm + .is_some_and(|elimination_algo| { + elimination_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }) + { + // todo - call the update window function for elimination routing + todo!(); + }; } .in_current_span(), ); diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 61b0b7d067d5..34ed0f719076 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -24,7 +24,7 @@ use euclid::{ }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ - elimination_rate_client::EliminationResponse, EliminationBasedRouting, + elimination_rate_client::{EliminationBasedRouting, EliminationResponse}, success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, }; use hyperswitch_domain_models::address::Address; @@ -1450,7 +1450,7 @@ pub async fn perform_elimination_routing( elimination_routing_config .params .as_ref() - .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)?, + .ok_or(errors::RoutingError::EliminationBasedRoutingParamsNotFoundError)?, ); let tenant_business_profile_id = routing::helpers::generate_tenant_business_profile_id( diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index c93e3ddd7d1a..c4c7a03ede91 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -18,7 +18,10 @@ use diesel_models::dynamic_routing_stats::DynamicRoutingStatsNew; use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use external_services::grpc_client::dynamic_routing::success_rate_client::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::{ + elimination_rate_client::{EliminationBasedRouting, EliminationResponse}, + success_rate_client::SuccessBasedDynamicRouting, +}; #[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -725,6 +728,113 @@ pub async fn fetch_success_based_routing_config( } } +/// update window for elimination based dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn update_window_for_elimination_routing( + state: &SessionState, + payment_attempt: &storage::PaymentAttempt, + routable_connectors: Vec, + business_profile: &domain::Profile, + elimination_routing_configs_params_interpolator: DynamicRoutingConfigParamsInterpolator, +) -> RouterResult<()> { + let elimination_based_dynamic_routing_ref: routing_types::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize DynamicRoutingAlgorithmRef from JSON")? + .unwrap_or_default(); + + let elimination_algo_ref = elimination_based_dynamic_routing_ref + .elimination_routing_algorithm + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("elimination_based_routing_algorithm not found in dynamic_routing_algorithm from business_profile table")?; + + if elimination_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { + let client = state + .grpc_client + .dynamic_routing + .elimination_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "elimination_rate gRPC client not found".to_string(), + })?; + + let elimination_routing_config = fetch_elimintaion_routing_configs( + state, + business_profile, + elimination_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "success_based_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + + let elimination_routing_config_params = elimination_routing_configs_params_interpolator + .get_string_val( + elimination_routing_config + .params + .as_ref() + .ok_or(errors::RoutingError::EliminationBasedRoutingParamsNotFoundError) + .change_context(errors::ApiErrorResponse::InternalServerError)?, + ); + + let tenant_business_profile_id = generate_tenant_business_profile_id( + &state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr(), + ); + + let elimination_based_connectors: EliminationResponse = client + .perform_elimination_routing( + tenant_business_profile_id, + elimination_routing_config_params, + routable_connectors.clone(), + elimination_routing_config.elimination_analyser_config, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to analyze/fetch elimintaion routing from dynamic routing service", + )?; + + // todo - call update window equivalent for elimination routing + // client + // .update_elimination_bucket( + // tenant_business_profile_id, + // success_based_routing_configs, + // success_based_routing_config_params, + // vec![routing_types::RoutableConnectorChoiceWithStatus::new( + // 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(), + // }, + // payment_status_attribute == common_enums::AttemptStatus::Charged, + // )], + // ) + // .await + // .change_context(errors::ApiErrorResponse::InternalServerError) + // .attach_printable( + // "unable to update success based routing window in dynamic routing service", + // )?; + Ok(()) + } else { + Ok(()) + } +} + /// metrics for success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] From 57ede653602927c02cbbc610955ba0f1af3012f3 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:35:01 +0000 Subject: [PATCH 21/27] chore: run formatter --- crates/router/src/core/payments/routing.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index f0b259b65cc6..aeef21715971 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -25,7 +25,6 @@ use euclid::{ #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ elimination_rate_client::{EliminationBasedRouting, EliminationResponse}, - success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, EliminationBasedRouting, }; From 3c2a01aa3d2b1d2746dc9711055ebe44b5aac1f3 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 23 Dec 2024 15:51:44 +0530 Subject: [PATCH 22/27] added update window --- crates/api_models/src/routing.rs | 2 +- crates/common_enums/src/enums.rs | 14 +++ .../payments/operations/payment_response.rs | 72 ++++++++------ crates/router/src/core/payments/routing.rs | 1 - crates/router/src/core/routing/helpers.rs | 99 ++++++++++--------- 5 files changed, 111 insertions(+), 77 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 2e570816ab4c..b6146794a9de 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -743,7 +743,7 @@ pub struct EliminationRoutingConfig { pub elimination_analyser_config: Option, } -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, ToSchema)] pub struct EliminationAnalyserConfig { pub bucket_size: Option, pub bucket_leak_interval_in_secs: Option, diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 5aac1a07457b..56247bfad64c 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3470,6 +3470,20 @@ pub enum ErrorCategory { ProcessorDeclineIncorrectData, } +impl ErrorCategory { + pub fn should_perform_elimination_routing( + self, + ) -> bool { + match self { + Self::FrmDecline | + Self::ProcessorDowntime | + Self::ProcessorDeclineUnauthorized => true, + Self::IssueWithPaymentMethod | + Self::ProcessorDeclineIncorrectData => false, + } + } +} + #[derive( Clone, Debug, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 3746cd6458f3..90f8fb86fedc 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1311,7 +1311,7 @@ async fn payment_response_update_tracker( .as_mut() .map(|info| info.status = status) }); - let (capture_update, mut payment_attempt_update) = match router_data.response.clone() { + let (capture_update, mut payment_attempt_update, gsm_error_category) = match router_data.response.clone() { Err(err) => { let auth_update = if Some(router_data.auth_type) != payment_data.payment_attempt.authentication_type @@ -1320,7 +1320,7 @@ async fn payment_response_update_tracker( } else { None }; - let (capture_update, attempt_update) = match payment_data.multiple_capture_data { + let (capture_update, attempt_update, gsm_error_category) = match payment_data.multiple_capture_data { Some(multiple_capture_data) => { let capture_update = storage::CaptureUpdate::ErrorUpdate { status: match err.status_code { @@ -1343,6 +1343,7 @@ async fn payment_response_update_tracker( updated_by: storage_scheme.to_string(), } }), + None, ) } None => { @@ -1359,7 +1360,7 @@ async fn payment_response_update_tracker( let gsm_unified_code = option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); - let gsm_unified_message = option_gsm.and_then(|gsm| gsm.unified_message); + let gsm_unified_message = option_gsm.clone().and_then(|gsm| gsm.unified_message); let (unified_code, unified_message) = if let Some((code, message)) = gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref()) @@ -1431,10 +1432,11 @@ async fn payment_response_update_tracker( payment_method_data: additional_payment_method_data, authentication_type: auth_update, }), + option_gsm.and_then(|option_gsm| option_gsm.error_category) ) } }; - (capture_update, attempt_update) + (capture_update, attempt_update, gsm_error_category) } Ok(payments_response) => { @@ -1468,6 +1470,7 @@ async fn payment_response_update_tracker( payment_method_data: None, authentication_type: auth_update, }), + None, ) } Ok(()) => { @@ -1523,7 +1526,7 @@ async fn payment_response_update_tracker( updated_by: storage_scheme.to_string(), }; - (None, Some(payment_attempt_update)) + (None, Some(payment_attempt_update), None) } types::PaymentsResponseData::TransactionResponse { resource_id, @@ -1737,7 +1740,7 @@ async fn payment_response_update_tracker( ), }; - (capture_updates, payment_attempt_update) + (capture_updates, payment_attempt_update, None) } types::PaymentsResponseData::TransactionUnresolvedResponse { resource_id, @@ -1765,22 +1768,23 @@ async fn payment_response_update_tracker( connector_response_reference_id, updated_by: storage_scheme.to_string(), }), + None ) } - types::PaymentsResponseData::SessionResponse { .. } => (None, None), - types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None), - types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), + types::PaymentsResponseData::SessionResponse { .. } => (None, None, None), + types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None, None), + types::PaymentsResponseData::TokenizationResponse { .. } => (None, None, None), types::PaymentsResponseData::ConnectorCustomerResponse { .. } => { - (None, None) + (None, None, None) } types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => { - (None, None) + (None, None, None) } - types::PaymentsResponseData::PostProcessingResponse { .. } => (None, None), + types::PaymentsResponseData::PostProcessingResponse { .. } => (None, None, None), types::PaymentsResponseData::IncrementalAuthorizationResponse { .. - } => (None, None), - types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None), + } => (None, None, None), + types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None, None), types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { @@ -1789,9 +1793,9 @@ async fn payment_response_update_tracker( &multiple_capture_data, capture_sync_response_list, )?; - (Some((multiple_capture_data, capture_update_list)), None) + (Some((multiple_capture_data, capture_update_list)), None, None) } - None => (None, None), + None => (None, None, None), }, } } @@ -2020,25 +2024,35 @@ async fn payment_response_update_tracker( routing_helpers::push_metrics_with_update_window_for_success_based_routing( &state, &payment_attempt, - routable_connectors, + routable_connectors.clone(), &business_profile, - dynamic_routing_config_params_interpolator, + dynamic_routing_config_params_interpolator.clone(), ) .await .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) .ok(); }; - if dynamic_routing_config - .elimination_routing_algorithm - .is_some_and(|elimination_algo| { - elimination_algo - .algorithm_id_with_timestamp - .algorithm_id - .is_some() - }) - { - // todo - call the update window function for elimination routing - todo!(); + if let Some(gsm_error_category) = gsm_error_category { + if dynamic_routing_config + .elimination_routing_algorithm + .is_some_and(|elimination_algo| { + elimination_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }) && gsm_error_category.should_perform_elimination_routing() + { + routing_helpers::update_window_for_elimination_routing( + &state, + &payment_attempt, + &business_profile, + dynamic_routing_config_params_interpolator, + gsm_error_category, + ) + .await + .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) + .ok(); + }; }; } .in_current_span(), diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index aeef21715971..34ed0f719076 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -26,7 +26,6 @@ use euclid::{ use external_services::grpc_client::dynamic_routing::{ elimination_rate_client::{EliminationBasedRouting, EliminationResponse}, success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, - EliminationBasedRouting, }; use hyperswitch_domain_models::address::Address; use kgraph_utils::{ diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index c4c7a03ede91..17d4e6203e64 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -19,7 +19,7 @@ use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use external_services::grpc_client::dynamic_routing::{ - elimination_rate_client::{EliminationBasedRouting, EliminationResponse}, + elimination_rate_client::EliminationBasedRouting, success_rate_client::SuccessBasedDynamicRouting, }; #[cfg(feature = "v1")] @@ -734,9 +734,9 @@ pub async fn fetch_success_based_routing_config( pub async fn update_window_for_elimination_routing( state: &SessionState, payment_attempt: &storage::PaymentAttempt, - routable_connectors: Vec, business_profile: &domain::Profile, elimination_routing_configs_params_interpolator: DynamicRoutingConfigParamsInterpolator, + gsm_error_category: common_enums::ErrorCategory, ) -> RouterResult<()> { let elimination_based_dynamic_routing_ref: routing_types::DynamicRoutingAlgorithmRef = business_profile @@ -768,7 +768,7 @@ pub async fn update_window_for_elimination_routing( business_profile, elimination_algo_ref .algorithm_id_with_timestamp - .algorithm_id + .algorithm_id.clone() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( "success_based_routing_algorithm_id not found in business_profile", @@ -778,57 +778,64 @@ pub async fn update_window_for_elimination_routing( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; + + let elimination_routing_configs = fetch_elimintaion_routing_configs( + state, + business_profile, + elimination_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "elimination_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve elimination based dynamic routing configs")?; + + let tenant_business_profile_id = generate_tenant_business_profile_id( + &state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr(), + ); + let elimination_routing_config_params = elimination_routing_configs_params_interpolator .get_string_val( - elimination_routing_config + elimination_routing_configs .params .as_ref() .ok_or(errors::RoutingError::EliminationBasedRoutingParamsNotFoundError) .change_context(errors::ApiErrorResponse::InternalServerError)?, ); - let tenant_business_profile_id = generate_tenant_business_profile_id( - &state.tenant.redis_key_prefix, - business_profile.get_id().get_string_repr(), - ); - - let elimination_based_connectors: EliminationResponse = client - .perform_elimination_routing( - tenant_business_profile_id, - elimination_routing_config_params, - routable_connectors.clone(), - elimination_routing_config.elimination_analyser_config, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to analyze/fetch elimintaion routing from dynamic routing service", - )?; - - // todo - call update window equivalent for elimination routing - // client - // .update_elimination_bucket( - // tenant_business_profile_id, - // success_based_routing_configs, - // success_based_routing_config_params, - // vec![routing_types::RoutableConnectorChoiceWithStatus::new( - // 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(), - // }, - // payment_status_attribute == common_enums::AttemptStatus::Charged, - // )], - // ) - // .await - // .change_context(errors::ApiErrorResponse::InternalServerError) - // .attach_printable( - // "unable to update success based routing window in dynamic routing service", - // )?; + client + .update_elimination_bucket_config( + tenant_business_profile_id, + elimination_routing_config_params, + vec![routing_types::RoutableConnectorChoiceWithBucketName::new( + 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(), + }, + gsm_error_category.to_string(), + )], + elimination_routing_config.elimination_analyser_config, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update success based routing window in dynamic routing service", + )?; Ok(()) } else { Ok(()) From 427a354116a27f9d49dc12d0775f76e3364a0a1a Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:22:56 +0000 Subject: [PATCH 23/27] chore: run formatter --- crates/common_enums/src/enums.rs | 11 +- .../payments/operations/payment_response.rs | 252 ++++++++++-------- crates/router/src/core/routing/helpers.rs | 47 ++-- 3 files changed, 162 insertions(+), 148 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 56247bfad64c..09190afe5e5b 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3471,15 +3471,10 @@ pub enum ErrorCategory { } impl ErrorCategory { - pub fn should_perform_elimination_routing( - self, - ) -> bool { + pub fn should_perform_elimination_routing(self) -> bool { match self { - Self::FrmDecline | - Self::ProcessorDowntime | - Self::ProcessorDeclineUnauthorized => true, - Self::IssueWithPaymentMethod | - Self::ProcessorDeclineIncorrectData => false, + Self::FrmDecline | Self::ProcessorDowntime | Self::ProcessorDeclineUnauthorized => true, + Self::IssueWithPaymentMethod | Self::ProcessorDeclineIncorrectData => false, } } } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 90f8fb86fedc..a62db3cae6ff 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1311,7 +1311,10 @@ async fn payment_response_update_tracker( .as_mut() .map(|info| info.status = status) }); - let (capture_update, mut payment_attempt_update, gsm_error_category) = match router_data.response.clone() { + let (capture_update, mut payment_attempt_update, gsm_error_category) = match router_data + .response + .clone() + { Err(err) => { let auth_update = if Some(router_data.auth_type) != payment_data.payment_attempt.authentication_type @@ -1320,122 +1323,124 @@ async fn payment_response_update_tracker( } else { None }; - let (capture_update, attempt_update, gsm_error_category) = match payment_data.multiple_capture_data { - Some(multiple_capture_data) => { - let capture_update = storage::CaptureUpdate::ErrorUpdate { - status: match err.status_code { - 500..=511 => enums::CaptureStatus::Pending, - _ => enums::CaptureStatus::Failed, - }, - error_code: Some(err.code), - error_message: Some(err.message), - error_reason: err.reason, - }; - let capture_update_list = vec![( - multiple_capture_data.get_latest_capture().clone(), - capture_update, - )]; - ( - Some((multiple_capture_data, capture_update_list)), - auth_update.map(|auth_type| { - storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { - authentication_type: auth_type, - updated_by: storage_scheme.to_string(), - } - }), - None, - ) - } - None => { - let connector_name = router_data.connector.to_string(); - let flow_name = core_utils::get_flow_name::()?; - let option_gsm = payments_helpers::get_gsm_record( - state, - Some(err.code.clone()), - Some(err.message.clone()), - connector_name, - flow_name.clone(), - ) - .await; - - let gsm_unified_code = - option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); - let gsm_unified_message = option_gsm.clone().and_then(|gsm| gsm.unified_message); - - let (unified_code, unified_message) = if let Some((code, message)) = - gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref()) - { - (code.to_owned(), message.to_owned()) - } else { + let (capture_update, attempt_update, gsm_error_category) = + match payment_data.multiple_capture_data { + Some(multiple_capture_data) => { + let capture_update = storage::CaptureUpdate::ErrorUpdate { + status: match err.status_code { + 500..=511 => enums::CaptureStatus::Pending, + _ => enums::CaptureStatus::Failed, + }, + error_code: Some(err.code), + error_message: Some(err.message), + error_reason: err.reason, + }; + let capture_update_list = vec![( + multiple_capture_data.get_latest_capture().clone(), + capture_update, + )]; ( - consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(), - consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), + Some((multiple_capture_data, capture_update_list)), + auth_update.map(|auth_type| { + storage::PaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type: auth_type, + updated_by: storage_scheme.to_string(), + } + }), + None, ) - }; - let unified_translated_message = locale - .as_ref() - .async_and_then(|locale_str| async { - payments_helpers::get_unified_translation( - state, - unified_code.to_owned(), - unified_message.to_owned(), - locale_str.to_owned(), + } + None => { + let connector_name = router_data.connector.to_string(); + let flow_name = core_utils::get_flow_name::()?; + let option_gsm = payments_helpers::get_gsm_record( + state, + Some(err.code.clone()), + Some(err.message.clone()), + connector_name, + flow_name.clone(), + ) + .await; + + let gsm_unified_code = + option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); + let gsm_unified_message = + option_gsm.clone().and_then(|gsm| gsm.unified_message); + + let (unified_code, unified_message) = if let Some((code, message)) = + gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref()) + { + (code.to_owned(), message.to_owned()) + } else { + ( + consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(), + consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), ) + }; + let unified_translated_message = locale + .as_ref() + .async_and_then(|locale_str| async { + payments_helpers::get_unified_translation( + state, + unified_code.to_owned(), + unified_message.to_owned(), + locale_str.to_owned(), + ) + .await + }) .await - }) - .await - .or(Some(unified_message)); + .or(Some(unified_message)); - let status = match err.attempt_status { - // Use the status sent by connector in error_response if it's present - Some(status) => status, - None => - // mark previous attempt status for technical failures in PSync flow - { - if flow_name == "PSync" { - match err.status_code { - // marking failure for 2xx because this is genuine payment failure - 200..=299 => enums::AttemptStatus::Failure, - _ => router_data.status, - } - } else if flow_name == "Capture" { - match err.status_code { - 500..=511 => enums::AttemptStatus::Pending, - // don't update the status for 429 error status - 429 => router_data.status, - _ => enums::AttemptStatus::Failure, - } - } else { - match err.status_code { - 500..=511 => enums::AttemptStatus::Pending, - _ => enums::AttemptStatus::Failure, + let status = match err.attempt_status { + // Use the status sent by connector in error_response if it's present + Some(status) => status, + None => + // mark previous attempt status for technical failures in PSync flow + { + if flow_name == "PSync" { + match err.status_code { + // marking failure for 2xx because this is genuine payment failure + 200..=299 => enums::AttemptStatus::Failure, + _ => router_data.status, + } + } else if flow_name == "Capture" { + match err.status_code { + 500..=511 => enums::AttemptStatus::Pending, + // don't update the status for 429 error status + 429 => router_data.status, + _ => enums::AttemptStatus::Failure, + } + } else { + match err.status_code { + 500..=511 => enums::AttemptStatus::Pending, + _ => enums::AttemptStatus::Failure, + } } } - } - }; - ( - None, - Some(storage::PaymentAttemptUpdate::ErrorUpdate { - connector: None, - status, - error_message: Some(Some(err.message)), - error_code: Some(Some(err.code)), - error_reason: Some(err.reason), - amount_capturable: router_data - .request - .get_amount_capturable(&payment_data, status) - .map(MinorUnit::new), - updated_by: storage_scheme.to_string(), - unified_code: Some(Some(unified_code)), - unified_message: Some(unified_translated_message), - connector_transaction_id: err.connector_transaction_id, - payment_method_data: additional_payment_method_data, - authentication_type: auth_update, - }), - option_gsm.and_then(|option_gsm| option_gsm.error_category) - ) - } - }; + }; + ( + None, + Some(storage::PaymentAttemptUpdate::ErrorUpdate { + connector: None, + status, + error_message: Some(Some(err.message)), + error_code: Some(Some(err.code)), + error_reason: Some(err.reason), + amount_capturable: router_data + .request + .get_amount_capturable(&payment_data, status) + .map(MinorUnit::new), + updated_by: storage_scheme.to_string(), + unified_code: Some(Some(unified_code)), + unified_message: Some(unified_translated_message), + connector_transaction_id: err.connector_transaction_id, + payment_method_data: additional_payment_method_data, + authentication_type: auth_update, + }), + option_gsm.and_then(|option_gsm| option_gsm.error_category), + ) + } + }; (capture_update, attempt_update, gsm_error_category) } @@ -1768,23 +1773,31 @@ async fn payment_response_update_tracker( connector_response_reference_id, updated_by: storage_scheme.to_string(), }), - None + None, ) } types::PaymentsResponseData::SessionResponse { .. } => (None, None, None), - types::PaymentsResponseData::SessionTokenResponse { .. } => (None, None, None), - types::PaymentsResponseData::TokenizationResponse { .. } => (None, None, None), + types::PaymentsResponseData::SessionTokenResponse { .. } => { + (None, None, None) + } + types::PaymentsResponseData::TokenizationResponse { .. } => { + (None, None, None) + } types::PaymentsResponseData::ConnectorCustomerResponse { .. } => { (None, None, None) } types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => { (None, None, None) } - types::PaymentsResponseData::PostProcessingResponse { .. } => (None, None, None), + types::PaymentsResponseData::PostProcessingResponse { .. } => { + (None, None, None) + } types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None, None), - types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None, None), + types::PaymentsResponseData::SessionUpdateResponse { .. } => { + (None, None, None) + } types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { @@ -1793,7 +1806,11 @@ async fn payment_response_update_tracker( &multiple_capture_data, capture_sync_response_list, )?; - (Some((multiple_capture_data, capture_update_list)), None, None) + ( + Some((multiple_capture_data, capture_update_list)), + None, + None, + ) } None => (None, None, None), }, @@ -2040,7 +2057,8 @@ async fn payment_response_update_tracker( .algorithm_id_with_timestamp .algorithm_id .is_some() - }) && gsm_error_category.should_perform_elimination_routing() + }) + && gsm_error_category.should_perform_elimination_routing() { routing_helpers::update_window_for_elimination_routing( &state, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 17d4e6203e64..7c3532d4ad30 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -768,7 +768,8 @@ pub async fn update_window_for_elimination_routing( business_profile, elimination_algo_ref .algorithm_id_with_timestamp - .algorithm_id.clone() + .algorithm_id + .clone() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( "success_based_routing_algorithm_id not found in business_profile", @@ -814,28 +815,28 @@ pub async fn update_window_for_elimination_routing( ); client - .update_elimination_bucket_config( - tenant_business_profile_id, - elimination_routing_config_params, - vec![routing_types::RoutableConnectorChoiceWithBucketName::new( - 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(), - }, - gsm_error_category.to_string(), - )], - elimination_routing_config.elimination_analyser_config, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to update success based routing window in dynamic routing service", - )?; + .update_elimination_bucket_config( + tenant_business_profile_id, + elimination_routing_config_params, + vec![routing_types::RoutableConnectorChoiceWithBucketName::new( + 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(), + }, + gsm_error_category.to_string(), + )], + elimination_routing_config.elimination_analyser_config, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update success based routing window in dynamic routing service", + )?; Ok(()) } else { Ok(()) From 72659dd9bc3ec4500e1217e010ae30183d3753ff Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 23 Dec 2024 15:59:09 +0530 Subject: [PATCH 24/27] minor refactors --- crates/router/src/core/routing/helpers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 17d4e6203e64..ca1852ce84b8 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -619,7 +619,7 @@ pub async fn get_cached_elimination_routing_config_for_profile<'a>( .await } -/// Refreshes the cached success_based routing configs specific to tenant and profile +/// Refreshes the cached elimination routing configs specific to tenant and profile #[cfg(feature = "v1")] pub async fn refresh_elimination_routing_cache( state: &SessionState, @@ -639,7 +639,7 @@ pub async fn refresh_elimination_routing_cache( config } -/// Checked fetch of success based routing configs +/// Checked fetch of elimination based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn fetch_elimintaion_routing_configs( @@ -771,12 +771,12 @@ pub async fn update_window_for_elimination_routing( .algorithm_id.clone() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( - "success_based_routing_algorithm_id not found in business_profile", + "elimination_routing_algorithm_id not found in business_profile", )?, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + .attach_printable("unable to retrieve elimination based dynamic routing configs")?; let payment_connector = &payment_attempt.connector.clone().ok_or( errors::ApiErrorResponse::GenericNotFoundError { From 7e4f49b792d693abc40e1d058ac0210402f1618d Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 23 Dec 2024 16:20:43 +0530 Subject: [PATCH 25/27] minor refactors --- crates/common_enums/src/enums.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 09190afe5e5b..e90be6e2074b 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3473,8 +3473,8 @@ pub enum ErrorCategory { impl ErrorCategory { pub fn should_perform_elimination_routing(self) -> bool { match self { - Self::FrmDecline | Self::ProcessorDowntime | Self::ProcessorDeclineUnauthorized => true, - Self::IssueWithPaymentMethod | Self::ProcessorDeclineIncorrectData => false, + Self::ProcessorDowntime | Self::ProcessorDeclineUnauthorized => true, + Self::IssueWithPaymentMethod | Self::ProcessorDeclineIncorrectData | Self::FrmDecline => false, } } } From edb8e04d98598689e6b2b6fdfad05a8fc5d10ecd Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:54:34 +0000 Subject: [PATCH 26/27] chore: run formatter --- crates/common_enums/src/enums.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 5751cd43422a..538a3184f3e1 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3541,7 +3541,9 @@ impl ErrorCategory { pub fn should_perform_elimination_routing(self) -> bool { match self { Self::ProcessorDowntime | Self::ProcessorDeclineUnauthorized => true, - Self::IssueWithPaymentMethod | Self::ProcessorDeclineIncorrectData | Self::FrmDecline => false, + Self::IssueWithPaymentMethod + | Self::ProcessorDeclineIncorrectData + | Self::FrmDecline => false, } } } From 432ab4c938f931d1a35e5f88dffb976b52347f45 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Tue, 24 Dec 2024 17:14:29 +0530 Subject: [PATCH 27/27] minor refactors --- crates/router/src/core/payments.rs | 188 +++++++++--------- .../payments/operations/payment_response.rs | 1 + crates/router/src/core/payments/routing.rs | 1 + crates/router/src/core/routing/helpers.rs | 1 + 4 files changed, 98 insertions(+), 93 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 76683f5959b2..5bea394bc1fa 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6178,103 +6178,105 @@ where .parse_value("DynamicRoutingAlgorithmRef") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")?; - // let dynamic_split = api_models::routing::RoutingVolumeSplit { - // routing_type: api_models::routing::RoutingType::Dynamic, - // split: dynamic_routing_config - // .dynamic_routing_volume_split - // .unwrap_or_default(), - // }; - // let static_split: api_models::routing::RoutingVolumeSplit = - // api_models::routing::RoutingVolumeSplit { - // routing_type: api_models::routing::RoutingType::Static, - // split: crate::consts::DYNAMIC_ROUTING_MAX_VOLUME - // - dynamic_routing_config - // .dynamic_routing_volume_split - // .unwrap_or_default(), - // }; - // let volume_split_vec = vec![dynamic_split, static_split]; - // let routing_choice = - // routing::perform_dynamic_routing_volume_split(volume_split_vec, None) - // .change_context(errors::ApiErrorResponse::InternalServerError) - // .attach_printable("failed to perform volume split on routing type")?; - // - // if routing_choice.routing_type.is_dynamic_routing() { - let dynamic_routing_config_params_interpolator = - routing_helpers::DynamicRoutingConfigParamsInterpolator::new( - payment_data.get_payment_attempt().payment_method, - payment_data.get_payment_attempt().payment_method_type, - payment_data.get_payment_attempt().authentication_type, - payment_data.get_payment_attempt().currency, - payment_data - .get_billing_address() - .and_then(|address| address.address) - .and_then(|address| address.country), - payment_data - .get_payment_attempt() - .payment_method_data - .as_ref() - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card")) - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card_network")) - .and_then(|network| network.as_str()) - .map(|network| network.to_string()), - payment_data - .get_payment_attempt() - .payment_method_data - .as_ref() - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card")) - .and_then(|data| data.as_object()) - .and_then(|card| card.get("card_isin")) - .and_then(|card_isin| card_isin.as_str()) - .map(|card_isin| card_isin.to_string()), - ); - let mut connectors = if dynamic_routing_config.success_based_algorithm.is_some_and( - |success_based_algo| { - success_based_algo - .algorithm_id_with_timestamp - .algorithm_id - .is_some() - }, - ) { - routing::perform_success_based_routing( - state, - connectors.clone(), - business_profile, - dynamic_routing_config_params_interpolator.clone(), - ) - .await - .map_err(|e| logger::error!(success_rate_routing_error=?e)) - .unwrap_or(connectors) - } else { - connectors + let dynamic_split = api_models::routing::RoutingVolumeSplit { + routing_type: api_models::routing::RoutingType::Dynamic, + split: dynamic_routing_config + .dynamic_routing_volume_split + .unwrap_or_default(), }; + let static_split: api_models::routing::RoutingVolumeSplit = + api_models::routing::RoutingVolumeSplit { + routing_type: api_models::routing::RoutingType::Static, + split: crate::consts::DYNAMIC_ROUTING_MAX_VOLUME + - dynamic_routing_config + .dynamic_routing_volume_split + .unwrap_or_default(), + }; + let volume_split_vec = vec![dynamic_split, static_split]; + let routing_choice = + routing::perform_dynamic_routing_volume_split(volume_split_vec, None) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to perform volume split on routing type")?; + + if routing_choice.routing_type.is_dynamic_routing() { + let dynamic_routing_config_params_interpolator = + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( + payment_data.get_payment_attempt().payment_method, + payment_data.get_payment_attempt().payment_method_type, + payment_data.get_payment_attempt().authentication_type, + payment_data.get_payment_attempt().currency, + payment_data + .get_billing_address() + .and_then(|address| address.address) + .and_then(|address| address.country), + payment_data + .get_payment_attempt() + .payment_method_data + .as_ref() + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card")) + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card_network")) + .and_then(|network| network.as_str()) + .map(|network| network.to_string()), + payment_data + .get_payment_attempt() + .payment_method_data + .as_ref() + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card")) + .and_then(|data| data.as_object()) + .and_then(|card| card.get("card_isin")) + .and_then(|card_isin| card_isin.as_str()) + .map(|card_isin| card_isin.to_string()), + ); + let mut connectors = if dynamic_routing_config.success_based_algorithm.is_some_and( + |success_based_algo| { + success_based_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }, + ) { + logger::info!("Performing success based routing from dynamic_routing service"); + routing::perform_success_based_routing( + state, + connectors.clone(), + business_profile, + dynamic_routing_config_params_interpolator.clone(), + ) + .await + .map_err(|e| logger::error!(success_rate_routing_error=?e)) + .unwrap_or(connectors) + } else { + connectors + }; - connectors = if dynamic_routing_config - .elimination_routing_algorithm - .is_some_and(|elimination_routing_algo| { - elimination_routing_algo - .algorithm_id_with_timestamp - .algorithm_id - .is_some() - }) { - routing::perform_elimination_routing( - state, - connectors.clone(), - business_profile, - dynamic_routing_config_params_interpolator, - ) - .await - .map_err(|e| logger::error!(success_rate_routing_error=?e)) - .unwrap_or(connectors) + connectors = if dynamic_routing_config + .elimination_routing_algorithm + .is_some_and(|elimination_routing_algo| { + elimination_routing_algo + .algorithm_id_with_timestamp + .algorithm_id + .is_some() + }) { + logger::info!("Performing elimination routing from dynamic_routing service"); + routing::perform_elimination_routing( + state, + connectors.clone(), + business_profile, + dynamic_routing_config_params_interpolator, + ) + .await + .map_err(|e| logger::error!(success_rate_routing_error=?e)) + .unwrap_or(connectors) + } else { + connectors + }; + connectors } else { connectors - }; - connectors - // } else { - // connectors - // } + } } else { connectors } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 4d26e28ea38d..1b12e70f17e2 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -2060,6 +2060,7 @@ async fn payment_response_update_tracker( }) && gsm_error_category.should_perform_elimination_routing() { + logger::info!("Performing update window for elimination routing"); routing_helpers::update_window_for_elimination_routing( &state, &payment_attempt, diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 1c49662f73f4..ec889531d8f2 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1465,6 +1465,7 @@ pub async fn perform_elimination_routing( elimination_routing_config_params, routable_connectors.clone(), elimination_routing_config.elimination_analyser_config, + state.get_grpc_headers(), ) .await .change_context(errors::RoutingError::ElimintaionRoutingCalculationError) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 0f284cf56134..5bc0811c1e05 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -831,6 +831,7 @@ pub async fn update_window_for_elimination_routing( gsm_error_category.to_string(), )], elimination_routing_config.elimination_analyser_config, + state.get_grpc_headers(), ) .await .change_context(errors::ApiErrorResponse::InternalServerError)