From cf10a6370f2a86054222824783fa1159beda29a8 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Thu, 10 Oct 2024 11:34:18 +0530 Subject: [PATCH 01/19] refactor(router): dynamic routing in core flows init --- crates/api_models/src/routing.rs | 37 ++++- crates/router/src/core/routing.rs | 244 +++++++++++++++------------- crates/router/src/routes/routing.rs | 4 +- 3 files changed, 163 insertions(+), 122 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index fc65ab037c2..39624dd40e7 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -523,21 +523,42 @@ pub struct DynamicAlgorithmWithTimestamp { #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { pub success_based_algorithm: - Option>, + Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SuccessBasedAlgorithm { + pub algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp , + pub enabled_feature: SuccessBasedRoutingFeatures, } impl DynamicRoutingAlgorithmRef { - pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId) { - self.success_based_algorithm = Some(DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }) + pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId, enabled_feature: SuccessBasedRoutingFeatures) { + self.success_based_algorithm = Some( + SuccessBasedAlgorithm { + algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature + } + ) } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct ToggleSuccessBasedRoutingQuery { - pub status: bool, + pub enable: SuccessBasedRoutingFeatures, +} + +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum SuccessBasedRoutingFeatures { + #[default] + Metrics, + DynamicConnectorSelection, + None, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] @@ -551,7 +572,7 @@ pub struct SuccessBasedRoutingUpdateConfigQuery { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ToggleSuccessBasedRoutingWrapper { pub profile_id: common_utils::id_type::ProfileId, - pub status: bool, + pub enable: SuccessBasedRoutingFeatures, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index b7b1c13ea80..5256e61d700 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -24,6 +24,7 @@ use crate::{ core::{admin, errors::RouterResult}, db::StorageInterface, }; +use api_models::routing as api_routing; use crate::{ core::{ errors::{self, RouterResponse, StorageErrorExt}, @@ -433,11 +434,16 @@ pub async fn link_routing_config( utils::when( matches!( - dynamic_routing_ref.success_based_algorithm, - Some(routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: Some(ref id), - timestamp: _ - }) if id == &algorithm_id + Some(dynamic_routing_ref.success_based_algorithm, + api_routing::SuccessBasedAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: Some(ref id), + timestamp: _ + }, + enabled_feature: _ + }) + if id == &algorithm_id ), || { Err(errors::ApiErrorResponse::PreconditionFailed { @@ -446,7 +452,12 @@ pub async fn link_routing_config( }, )?; - dynamic_routing_ref.update_algorithm_id(algorithm_id); + dynamic_routing_ref.update_algorithm_id(algorithm_id, dynamic_routing_ref + .success_based_algorithm + .clone() + .unwrap() + .enabled_feature + ); helpers::update_business_profile_active_dynamic_algorithm_ref( db, key_manager_state, @@ -1162,9 +1173,10 @@ pub async fn toggle_success_based_routing( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - status: bool, + success_based_routing_state: api_routing::SuccessBasedRoutingFeatures, profile_id: common_utils::id_type::ProfileId, ) -> RouterResponse { + println!(">>>>>>>>>>>>>>>>>>>>>{success_based_routing_state:?}"); metrics::ROUTING_CREATE_REQUEST_RECEIVED.add( &metrics::CONTEXT, 1, @@ -1198,116 +1210,124 @@ pub async fn toggle_success_based_routing( )? .unwrap_or_default(); - if status { - let default_success_based_routing_config = routing::SuccessBasedRoutingConfig::default(); - let algorithm_id = common_utils::generate_routing_id_of_default_length(); - let timestamp = common_utils::date_time::now(); - let algo = RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: business_profile.get_id().to_owned(), - merchant_id: merchant_account.get_id().to_owned(), - name: "Dynamic routing algorithm".to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, - }; - - let record = db - .insert_routing_algorithm(algo) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to insert record in routing algorithm table")?; - - success_based_dynamic_routing_algo_ref.update_algorithm_id(algorithm_id); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - success_based_dynamic_routing_algo_ref, - ) - .await?; - - let new_record = record.foreign_into(); - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), - ); - Ok(service_api::ApplicationResponse::Json(new_record)) - } else { - let timestamp = common_utils::date_time::now_unix_timestamp(); - match success_based_dynamic_routing_algo_ref.success_based_algorithm { - Some(algorithm_ref) => { - if let Some(algorithm_id) = algorithm_ref.algorithm_id { - let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { - success_based_algorithm: Some( - routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: None, - timestamp, - }, - ), - }; - - // redact cache for success based routing configs - let cache_key = format!( - "{}_{}", - business_profile.get_id().get_string_repr(), - algorithm_id.get_string_repr() - ); - let cache_entries_to_redact = - vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( - cache_key.into(), - )]; - let _ = cache::publish_into_redact_channel( - state.store.get_cache_store().as_ref(), - cache_entries_to_redact, - ) + match success_based_routing_state { + api_routing::SuccessBasedRoutingFeatures::Metrics => { + let default_success_based_routing_config = routing::SuccessBasedRoutingConfig::default(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: business_profile.get_id().to_owned(), + merchant_id: merchant_account.get_id().to_owned(), + name: "Dynamic routing algorithm".to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + let record = db + .insert_routing_algorithm(algo) .await - .map_err(|e| { - logger::error!( - "unable to publish into the redact channel for evicting the success based routing config cache {e:?}" - ) - }); - - let record = db - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &algorithm_id, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; - let response = record.foreign_into(); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - dynamic_routing_algorithm, - ) - .await?; + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to insert record in routing algorithm table")?; + + success_based_dynamic_routing_algo_ref.update_algorithm_id(algorithm_id, api_routing::SuccessBasedRoutingFeatures::Metrics); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; - metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), - ); + let new_record = record.foreign_into(); - Ok(service_api::ApplicationResponse::Json(response)) - } else { - Err(errors::ApiErrorResponse::PreconditionFailed { + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), + ); + Ok(service_api::ApplicationResponse::Json(new_record)) + }, + api_routing::SuccessBasedRoutingFeatures::None => { + let timestamp = common_utils::date_time::now_unix_timestamp(); + match success_based_dynamic_routing_algo_ref.success_based_algorithm { + Some(algorithm_ref) => { + if let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id { + let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: Some( + api_routing::SuccessBasedAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: None, + timestamp, + }, + enabled_feature: api_routing::SuccessBasedRoutingFeatures::None, + } + ), + }; + + // redact cache for success based routing configs + let cache_key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = + vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( + cache_key.into(), + )]; + let _ = cache::publish_into_redact_channel( + state.store.get_cache_store().as_ref(), + cache_entries_to_redact, + ) + .await + .map_err(|e| { + logger::error!( + "unable to publish into the redact channel for evicting the success based routing config cache {e:?}" + ) + }); + + let record = db + .find_routing_algorithm_by_profile_id_algorithm_id( + business_profile.get_id(), + &algorithm_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; + let response = record.foreign_into(); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + dynamic_routing_algorithm, + ) + .await?; + + metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), + ); + + Ok(service_api::ApplicationResponse::Json(response)) + } else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? + } + } + None => Err(errors::ApiErrorResponse::PreconditionFailed { message: "Algorithm is already inactive".to_string(), - })? - } + })?, } - None => Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Algorithm is already inactive".to_string(), - })?, - } + }, + _ => todo!() } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 4c8d89fa87d..a504728314a 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -989,7 +989,7 @@ pub async fn toggle_success_based_routing( ) -> impl Responder { let flow = Flow::ToggleDynamicRouting; let wrapper = routing_types::ToggleSuccessBasedRoutingWrapper { - status: query.into_inner().status, + enable: query.into_inner().enable, profile_id: path.into_inner().profile_id, }; Box::pin(oss_api::server_wrap( @@ -1005,7 +1005,7 @@ pub async fn toggle_success_based_routing( state, auth.merchant_account, auth.key_store, - wrapper.status, + wrapper.enable, wrapper.profile_id, ) }, From d1e9a6d6fba13285a6e1fa6284f9da5f8ceb61a7 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Thu, 10 Oct 2024 11:55:25 +0530 Subject: [PATCH 02/19] minor refactors --- crates/router/src/core/routing.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 5256e61d700..3e87e718a24 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -434,16 +434,15 @@ pub async fn link_routing_config( utils::when( matches!( - Some(dynamic_routing_ref.success_based_algorithm, - api_routing::SuccessBasedAlgorithm { + dynamic_routing_ref.success_based_algorithm, + Some(api_routing::SuccessBasedAlgorithm { algorithm_id_with_timestamp: routing_types::DynamicAlgorithmWithTimestamp { algorithm_id: Some(ref id), timestamp: _ }, enabled_feature: _ - }) - if id == &algorithm_id + }) if id == &algorithm_id ), || { Err(errors::ApiErrorResponse::PreconditionFailed { From 760545573c03b1a0fa24274c3c2974672fe1999b Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Fri, 11 Oct 2024 09:15:01 +0530 Subject: [PATCH 03/19] remove redundant algorithm creations --- crates/api_models/src/routing.rs | 37 ++-- crates/router/src/core/payments.rs | 17 ++ crates/router/src/core/routing.rs | 274 ++++++++++++++++++++--------- 3 files changed, 229 insertions(+), 99 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 39624dd40e7..89871421ad8 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -522,28 +522,35 @@ pub struct DynamicAlgorithmWithTimestamp { #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { - pub success_based_algorithm: - Option, + pub success_based_algorithm: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SuccessBasedAlgorithm { - pub algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp , + pub algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp, pub enabled_feature: SuccessBasedRoutingFeatures, } +impl SuccessBasedAlgorithm { + pub fn update_enabled_features(&mut self, feature_to_enable: SuccessBasedRoutingFeatures) { + self.enabled_feature = feature_to_enable + } +} + impl DynamicRoutingAlgorithmRef { - pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId, enabled_feature: SuccessBasedRoutingFeatures) { - self.success_based_algorithm = Some( - SuccessBasedAlgorithm { - algorithm_id_with_timestamp: - DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }, - enabled_feature - } - ) + pub fn update_algorithm_id( + &mut self, + new_id: common_utils::id_type::RoutingId, + enabled_feature: SuccessBasedRoutingFeatures, + ) { + self.success_based_algorithm = Some(SuccessBasedAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature, + }) } } @@ -552,7 +559,7 @@ pub struct ToggleSuccessBasedRoutingQuery { pub enable: SuccessBasedRoutingFeatures, } -#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] #[serde(rename_all = "snake_case")] pub enum SuccessBasedRoutingFeatures { #[default] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 9f904116171..0ae898660f7 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4579,6 +4579,23 @@ where .await .change_context(errors::ApiErrorResponse::InternalServerError)?; + // success_based_routing_for_connectors + business_profile.dynamic_routing_algorithm.map(|dynamic_routing_algorithm| + { + let mut success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + )? + .unwrap_or_default(); + } + ); + let connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 3e87e718a24..3f53c657207 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -2,7 +2,7 @@ pub mod helpers; pub mod transformers; use api_models::{ - enums, mandates as mandates_api, routing, + enums, mandates as mandates_api, routing, routing as api_routing, routing::{self as routing_types, RoutingRetrieveQuery}, }; use diesel_models::routing_algorithm::RoutingAlgorithm; @@ -24,7 +24,6 @@ use crate::{ core::{admin, errors::RouterResult}, db::StorageInterface, }; -use api_models::routing as api_routing; use crate::{ core::{ errors::{self, RouterResponse, StorageErrorExt}, @@ -451,11 +450,13 @@ pub async fn link_routing_config( }, )?; - dynamic_routing_ref.update_algorithm_id(algorithm_id, dynamic_routing_ref - .success_based_algorithm - .clone() - .unwrap() - .enabled_feature + dynamic_routing_ref.update_algorithm_id( + algorithm_id, + dynamic_routing_ref + .success_based_algorithm + .clone() + .unwrap() + .enabled_feature, ); helpers::update_business_profile_active_dynamic_algorithm_ref( db, @@ -1172,10 +1173,9 @@ pub async fn toggle_success_based_routing( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - success_based_routing_state: api_routing::SuccessBasedRoutingFeatures, + feature_to_enable: api_routing::SuccessBasedRoutingFeatures, profile_id: common_utils::id_type::ProfileId, ) -> RouterResponse { - println!(">>>>>>>>>>>>>>>>>>>>>{success_based_routing_state:?}"); metrics::ROUTING_CREATE_REQUEST_RECEIVED.add( &metrics::CONTEXT, 1, @@ -1209,9 +1209,109 @@ pub async fn toggle_success_based_routing( )? .unwrap_or_default(); - match success_based_routing_state { - api_routing::SuccessBasedRoutingFeatures::Metrics => { - let default_success_based_routing_config = routing::SuccessBasedRoutingConfig::default(); + match feature_to_enable { + api_routing::SuccessBasedRoutingFeatures::Metrics + | api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { + if let Some(ref mut algo_with_timestamp) = + success_based_dynamic_routing_algo_ref.success_based_algorithm + { + match algo_with_timestamp + .algorithm_id_with_timestamp + .algorithm_id + .clone() + { + Some(algorithm_id) => { + if algo_with_timestamp.enabled_feature == feature_to_enable { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Success based routing is already enabled".to_string(), + })? + } else { + algo_with_timestamp.update_enabled_features(feature_to_enable); + let record = db + .find_routing_algorithm_by_profile_id_algorithm_id( + business_profile.get_id(), + &algorithm_id, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::ResourceIdNotFound, + )?; + let response = record.foreign_into(); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([( + "profile_id", + profile_id.get_string_repr().to_owned(), + )]), + ); + Ok(service_api::ApplicationResponse::Json(response)) + } + } + None => { + let default_success_based_routing_config = + routing::SuccessBasedRoutingConfig::default(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: business_profile.get_id().to_owned(), + merchant_id: merchant_account.get_id().to_owned(), + name: "Dynamic routing algorithm".to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Unable to insert record in routing algorithm table", + )?; + + success_based_dynamic_routing_algo_ref.update_algorithm_id( + algorithm_id, + api_routing::SuccessBasedRoutingFeatures::Metrics, + ); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; + + let new_record = record.foreign_into(); + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([( + "profile_id", + profile_id.get_string_repr().to_owned(), + )]), + ); + Ok(service_api::ApplicationResponse::Json(new_record)) + } + } + } else { + let default_success_based_routing_config = + routing::SuccessBasedRoutingConfig::default(); let algorithm_id = common_utils::generate_routing_id_of_default_length(); let timestamp = common_utils::date_time::now(); let algo = RoutingAlgorithm { @@ -1233,7 +1333,10 @@ pub async fn toggle_success_based_routing( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to insert record in routing algorithm table")?; - success_based_dynamic_routing_algo_ref.update_algorithm_id(algorithm_id, api_routing::SuccessBasedRoutingFeatures::Metrics); + success_based_dynamic_routing_algo_ref.update_algorithm_id( + algorithm_id, + api_routing::SuccessBasedRoutingFeatures::Metrics, + ); helpers::update_business_profile_active_dynamic_algorithm_ref( db, key_manager_state, @@ -1251,82 +1354,85 @@ pub async fn toggle_success_based_routing( &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), ); Ok(service_api::ApplicationResponse::Json(new_record)) - }, - api_routing::SuccessBasedRoutingFeatures::None => { - let timestamp = common_utils::date_time::now_unix_timestamp(); - match success_based_dynamic_routing_algo_ref.success_based_algorithm { - Some(algorithm_ref) => { - if let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id { - let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { - success_based_algorithm: Some( - api_routing::SuccessBasedAlgorithm { - algorithm_id_with_timestamp: - routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: None, - timestamp, - }, - enabled_feature: api_routing::SuccessBasedRoutingFeatures::None, - } - ), - }; - - // redact cache for success based routing configs - let cache_key = format!( - "{}_{}", - business_profile.get_id().get_string_repr(), - algorithm_id.get_string_repr() - ); - let cache_entries_to_redact = - vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( - cache_key.into(), - )]; - let _ = cache::publish_into_redact_channel( - state.store.get_cache_store().as_ref(), - cache_entries_to_redact, - ) - .await - .map_err(|e| { - logger::error!( - "unable to publish into the redact channel for evicting the success based routing config cache {e:?}" - ) - }); + } + } + api_routing::SuccessBasedRoutingFeatures::None => { + let timestamp = common_utils::date_time::now_unix_timestamp(); + match success_based_dynamic_routing_algo_ref.success_based_algorithm { + Some(algorithm_ref) => { + if let Some(algorithm_id) = + algorithm_ref.algorithm_id_with_timestamp.algorithm_id + { + let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: Some(api_routing::SuccessBasedAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: None, + timestamp, + }, + enabled_feature: api_routing::SuccessBasedRoutingFeatures::None, + }), + }; - let record = db - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &algorithm_id, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; - let response = record.foreign_into(); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - dynamic_routing_algorithm, + // redact cache for success based routing configs + let cache_key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = + vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( + cache_key.into(), + )]; + let _ = cache::publish_into_redact_channel( + state.store.get_cache_store().as_ref(), + cache_entries_to_redact, + ) + .await + .map_err(|e| { + logger::error!( + "unable to publish into the redact channel for evicting the success based routing config cache {e:?}" ) - .await?; - - metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), - ); + }); - Ok(service_api::ApplicationResponse::Json(response)) - } else { - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Algorithm is already inactive".to_string(), - })? - } + let record = db + .find_routing_algorithm_by_profile_id_algorithm_id( + business_profile.get_id(), + &algorithm_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; + let response = record.foreign_into(); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + dynamic_routing_algorithm, + ) + .await?; + + metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([( + "profile_id", + profile_id.get_string_repr().to_owned(), + )]), + ); + + Ok(service_api::ApplicationResponse::Json(response)) + } else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? } - None => Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Algorithm is already inactive".to_string(), - })?, + } + None => Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })?, } - }, - _ => todo!() + } } } From de61f0903170bc0deb080ba224c520a7f6944ac8 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Fri, 11 Oct 2024 12:32:40 +0530 Subject: [PATCH 04/19] add function for dynamic routing --- crates/router/Cargo.toml | 2 +- crates/router/src/core/payments.rs | 47 ++++++++++++++++------- crates/router/src/core/routing/helpers.rs | 42 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 5becef839d5..fac198edfa4 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.rs b/crates/router/src/core/payments.rs index 0ae898660f7..85769488781 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -23,6 +23,7 @@ use api_models::{ self, enums, mandates::RecurringDetails, payments::{self as payments_api, HeaderPayload}, + routing as api_routing, }; pub use common_enums::enums::CallConnectorAction; use common_utils::{ @@ -4580,22 +4581,40 @@ where .change_context(errors::ApiErrorResponse::InternalServerError)?; // success_based_routing_for_connectors - business_profile.dynamic_routing_algorithm.map(|dynamic_routing_algorithm| + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] + { + if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() { - let mut success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to deserialize dynamic routing algorithm ref from business profile", - )? - .unwrap_or_default(); + let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + )? + .unwrap_or_default(); + let success_based_algo_ref = success_based_dynamic_routing_algo_ref + .success_based_algorithm + .unwrap(); + if success_based_algo_ref.enabled_feature + == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection + { + // perform success based routing + fetch_success_based_routing_configs( + &state, + routable_connectors, + &business_profile, + dynamic_routing_algorithm, + ) + .await + .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) + .ok(); + } } - ); - + } let connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 22966208dd4..88ac6b3a7f1 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -588,6 +588,48 @@ pub async fn refresh_success_based_routing_cache( config } +/// success based dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn perform_success_based_routing( + state: &SessionState, + routable_connectors: Vec, + business_profile: &domain::Profile, + dynamic_routing_algorithm: serde_json::Value, +) -> RouterResult<()> { + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate gRPC client not found".to_string(), + })?; + + let success_based_routing_configs = + fetch_success_based_routing_configs(state, business_profile, dynamic_routing_algorithm) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + + let tenant_business_profile_id = format!( + "{}:{}", + state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr() + ); + + let success_based_connectors = client + .calculate_success_rate( + tenant_business_profile_id.clone(), + success_based_routing_configs.clone(), + routable_connectors.clone(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to calculate/fetch success rate from dynamic routing service")?; + Ok(()) +} + /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] From b7b26b98cef55d912b28ea2af14f3726a02ec9e4 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Fri, 11 Oct 2024 19:39:18 +0530 Subject: [PATCH 05/19] core flows for dynamic routing --- config/development.toml | 4 + crates/router/src/core/payments.rs | 14 +- crates/router/src/core/routing.rs | 150 +++++++++++++++++++++- crates/router/src/core/routing/helpers.rs | 6 +- 4 files changed, 168 insertions(+), 6 deletions(-) diff --git a/config/development.toml b/config/development.toml index f0e26172514..6df98056178 100644 --- a/config/development.toml +++ b/config/development.toml @@ -752,3 +752,7 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[grpc_client.dynamic_routing_client] +host = "localhost" +port = 7000 diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 85769488781..798d0a571ed 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4558,6 +4558,9 @@ where F: Send + Clone, D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { + println!(">>>>>>>>>>code comes here 0"); + use super::routing::helpers::perform_success_based_routing; + let routing_algorithm_id = { let routing_algorithm = business_profile.routing_algorithm.clone(); @@ -4585,6 +4588,7 @@ where { if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() { + println!(">>>>>>>>>>code comes here 1"); let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile .dynamic_routing_algorithm @@ -4599,19 +4603,25 @@ where let success_based_algo_ref = success_based_dynamic_routing_algo_ref .success_based_algorithm .unwrap(); + println!( + ">>>>>>>>>>code comes here 1.5 {:?}", + success_based_algo_ref.enabled_feature + ); if success_based_algo_ref.enabled_feature == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection { + println!(">>>>>>>>>>code comes here 2"); // perform success based routing - fetch_success_based_routing_configs( + let connectors = perform_success_based_routing( &state, - routable_connectors, + connectors.clone(), &business_profile, dynamic_routing_algorithm, ) .await .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) .ok(); + println!(">>>>>>>>>>code comes here 3 {connectors:?}"); } } } diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 3f53c657207..c6d74c74632 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1210,8 +1210,7 @@ pub async fn toggle_success_based_routing( .unwrap_or_default(); match feature_to_enable { - api_routing::SuccessBasedRoutingFeatures::Metrics - | api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { + api_routing::SuccessBasedRoutingFeatures::Metrics => { if let Some(ref mut algo_with_timestamp) = success_based_dynamic_routing_algo_ref.success_based_algorithm { @@ -1356,6 +1355,153 @@ pub async fn toggle_success_based_routing( Ok(service_api::ApplicationResponse::Json(new_record)) } } + api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { + // we can probably remove this block and merge it above + if let Some(ref mut algo_with_timestamp) = + success_based_dynamic_routing_algo_ref.success_based_algorithm + { + match algo_with_timestamp + .algorithm_id_with_timestamp + .algorithm_id + .clone() + { + Some(algorithm_id) => { + if algo_with_timestamp.enabled_feature == feature_to_enable { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Success based routing is already enabled".to_string(), + })? + } else { + algo_with_timestamp.update_enabled_features(feature_to_enable); + let record = db + .find_routing_algorithm_by_profile_id_algorithm_id( + business_profile.get_id(), + &algorithm_id, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::ResourceIdNotFound, + )?; + let response = record.foreign_into(); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([( + "profile_id", + profile_id.get_string_repr().to_owned(), + )]), + ); + Ok(service_api::ApplicationResponse::Json(response)) + } + } + None => { + // this can only occur when dynamic routing isn't enabled + let default_success_based_routing_config = + routing::SuccessBasedRoutingConfig::default(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: business_profile.get_id().to_owned(), + merchant_id: merchant_account.get_id().to_owned(), + name: "Dynamic routing algorithm".to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Unable to insert record in routing algorithm table", + )?; + + success_based_dynamic_routing_algo_ref.update_algorithm_id( + algorithm_id, + api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection, + ); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; + + let new_record = record.foreign_into(); + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([( + "profile_id", + profile_id.get_string_repr().to_owned(), + )]), + ); + Ok(service_api::ApplicationResponse::Json(new_record)) + } + } + } else { + let default_success_based_routing_config = + routing::SuccessBasedRoutingConfig::default(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: business_profile.get_id().to_owned(), + merchant_id: merchant_account.get_id().to_owned(), + name: "Dynamic routing algorithm".to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to insert record in routing algorithm table")?; + + success_based_dynamic_routing_algo_ref.update_algorithm_id( + algorithm_id, + api_routing::SuccessBasedRoutingFeatures::Metrics, + ); + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo_ref, + ) + .await?; + + let new_record = record.foreign_into(); + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), + ); + Ok(service_api::ApplicationResponse::Json(new_record)) + } + } api_routing::SuccessBasedRoutingFeatures::None => { let timestamp = common_utils::date_time::now_unix_timestamp(); match success_based_dynamic_routing_algo_ref.success_based_algorithm { diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 88ac6b3a7f1..53632aeef23 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -588,6 +588,7 @@ pub async fn refresh_success_based_routing_cache( config } +use external_services::grpc_client::dynamic_routing::success_rate::CalSuccessRateResponse; /// success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] @@ -596,7 +597,7 @@ pub async fn perform_success_based_routing( routable_connectors: Vec, business_profile: &domain::Profile, dynamic_routing_algorithm: serde_json::Value, -) -> RouterResult<()> { +) -> RouterResult { let client = state .grpc_client .dynamic_routing @@ -627,7 +628,7 @@ pub async fn perform_success_based_routing( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to calculate/fetch success rate from dynamic routing service")?; - Ok(()) + Ok(success_based_connectors) } /// Checked fetch of success based routing configs @@ -649,6 +650,7 @@ pub async fn fetch_success_based_routing_configs( message: "success_based_algorithm not found in dynamic_routing_algorithm_ref" .to_string(), })? + .algorithm_id_with_timestamp .algorithm_id // error can be possible when the feature is toggled off. .ok_or(errors::ApiErrorResponse::GenericNotFoundError { From e5ce9e8790588824162a2e10c96fc63987e72d40 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 14 Oct 2024 18:40:15 +0530 Subject: [PATCH 06/19] add integration in core flows --- crates/router/src/core/payments.rs | 48 +--- crates/router/src/core/routing.rs | 258 ++-------------------- crates/router/src/core/routing/helpers.rs | 171 +++++++++++--- 3 files changed, 172 insertions(+), 305 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 798d0a571ed..8ba6426802c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -23,7 +23,6 @@ use api_models::{ self, enums, mandates::RecurringDetails, payments::{self as payments_api, HeaderPayload}, - routing as api_routing, }; pub use common_enums::enums::CallConnectorAction; use common_utils::{ @@ -4588,43 +4587,20 @@ where { if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() { - println!(">>>>>>>>>>code comes here 1"); - let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to deserialize dynamic routing algorithm ref from business profile", - )? - .unwrap_or_default(); - let success_based_algo_ref = success_based_dynamic_routing_algo_ref - .success_based_algorithm - .unwrap(); - println!( - ">>>>>>>>>>code comes here 1.5 {:?}", - success_based_algo_ref.enabled_feature - ); - if success_based_algo_ref.enabled_feature - == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection - { - println!(">>>>>>>>>>code comes here 2"); - // perform success based routing - let connectors = perform_success_based_routing( - &state, - connectors.clone(), - &business_profile, - dynamic_routing_algorithm, - ) - .await - .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) - .ok(); - println!(">>>>>>>>>>code comes here 3 {connectors:?}"); - } + println!(">>>>>>>>>>code comes here 2"); + let connectors = perform_success_based_routing( + state, + connectors.clone(), + business_profile, + dynamic_routing_algorithm, + ) + .await + .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) + .ok(); + println!(">>>>>>>>>>code comes here 3 {connectors:?}"); } } + let connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index c6d74c74632..75d8c57656d 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -455,7 +455,10 @@ pub async fn link_routing_config( dynamic_routing_ref .success_based_algorithm .clone() - .unwrap() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "missing success_based_algorithm in dynamic_algorithm_ref from business_profile table", + )? .enabled_feature, ); helpers::update_business_profile_active_dynamic_algorithm_ref( @@ -1210,7 +1213,8 @@ pub async fn toggle_success_based_routing( .unwrap_or_default(); match feature_to_enable { - api_routing::SuccessBasedRoutingFeatures::Metrics => { + api_routing::SuccessBasedRoutingFeatures::Metrics + | api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { if let Some(ref mut algo_with_timestamp) = success_based_dynamic_routing_algo_ref.success_based_algorithm { @@ -1257,249 +1261,27 @@ pub async fn toggle_success_based_routing( } } None => { - let default_success_based_routing_config = - routing::SuccessBasedRoutingConfig::default(); - let algorithm_id = common_utils::generate_routing_id_of_default_length(); - let timestamp = common_utils::date_time::now(); - let algo = RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: business_profile.get_id().to_owned(), - merchant_id: merchant_account.get_id().to_owned(), - name: "Dynamic routing algorithm".to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, - }; - - let record = db - .insert_routing_algorithm(algo) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Unable to insert record in routing algorithm table", - )?; - - success_based_dynamic_routing_algo_ref.update_algorithm_id( - algorithm_id, - api_routing::SuccessBasedRoutingFeatures::Metrics, - ); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, + helpers::default_success_based_routing_setup( + &state, + key_store, + business_profile.clone(), + feature_to_enable, + merchant_account.get_id().to_owned(), success_based_dynamic_routing_algo_ref, ) - .await?; - - let new_record = record.foreign_into(); - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([( - "profile_id", - profile_id.get_string_repr().to_owned(), - )]), - ); - Ok(service_api::ApplicationResponse::Json(new_record)) - } - } - } else { - let default_success_based_routing_config = - routing::SuccessBasedRoutingConfig::default(); - let algorithm_id = common_utils::generate_routing_id_of_default_length(); - let timestamp = common_utils::date_time::now(); - let algo = RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: business_profile.get_id().to_owned(), - merchant_id: merchant_account.get_id().to_owned(), - name: "Dynamic routing algorithm".to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, - }; - - let record = db - .insert_routing_algorithm(algo) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to insert record in routing algorithm table")?; - - success_based_dynamic_routing_algo_ref.update_algorithm_id( - algorithm_id, - api_routing::SuccessBasedRoutingFeatures::Metrics, - ); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - success_based_dynamic_routing_algo_ref, - ) - .await?; - - let new_record = record.foreign_into(); - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), - ); - Ok(service_api::ApplicationResponse::Json(new_record)) - } - } - api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { - // we can probably remove this block and merge it above - if let Some(ref mut algo_with_timestamp) = - success_based_dynamic_routing_algo_ref.success_based_algorithm - { - match algo_with_timestamp - .algorithm_id_with_timestamp - .algorithm_id - .clone() - { - Some(algorithm_id) => { - if algo_with_timestamp.enabled_feature == feature_to_enable { - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Success based routing is already enabled".to_string(), - })? - } else { - algo_with_timestamp.update_enabled_features(feature_to_enable); - let record = db - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &algorithm_id, - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::ResourceIdNotFound, - )?; - let response = record.foreign_into(); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - success_based_dynamic_routing_algo_ref, - ) - .await?; - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([( - "profile_id", - profile_id.get_string_repr().to_owned(), - )]), - ); - Ok(service_api::ApplicationResponse::Json(response)) - } - } - None => { - // this can only occur when dynamic routing isn't enabled - let default_success_based_routing_config = - routing::SuccessBasedRoutingConfig::default(); - let algorithm_id = common_utils::generate_routing_id_of_default_length(); - let timestamp = common_utils::date_time::now(); - let algo = RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: business_profile.get_id().to_owned(), - merchant_id: merchant_account.get_id().to_owned(), - name: "Dynamic routing algorithm".to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, - }; - - let record = db - .insert_routing_algorithm(algo) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Unable to insert record in routing algorithm table", - )?; - - success_based_dynamic_routing_algo_ref.update_algorithm_id( - algorithm_id, - api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection, - ); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - success_based_dynamic_routing_algo_ref, - ) - .await?; - - let new_record = record.foreign_into(); - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([( - "profile_id", - profile_id.get_string_repr().to_owned(), - )]), - ); - Ok(service_api::ApplicationResponse::Json(new_record)) + .await } } } else { - let default_success_based_routing_config = - routing::SuccessBasedRoutingConfig::default(); - let algorithm_id = common_utils::generate_routing_id_of_default_length(); - let timestamp = common_utils::date_time::now(); - let algo = RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: business_profile.get_id().to_owned(), - merchant_id: merchant_account.get_id().to_owned(), - name: "Dynamic routing algorithm".to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, - }; - - let record = db - .insert_routing_algorithm(algo) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to insert record in routing algorithm table")?; - - success_based_dynamic_routing_algo_ref.update_algorithm_id( - algorithm_id, - api_routing::SuccessBasedRoutingFeatures::Metrics, - ); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, + helpers::default_success_based_routing_setup( + &state, + key_store, + business_profile.clone(), + feature_to_enable, + merchant_account.get_id().to_owned(), success_based_dynamic_routing_algo_ref, ) - .await?; - - let new_record = record.foreign_into(); - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), - ); - Ok(service_api::ApplicationResponse::Json(new_record)) + .await } } api_routing::SuccessBasedRoutingFeatures::None => { diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 53632aeef23..c01bbe7ae1f 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -12,10 +12,14 @@ use api_models::routing as routing_types; use common_utils::ext_traits::ValueExt; use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState}; use diesel_models::configs; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(feature = "dynamic_routing")] use external_services::grpc_client::dynamic_routing::SuccessBasedDynamicRouting; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use hyperswitch_domain_models::api::ApplicationResponse; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, metrics::add_attributes, tracing}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; @@ -30,7 +34,7 @@ use crate::{ utils::StringExt, }; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use crate::{core::metrics as core_metrics, routes::metrics}; +use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them @@ -588,7 +592,6 @@ pub async fn refresh_success_based_routing_cache( config } -use external_services::grpc_client::dynamic_routing::success_rate::CalSuccessRateResponse; /// success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] @@ -597,38 +600,88 @@ pub async fn perform_success_based_routing( routable_connectors: Vec, business_profile: &domain::Profile, dynamic_routing_algorithm: serde_json::Value, -) -> RouterResult { - let client = state - .grpc_client - .dynamic_routing - .success_rate_client - .as_ref() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_rate gRPC client not found".to_string(), - })?; +) -> RouterResult> { + use external_services::grpc_client::dynamic_routing::success_rate::CalSuccessRateResponse; + + let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + )? + .unwrap_or_default(); + let success_based_algo_ref = success_based_dynamic_routing_algo_ref + .success_based_algorithm + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch success_based_algorithm from dynamic_routing_algorithm", + )?; + if success_based_algo_ref.enabled_feature + == routing_types::SuccessBasedRoutingFeatures::DynamicConnectorSelection + { + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate gRPC client not found".to_string(), + })?; + + let success_based_routing_configs = + fetch_success_based_routing_configs(state, business_profile, dynamic_routing_algorithm) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to retrieve success_rate based dynamic routing configs", + )?; - let success_based_routing_configs = - fetch_success_based_routing_configs(state, business_profile, dynamic_routing_algorithm) + let tenant_business_profile_id = format!( + "{}:{}", + state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr() + ); + + let success_based_connectors: CalSuccessRateResponse = client + .calculate_success_rate( + tenant_business_profile_id.clone(), + success_based_routing_configs.clone(), + routable_connectors.clone(), + ) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; - - let tenant_business_profile_id = format!( - "{}:{}", - state.tenant.redis_key_prefix, - business_profile.get_id().get_string_repr() - ); - - let success_based_connectors = client - .calculate_success_rate( - tenant_business_profile_id.clone(), - success_based_routing_configs.clone(), - routable_connectors.clone(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to calculate/fetch success rate from dynamic routing service")?; - Ok(success_based_connectors) + .attach_printable( + "unable to calculate/fetch success rate from dynamic routing service", + )?; + + let mut connectors = Vec::with_capacity(success_based_connectors.labels_with_score.len()); + for label_with_score in success_based_connectors.labels_with_score { + let (connector, merchant_connector_id) = label_with_score.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to split connector_name and mca_id from the first connector obtained from dynamic routing service", + )?; + connectors.push(routing_types::RoutableConnectorChoice { + choice_kind: routing_types::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str(connector) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to infer routable_connector from connector")?, + merchant_connector_id: Some( + id_type::MerchantConnectorAccountId::wrap(merchant_connector_id.to_string()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to infer routable_connector from connector")?, + ), + }); + } + Ok(connectors) + } else { + Ok(routable_connectors) + } } /// Checked fetch of success based routing configs @@ -919,3 +972,59 @@ fn get_success_based_metrics_outcome_for_payment( _ => common_enums::SuccessBasedRoutingConclusiveState::NonDeterministic, } } + +/// Checked fetch of success based routing configs +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn default_success_based_routing_setup( + state: &SessionState, + key_store: domain::MerchantKeyStore, + business_profile: domain::Profile, + feature_to_enable: routing_types::SuccessBasedRoutingFeatures, + merchant_id: id_type::MerchantId, + mut success_based_dynamic_routing_algo: routing_types::DynamicRoutingAlgorithmRef, +) -> RouterResult> { + let db = state.store.as_ref(); + let key_manager_state = &state.into(); + let profile_id = business_profile.get_id().to_owned(); + let default_success_based_routing_config = routing_types::SuccessBasedRoutingConfig::default(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = routing_algorithm::RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: profile_id.clone(), + merchant_id, + name: "Dynamic routing algorithm".to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to insert record in routing algorithm table")?; + + success_based_dynamic_routing_algo.update_algorithm_id(algorithm_id, feature_to_enable); + update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + success_based_dynamic_routing_algo, + ) + .await?; + + let new_record = record.foreign_into(); + + core_metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id.get_string_repr().to_string())]), + ); + Ok(ApplicationResponse::Json(new_record)) +} From 73ecfd0cc83015b650ed70240504039d2b406bda Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 14 Oct 2024 22:28:08 +0530 Subject: [PATCH 07/19] minor refactors --- crates/api_models/src/routing.rs | 2 +- crates/router/src/core/payments.rs | 21 ++--- crates/router/src/core/payments/routing.rs | 98 ++++++++++++++++++++++ crates/router/src/core/routing.rs | 16 ++-- crates/router/src/core/routing/helpers.rs | 6 +- crates/router/src/routes/routing.rs | 4 +- 6 files changed, 123 insertions(+), 24 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 89871421ad8..c982cb4863c 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -579,7 +579,7 @@ pub struct SuccessBasedRoutingUpdateConfigQuery { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ToggleSuccessBasedRoutingWrapper { pub profile_id: common_utils::id_type::ProfileId, - pub enable: SuccessBasedRoutingFeatures, + pub feature_to_enable: SuccessBasedRoutingFeatures, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8ba6426802c..d178df0b200 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4557,9 +4557,6 @@ where F: Send + Clone, D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - println!(">>>>>>>>>>code comes here 0"); - use super::routing::helpers::perform_success_based_routing; - let routing_algorithm_id = { let routing_algorithm = business_profile.routing_algorithm.clone(); @@ -4584,22 +4581,26 @@ where // success_based_routing_for_connectors #[cfg(all(feature = "v1", feature = "dynamic_routing"))] - { + let connectors = { if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() { - println!(">>>>>>>>>>code comes here 2"); - let connectors = perform_success_based_routing( + if let Ok(success_based_connectors) = routing::perform_success_based_routing( state, connectors.clone(), business_profile, dynamic_routing_algorithm, ) .await - .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) - .ok(); - println!(">>>>>>>>>>code comes here 3 {connectors:?}"); + .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) + { + success_based_connectors + } else { + connectors + } + } else { + connectors } - } + }; let connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 04080a42730..5af172939ed 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -7,6 +7,8 @@ use std::{ sync::Arc, }; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use api_models::routing as api_routing; use api_models::{ admin as admin_api, enums::{self as api_enums, CountryAlpha2}, @@ -21,6 +23,10 @@ use euclid::{ enums as euclid_enums, frontend::{ast, dir as euclid_dir}, }; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use external_services::grpc_client::dynamic_routing::{ + success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, +}; use kgraph_utils::{ mca as mca_graph, transformers::{IntoContext, IntoDirValue}, @@ -1202,3 +1208,95 @@ pub fn make_dsl_input_for_surcharge( }; Ok(backend_input) } + +/// success based dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn perform_success_based_routing( + state: &SessionState, + routable_connectors: Vec, + business_profile: &domain::Profile, + dynamic_routing_algorithm: serde_json::Value, +) -> RoutingResult> { + let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + )? + .unwrap_or_default(); + let success_based_algo_ref = success_based_dynamic_routing_algo_ref + .success_based_algorithm + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch success_based_algorithm from dynamic_routing_algorithm", + )?; + if success_based_algo_ref.enabled_feature + == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection + { + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate gRPC client not found".to_string(), + })?; + + let success_based_routing_configs = routing::helpers::fetch_success_based_routing_configs( + state, + business_profile, + dynamic_routing_algorithm, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + + let tenant_business_profile_id = format!( + "{}:{}", + state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr() + ); + + let success_based_connectors: CalSuccessRateResponse = client + .calculate_success_rate( + tenant_business_profile_id.clone(), + success_based_routing_configs.clone(), + routable_connectors.clone(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to calculate/fetch success rate from dynamic routing service", + )?; + + let mut connectors = Vec::with_capacity(success_based_connectors.labels_with_score.len()); + for label_with_score in success_based_connectors.labels_with_score { + let (connector, merchant_connector_id) = label_with_score.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to split connector_name and mca_id from the first connector obtained from dynamic routing service", + )?; + connectors.push(api_routing::RoutableConnectorChoice { + choice_kind: api_routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str(connector) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to infer routable_connector from connector")?, + merchant_connector_id: Some( + common_utils::id_type::MerchantConnectorAccountId::wrap( + merchant_connector_id.to_string(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to infer routable_connector from connector")?, + ), + }); + } + Ok(connectors) + } else { + Ok(routable_connectors) + } +} diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 75d8c57656d..792a74a27ba 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -2,7 +2,7 @@ pub mod helpers; pub mod transformers; use api_models::{ - enums, mandates as mandates_api, routing, routing as api_routing, + enums, mandates as mandates_api, routing, routing::{self as routing_types, RoutingRetrieveQuery}, }; use diesel_models::routing_algorithm::RoutingAlgorithm; @@ -434,7 +434,7 @@ pub async fn link_routing_config( utils::when( matches!( dynamic_routing_ref.success_based_algorithm, - Some(api_routing::SuccessBasedAlgorithm { + Some(routing::SuccessBasedAlgorithm { algorithm_id_with_timestamp: routing_types::DynamicAlgorithmWithTimestamp { algorithm_id: Some(ref id), @@ -1176,7 +1176,7 @@ pub async fn toggle_success_based_routing( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - feature_to_enable: api_routing::SuccessBasedRoutingFeatures, + feature_to_enable: routing::SuccessBasedRoutingFeatures, profile_id: common_utils::id_type::ProfileId, ) -> RouterResponse { metrics::ROUTING_CREATE_REQUEST_RECEIVED.add( @@ -1213,8 +1213,8 @@ pub async fn toggle_success_based_routing( .unwrap_or_default(); match feature_to_enable { - api_routing::SuccessBasedRoutingFeatures::Metrics - | api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { + routing::SuccessBasedRoutingFeatures::Metrics + | routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { if let Some(ref mut algo_with_timestamp) = success_based_dynamic_routing_algo_ref.success_based_algorithm { @@ -1284,7 +1284,7 @@ pub async fn toggle_success_based_routing( .await } } - api_routing::SuccessBasedRoutingFeatures::None => { + routing::SuccessBasedRoutingFeatures::None => { let timestamp = common_utils::date_time::now_unix_timestamp(); match success_based_dynamic_routing_algo_ref.success_based_algorithm { Some(algorithm_ref) => { @@ -1292,13 +1292,13 @@ pub async fn toggle_success_based_routing( algorithm_ref.algorithm_id_with_timestamp.algorithm_id { let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { - success_based_algorithm: Some(api_routing::SuccessBasedAlgorithm { + success_based_algorithm: Some(routing::SuccessBasedAlgorithm { algorithm_id_with_timestamp: routing_types::DynamicAlgorithmWithTimestamp { algorithm_id: None, timestamp, }, - enabled_feature: api_routing::SuccessBasedRoutingFeatures::None, + enabled_feature: routing::SuccessBasedRoutingFeatures::None, }), }; diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index c01bbe7ae1f..5e4d36ba8d9 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -16,7 +16,9 @@ use diesel_models::configs; use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(feature = "dynamic_routing")] -use external_services::grpc_client::dynamic_routing::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::{ + success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, +}; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -601,8 +603,6 @@ pub async fn perform_success_based_routing( business_profile: &domain::Profile, dynamic_routing_algorithm: serde_json::Value, ) -> RouterResult> { - use external_services::grpc_client::dynamic_routing::success_rate::CalSuccessRateResponse; - let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = business_profile .dynamic_routing_algorithm diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index a504728314a..cc4a9510ab6 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -989,7 +989,7 @@ pub async fn toggle_success_based_routing( ) -> impl Responder { let flow = Flow::ToggleDynamicRouting; let wrapper = routing_types::ToggleSuccessBasedRoutingWrapper { - enable: query.into_inner().enable, + feature_to_enable: query.into_inner().enable, profile_id: path.into_inner().profile_id, }; Box::pin(oss_api::server_wrap( @@ -1005,7 +1005,7 @@ pub async fn toggle_success_based_routing( state, auth.merchant_account, auth.key_store, - wrapper.enable, + wrapper.feature_to_enable, wrapper.profile_id, ) }, From 8dbdb81a82dc0975e948b84b591562a8481b0de3 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Tue, 15 Oct 2024 00:05:46 +0530 Subject: [PATCH 08/19] add new error variants in routing error --- crates/router/src/core/errors.rs | 10 +++++++ crates/router/src/core/payments/routing.rs | 35 ++++++++++++++-------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index c56bfaca913..1e3c03f61f7 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -298,6 +298,8 @@ pub enum RoutingError { DslFinalConnectorSelectionFailed, #[error("[DSL] Received incorrect selection algorithm as DSL output")] DslIncorrectSelectionAlgorithm, + #[error("unable to find '{field}'")] + GenericNotFoundError { field: String }, #[error("there was an error saving/retrieving values from the kgraph cache")] KgraphCacheFailure, #[error("failed to refresh the kgraph cache")] @@ -318,6 +320,14 @@ pub enum RoutingError { VolumeSplitFailed, #[error("Unable to parse metadata")] MetadataParsingError, + #[error("Unable to retrieve success based routing config")] + SuccessBasedRoutingConfigError, + #[error("Unable to calculate success based routing config from dynamic routing service")] + SuccessRateCalculationError, + #[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}'")] + InvalidSuccessBasedConnectorLabel(String), } #[derive(Debug, Clone, thiserror::Error)] diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 5af172939ed..ce1c75d6381 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1223,17 +1223,21 @@ pub async fn perform_success_based_routing( .clone() .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::RoutingError::GenericNotFoundError { + field: "dynamic_routing_algorithm".to_string(), + }) .attach_printable( "unable to deserialize dynamic routing algorithm ref from business profile", )? .unwrap_or_default(); + let success_based_algo_ref = success_based_dynamic_routing_algo_ref .success_based_algorithm - .ok_or(errors::ApiErrorResponse::InternalServerError) + .ok_or(errors::RoutingError::GenericNotFoundError { field: "success_based_algorithm".to_string() }) .attach_printable( - "unable to fetch success_based_algorithm from dynamic_routing_algorithm", + "success_based_algorithm not found in dynamic_routing_algorithm from business_profile table", )?; + if success_based_algo_ref.enabled_feature == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection { @@ -1242,9 +1246,10 @@ pub async fn perform_success_based_routing( .dynamic_routing .success_rate_client .as_ref() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_rate gRPC client not found".to_string(), - })?; + .ok_or(errors::RoutingError::GenericNotFoundError { + field: "success_rate gRPC client".to_string(), + }) + .attach_printable("success_rate gRPC client not found")?; let success_based_routing_configs = routing::helpers::fetch_success_based_routing_configs( state, @@ -1252,7 +1257,7 @@ pub async fn perform_success_based_routing( dynamic_routing_algorithm, ) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; let tenant_business_profile_id = format!( @@ -1268,7 +1273,7 @@ pub async fn perform_success_based_routing( routable_connectors.clone(), ) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::RoutingError::SuccessRateCalculationError) .attach_printable( "unable to calculate/fetch success rate from dynamic routing service", )?; @@ -1277,21 +1282,27 @@ pub async fn perform_success_based_routing( for label_with_score in success_based_connectors.labels_with_score { let (connector, merchant_connector_id) = label_with_score.label .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) + .ok_or(errors::RoutingError::InvalidSuccessBasedConnectorLabel(label_with_score.label.to_string())) .attach_printable( "unable to split connector_name and mca_id from the first connector obtained from dynamic routing service", )?; connectors.push(api_routing::RoutableConnectorChoice { choice_kind: api_routing::RoutableChoiceKind::FullStruct, connector: common_enums::RoutableConnectors::from_str(connector) - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "RoutableConnectors".to_string(), + }) .attach_printable("unable to infer routable_connector from connector")?, merchant_connector_id: Some( common_utils::id_type::MerchantConnectorAccountId::wrap( merchant_connector_id.to_string(), ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to infer routable_connector from connector")?, + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "MerchantConnectorAccountId".to_string(), + }) + .attach_printable("unable to infer MerchantConnectorAccountId from string")?, ), }); } From 78d0f3b2047e95855bb2e0d18548ca331610840a Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Wed, 16 Oct 2024 17:55:49 +0530 Subject: [PATCH 09/19] fix comments --- crates/router/src/core/errors.rs | 4 +-- crates/router/src/core/payments.rs | 32 ++++++++++------------ crates/router/src/core/payments/routing.rs | 8 +++--- crates/router/src/core/routing.rs | 6 ++++ crates/router/src/core/routing/helpers.rs | 9 ++++-- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 1e3c03f61f7..baf34354166 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -298,8 +298,6 @@ pub enum RoutingError { DslFinalConnectorSelectionFailed, #[error("[DSL] Received incorrect selection algorithm as DSL output")] DslIncorrectSelectionAlgorithm, - #[error("unable to find '{field}'")] - GenericNotFoundError { field: String }, #[error("there was an error saving/retrieving values from the kgraph cache")] KgraphCacheFailure, #[error("failed to refresh the kgraph cache")] @@ -328,6 +326,8 @@ pub enum RoutingError { GenericConversionError { from: String, to: String }, #[error("Invalid success based connector label received from dynamic routing service: '{0}'")] InvalidSuccessBasedConnectorLabel(String), + #[error("unable to find '{field}'")] + GenericNotFoundError { field: String }, } #[derive(Debug, Clone, thiserror::Error)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index d178df0b200..c29b09ccbe9 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4582,24 +4582,22 @@ where // success_based_routing_for_connectors #[cfg(all(feature = "v1", feature = "dynamic_routing"))] let connectors = { - if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() - { - if let Ok(success_based_connectors) = routing::perform_success_based_routing( - state, - connectors.clone(), - business_profile, - dynamic_routing_algorithm, - ) + business_profile + .dynamic_routing_algorithm + .clone() + .async_map(|dynamic_routing_algorithm| async { + routing::perform_success_based_routing( + state, + connectors.clone(), + business_profile, + dynamic_routing_algorithm, + ) + .await + .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) + .unwrap_or(connectors.clone()) + }) .await - .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) - { - success_based_connectors - } else { - connectors - } - } else { - connectors - } + .unwrap_or(connectors) }; let connectors = routing::perform_eligibility_analysis_with_fallback( diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index ce1c75d6381..1837fa77261 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1227,7 +1227,7 @@ pub async fn perform_success_based_routing( field: "dynamic_routing_algorithm".to_string(), }) .attach_printable( - "unable to deserialize dynamic routing algorithm ref from business profile", + "unable to deserialize dynamic_routing_algorithm from business profile to dynamic_routing_algorithm_ref", )? .unwrap_or_default(); @@ -1258,7 +1258,7 @@ pub async fn perform_success_based_routing( ) .await .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) - .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + .attach_printable("unable to fetch success_rate based dynamic routing configs")?; let tenant_business_profile_id = format!( "{}:{}", @@ -1293,7 +1293,7 @@ pub async fn perform_success_based_routing( from: "String".to_string(), to: "RoutableConnectors".to_string(), }) - .attach_printable("unable to infer routable_connector from connector")?, + .attach_printable("unable to convert routable_connector from connector")?, merchant_connector_id: Some( common_utils::id_type::MerchantConnectorAccountId::wrap( merchant_connector_id.to_string(), @@ -1302,7 +1302,7 @@ pub async fn perform_success_based_routing( from: "String".to_string(), to: "MerchantConnectorAccountId".to_string(), }) - .attach_printable("unable to infer MerchantConnectorAccountId from string")?, + .attach_printable("unable to convert MerchantConnectorAccountId from string")?, ), }); } diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 792a74a27ba..146a39e4229 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1224,11 +1224,14 @@ pub async fn toggle_success_based_routing( .clone() { Some(algorithm_id) => { + // algorithm is already present in profile if algo_with_timestamp.enabled_feature == feature_to_enable { + // algortihm already has the required feature Err(errors::ApiErrorResponse::PreconditionFailed { message: "Success based routing is already enabled".to_string(), })? } else { + // enable the requested feature for the algorithm algo_with_timestamp.update_enabled_features(feature_to_enable); let record = db .find_routing_algorithm_by_profile_id_algorithm_id( @@ -1261,6 +1264,7 @@ pub async fn toggle_success_based_routing( } } None => { + // algorithm isn't present in profile helpers::default_success_based_routing_setup( &state, key_store, @@ -1273,6 +1277,7 @@ pub async fn toggle_success_based_routing( } } } else { + // algorithm isn't present in profile helpers::default_success_based_routing_setup( &state, key_store, @@ -1285,6 +1290,7 @@ pub async fn toggle_success_based_routing( } } routing::SuccessBasedRoutingFeatures::None => { + // disable success based routing for the requested profile let timestamp = common_utils::date_time::now_unix_timestamp(); match success_based_dynamic_routing_algo_ref.success_based_algorithm { Some(algorithm_ref) => { diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 5e4d36ba8d9..2a4d6416efd 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -2,6 +2,7 @@ //! //! Functions that are used to perform the retrieval of merchant's //! routing dict, configs, defaults + #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use std::str::FromStr; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] @@ -15,7 +16,7 @@ use diesel_models::configs; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use diesel_models::routing_algorithm; use error_stack::ResultExt; -#[cfg(feature = "dynamic_routing")] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use external_services::grpc_client::dynamic_routing::{ success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, }; @@ -611,15 +612,17 @@ pub async fn perform_success_based_routing( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( - "unable to deserialize dynamic routing algorithm ref from business profile", + "unable to deserialize dynamic routing algorithm from business profile to dynamic_routing_algorithm_ref", )? .unwrap_or_default(); + let success_based_algo_ref = success_based_dynamic_routing_algo_ref .success_based_algorithm .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( "unable to fetch success_based_algorithm from dynamic_routing_algorithm", )?; + if success_based_algo_ref.enabled_feature == routing_types::SuccessBasedRoutingFeatures::DynamicConnectorSelection { @@ -973,7 +976,7 @@ fn get_success_based_metrics_outcome_for_payment( } } -/// Checked fetch of success based routing configs +/// default config setup for success_based_routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn default_success_based_routing_setup( From 34d6e08aea6071b603141fb09df0bc73e984eaa9 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Wed, 16 Oct 2024 18:16:27 +0530 Subject: [PATCH 10/19] remove dynamic_routing from cargo.toml --- crates/router/Cargo.toml | 2 +- crates/router/src/core/payments/routing.rs | 4 ++-- crates/router/src/core/routing/helpers.rs | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index fac198edfa4..5becef839d5 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 = ["dynamic_routing","kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] +common_default = ["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 1837fa77261..ad7921706a7 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -7,8 +7,6 @@ use std::{ sync::Arc, }; -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use api_models::routing as api_routing; use api_models::{ admin as admin_api, enums::{self as api_enums, CountryAlpha2}, @@ -24,6 +22,8 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use api_models::routing as api_routing; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, }; diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 2a4d6416efd..96924eee598 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -2,7 +2,6 @@ //! //! Functions that are used to perform the retrieval of merchant's //! routing dict, configs, defaults - #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use std::str::FromStr; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] @@ -13,9 +12,9 @@ use api_models::routing as routing_types; use common_utils::ext_traits::ValueExt; use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState}; use diesel_models::configs; +use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] 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::CalSuccessRateResponse, SuccessBasedDynamicRouting, @@ -24,6 +23,8 @@ use external_services::grpc_client::dynamic_routing::{ use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, metrics::add_attributes, tracing}; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; @@ -36,8 +37,6 @@ use crate::{ types::{domain, storage}, utils::StringExt, }; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them From 0ece5b3fb40cd4d3ead771a41f011b444e053c2d Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:47:36 +0000 Subject: [PATCH 11/19] chore: run formatter --- crates/router/src/core/payments/routing.rs | 4 ++-- crates/router/src/core/routing/helpers.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index ad7921706a7..1837fa77261 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -7,6 +7,8 @@ use std::{ sync::Arc, }; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use api_models::routing as api_routing; use api_models::{ admin as admin_api, enums::{self as api_enums, CountryAlpha2}, @@ -22,8 +24,6 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use api_models::routing as api_routing; -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, }; diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 96924eee598..ab4366c0e07 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -12,9 +12,9 @@ use api_models::routing as routing_types; use common_utils::ext_traits::ValueExt; use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState}; use diesel_models::configs; -use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] 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::CalSuccessRateResponse, SuccessBasedDynamicRouting, @@ -23,8 +23,6 @@ use external_services::grpc_client::dynamic_routing::{ use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, metrics::add_attributes, tracing}; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; @@ -37,6 +35,8 @@ use crate::{ types::{domain, storage}, utils::StringExt, }; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them From 7a581ea3a75f606424f427ade6b879719a5e0b80 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Thu, 17 Oct 2024 14:29:55 +0530 Subject: [PATCH 12/19] minor nit picky refactors --- api-reference/openapi_spec.json | 184 ++------------------- crates/openapi/src/openapi.rs | 7 +- crates/openapi/src/routes/routing.rs | 29 +--- crates/router/src/core/payments.rs | 2 +- crates/router/src/core/payments/routing.rs | 4 +- crates/router/src/core/routing.rs | 2 +- crates/router/src/core/routing/helpers.rs | 14 +- 7 files changed, 23 insertions(+), 219 deletions(-) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d0e6c6ad0aa..2536aacf2ea 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -3912,12 +3912,12 @@ } }, { - "name": "status", + "name": "enable", "in": "query", - "description": "Boolean value for mentioning the expected state of dynamic routing", + "description": "Feature to enable for success based routing", "required": true, "schema": { - "type": "boolean" + "$ref": "#/components/schemas/SuccessBasedRoutingFeatures" } } ], @@ -3958,87 +3958,6 @@ ] } }, - "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/config/:algorithm_id": { - "patch": { - "tags": [ - "Routing" - ], - "summary": "Routing - Update config for success based dynamic routing", - "description": "Update config for success based dynamic routing", - "operationId": "Update configs for success based dynamic routing algorithm", - "parameters": [ - { - "name": "account_id", - "in": "path", - "description": "Merchant id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "profile_id", - "in": "path", - "description": "The unique identifier for a profile", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "algorithm_id", - "in": "path", - "description": "The unique identifier for routing algorithm", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessBasedRoutingConfig" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Routing Algorithm updated", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RoutingDictionaryRecord" - } - } - } - }, - "400": { - "description": "Request body is malformed" - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "Resource missing" - }, - "422": { - "description": "Unprocessable request" - }, - "500": { - "description": "Internal server error" - } - }, - "security": [ - { - "admin_api_key": [] - } - ] - } - }, "/blocklist": { "delete": { "tags": [ @@ -9401,23 +9320,6 @@ "ZMW" ] }, - "CurrentBlockThreshold": { - "type": "object", - "properties": { - "duration_in_mins": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - }, - "max_total_count": { - "type": "integer", - "format": "int64", - "nullable": true, - "minimum": 0 - } - } - }, "CustomerAcceptance": { "type": "object", "description": "This \"CustomerAcceptance\" object is passed during Payments-Confirm request, it enlists the type, time, and mode of acceptance properties related to an acceptance done by the customer. The customer_acceptance sub object is usually passed by the SDK or client.", @@ -23286,80 +23188,14 @@ "destination" ] }, - "SuccessBasedRoutingConfig": { - "type": "object", - "properties": { - "params": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SuccessBasedRoutingConfigParams" - }, - "nullable": true - }, - "config": { - "allOf": [ - { - "$ref": "#/components/schemas/SuccessBasedRoutingConfigBody" - } - ], - "nullable": true - } - } - }, - "SuccessBasedRoutingConfigBody": { - "type": "object", - "properties": { - "min_aggregates_size": { - "type": "integer", - "format": "int32", - "nullable": true, - "minimum": 0 - }, - "default_success_rate": { - "type": "number", - "format": "double", - "nullable": true - }, - "max_aggregates_size": { - "type": "integer", - "format": "int32", - "nullable": true, - "minimum": 0 - }, - "current_block_threshold": { - "allOf": [ - { - "$ref": "#/components/schemas/CurrentBlockThreshold" - } - ], - "nullable": true - } - } - }, - "SuccessBasedRoutingConfigParams": { + "SuccessBasedRoutingFeatures": { "type": "string", "enum": [ - "PaymentMethod", - "PaymentMethodType", - "Currency", - "AuthenticationType" + "metrics", + "dynamic_connector_selection", + "none" ] }, - "SuccessBasedRoutingUpdateConfigQuery": { - "type": "object", - "required": [ - "algorithm_id", - "profile_id" - ], - "properties": { - "algorithm_id": { - "type": "string" - }, - "profile_id": { - "type": "string" - } - } - }, "SurchargeDetailsResponse": { "type": "object", "required": [ @@ -23629,11 +23465,11 @@ "ToggleSuccessBasedRoutingQuery": { "type": "object", "required": [ - "status" + "enable" ], "properties": { - "status": { - "type": "boolean" + "enable": { + "$ref": "#/components/schemas/SuccessBasedRoutingFeatures" } } }, diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index a76726eb8ac..b459cee7ad4 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -159,7 +159,6 @@ Never share your secret api keys. Keep them guarded and secure. routes::routing::routing_retrieve_default_config_for_profiles, routes::routing::routing_update_default_config_for_profile, routes::routing::toggle_success_based_routing, - routes::routing::success_based_routing_update_configs, // Routes for blocklist routes::blocklist::remove_entry_from_blocklist, @@ -559,6 +558,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::RoutingDictionaryRecord, api_models::routing::RoutingKind, api_models::routing::RoutableConnectorChoice, + api_models::routing::SuccessBasedRoutingFeatures, api_models::routing::LinkedRoutingConfigRetrieveResponse, api_models::routing::RoutingRetrieveResponse, api_models::routing::ProfileDefaultRoutingConfig, @@ -570,11 +570,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::ConnectorVolumeSplit, api_models::routing::ConnectorSelection, api_models::routing::ToggleSuccessBasedRoutingQuery, - api_models::routing::SuccessBasedRoutingConfig, - api_models::routing::SuccessBasedRoutingConfigParams, - api_models::routing::SuccessBasedRoutingConfigBody, - api_models::routing::CurrentBlockThreshold, - api_models::routing::SuccessBasedRoutingUpdateConfigQuery, api_models::routing::ToggleSuccessBasedRoutingPath, api_models::routing::ast::RoutableChoiceKind, api_models::enums::RoutableConnectors, diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 009708d860d..0bb79a2bbe4 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -266,7 +266,7 @@ pub async fn routing_update_default_config_for_profile() {} params( ("account_id" = String, Path, description = "Merchant id"), ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), - ("status" = bool, Query, description = "Boolean value for mentioning the expected state of dynamic routing"), + ("enable" = SuccessBasedRoutingFeatures, Query, description = "Feature to enable for success based routing"), ), responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -281,30 +281,3 @@ pub async fn routing_update_default_config_for_profile() {} security(("api_key" = []), ("jwt_key" = [])) )] pub async fn toggle_success_based_routing() {} - -#[cfg(feature = "v1")] -/// Routing - Update config for success based dynamic routing -/// -/// Update config for success based dynamic routing -#[utoipa::path( - patch, - path = "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/config/:algorithm_id", - request_body = SuccessBasedRoutingConfig, - params( - ("account_id" = String, Path, description = "Merchant id"), - ("profile_id" = String, Path, description = "The unique identifier for a profile"), - ("algorithm_id" = String, Path, description = "The unique identifier for routing algorithm"), - ), - responses( - (status = 200, description = "Routing Algorithm updated", body = RoutingDictionaryRecord), - (status = 400, description = "Request body is malformed"), - (status = 500, description = "Internal server error"), - (status = 404, description = "Resource missing"), - (status = 422, description = "Unprocessable request"), - (status = 403, description = "Forbidden"), - ), - tag = "Routing", - operation_id = "Update configs for success based dynamic routing algorithm", - security(("admin_api_key" = [])) -)] -pub async fn success_based_routing_update_configs() {} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c29b09ccbe9..a6fc4884e45 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4579,7 +4579,7 @@ where .await .change_context(errors::ApiErrorResponse::InternalServerError)?; - // success_based_routing_for_connectors + // dynamic success based connector selection #[cfg(all(feature = "v1", feature = "dynamic_routing"))] let connectors = { business_profile diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index ad7921706a7..1837fa77261 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -7,6 +7,8 @@ use std::{ sync::Arc, }; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use api_models::routing as api_routing; use api_models::{ admin as admin_api, enums::{self as api_enums, CountryAlpha2}, @@ -22,8 +24,6 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use api_models::routing as api_routing; -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] use external_services::grpc_client::dynamic_routing::{ success_rate::CalSuccessRateResponse, SuccessBasedDynamicRouting, }; diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 146a39e4229..815e3807075 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1226,7 +1226,7 @@ pub async fn toggle_success_based_routing( Some(algorithm_id) => { // algorithm is already present in profile if algo_with_timestamp.enabled_feature == feature_to_enable { - // algortihm already has the required feature + // algorithm already has the required feature Err(errors::ApiErrorResponse::PreconditionFailed { message: "Success based routing is already enabled".to_string(), })? diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 96924eee598..e95bdf45d92 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -12,19 +12,17 @@ use api_models::routing as routing_types; use common_utils::ext_traits::ValueExt; use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState}; use diesel_models::configs; -use error_stack::ResultExt; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +#[cfg(feature = "v1")] 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::CalSuccessRateResponse, SuccessBasedDynamicRouting, }; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +#[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +#[cfg(any(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, metrics::add_attributes, tracing}; -#[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; @@ -37,6 +35,8 @@ use crate::{ types::{domain, storage}, utils::StringExt, }; +#[cfg(feature = "v1")] +use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them @@ -976,7 +976,7 @@ fn get_success_based_metrics_outcome_for_payment( } /// default config setup for success_based_routing -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[cfg(feature = "v1")] #[instrument(skip_all)] pub async fn default_success_based_routing_setup( state: &SessionState, From f1c89ff33e32eb79fc1c1612db459b971c3bfb7f Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Fri, 18 Oct 2024 08:51:17 +0530 Subject: [PATCH 13/19] address comments --- crates/router/Cargo.toml | 2 +- crates/router/src/core/errors.rs | 4 + crates/router/src/core/payments.rs | 9 +- crates/router/src/core/payments/routing.rs | 17 ++-- crates/router/src/core/routing.rs | 4 +- crates/router/src/core/routing/helpers.rs | 96 +--------------------- 6 files changed, 21 insertions(+), 111 deletions(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 5becef839d5..8eccb4da00a 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/errors.rs b/crates/router/src/core/errors.rs index baf34354166..18af1b064cd 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -322,12 +322,16 @@ pub enum RoutingError { SuccessBasedRoutingConfigError, #[error("Unable to calculate success based routing config from dynamic routing service")] SuccessRateCalculationError, + #[error("Success rate client from dynamic routing gRPC service not initialized")] + SuccessRateClientInitializationError, #[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}'")] InvalidSuccessBasedConnectorLabel(String), #[error("unable to find '{field}'")] GenericNotFoundError { field: String }, + #[error("Unable to deserialize from '{from}' to '{to}'")] + DeserializationError { from: String, to: String }, } #[derive(Debug, Clone, thiserror::Error)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a6fc4884e45..e4af8ce4e55 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4585,18 +4585,19 @@ where business_profile .dynamic_routing_algorithm .clone() - .async_map(|dynamic_routing_algorithm| async { + .async_map(|dynamic_routing_algorithm| { routing::perform_success_based_routing( state, connectors.clone(), business_profile, dynamic_routing_algorithm, ) - .await - .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) - .unwrap_or(connectors.clone()) }) .await + .transpose() + .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) + .ok() + .flatten() .unwrap_or(connectors) }; diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 1837fa77261..74fd8d5e376 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1223,11 +1223,12 @@ pub async fn perform_success_based_routing( .clone() .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) .transpose() - .change_context(errors::RoutingError::GenericNotFoundError { - field: "dynamic_routing_algorithm".to_string(), + .change_context(errors::RoutingError::DeserializationError { + from: "dynamic_routing_algorithm".to_string(), + to: "DynamicRoutingAlgorithmRef".to_string(), }) .attach_printable( - "unable to deserialize dynamic_routing_algorithm from business profile to dynamic_routing_algorithm_ref", + "unable to deserialize dynamic_routing_algorithm from business profile to DynamicRoutingAlgorithmRef", )? .unwrap_or_default(); @@ -1246,9 +1247,7 @@ pub async fn perform_success_based_routing( .dynamic_routing .success_rate_client .as_ref() - .ok_or(errors::RoutingError::GenericNotFoundError { - field: "success_rate gRPC client".to_string(), - }) + .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( @@ -1268,9 +1267,9 @@ pub async fn perform_success_based_routing( let success_based_connectors: CalSuccessRateResponse = client .calculate_success_rate( - tenant_business_profile_id.clone(), - success_based_routing_configs.clone(), - routable_connectors.clone(), + tenant_business_profile_id, + success_based_routing_configs, + routable_connectors, ) .await .change_context(errors::RoutingError::SuccessRateCalculationError) diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 815e3807075..ba50bd006fa 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1268,7 +1268,7 @@ pub async fn toggle_success_based_routing( helpers::default_success_based_routing_setup( &state, key_store, - business_profile.clone(), + business_profile, feature_to_enable, merchant_account.get_id().to_owned(), success_based_dynamic_routing_algo_ref, @@ -1281,7 +1281,7 @@ pub async fn toggle_success_based_routing( helpers::default_success_based_routing_setup( &state, key_store, - business_profile.clone(), + business_profile, feature_to_enable, merchant_account.get_id().to_owned(), success_based_dynamic_routing_algo_ref, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index e95bdf45d92..970365b683e 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -16,9 +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::CalSuccessRateResponse, SuccessBasedDynamicRouting, -}; +use external_services::grpc_client::dynamic_routing::SuccessBasedDynamicRouting; #[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] @@ -594,98 +592,6 @@ pub async fn refresh_success_based_routing_cache( config } -/// success based dynamic routing -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] -#[instrument(skip_all)] -pub async fn perform_success_based_routing( - state: &SessionState, - routable_connectors: Vec, - business_profile: &domain::Profile, - dynamic_routing_algorithm: serde_json::Value, -) -> RouterResult> { - let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to deserialize dynamic routing algorithm from business profile to dynamic_routing_algorithm_ref", - )? - .unwrap_or_default(); - - let success_based_algo_ref = success_based_dynamic_routing_algo_ref - .success_based_algorithm - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch success_based_algorithm from dynamic_routing_algorithm", - )?; - - if success_based_algo_ref.enabled_feature - == routing_types::SuccessBasedRoutingFeatures::DynamicConnectorSelection - { - let client = state - .grpc_client - .dynamic_routing - .success_rate_client - .as_ref() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_rate gRPC client not found".to_string(), - })?; - - let success_based_routing_configs = - fetch_success_based_routing_configs(state, business_profile, dynamic_routing_algorithm) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to retrieve success_rate based dynamic routing configs", - )?; - - let tenant_business_profile_id = format!( - "{}:{}", - state.tenant.redis_key_prefix, - business_profile.get_id().get_string_repr() - ); - - let success_based_connectors: CalSuccessRateResponse = client - .calculate_success_rate( - tenant_business_profile_id.clone(), - success_based_routing_configs.clone(), - routable_connectors.clone(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to calculate/fetch success rate from dynamic routing service", - )?; - - let mut connectors = Vec::with_capacity(success_based_connectors.labels_with_score.len()); - for label_with_score in success_based_connectors.labels_with_score { - let (connector, merchant_connector_id) = label_with_score.label - .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to split connector_name and mca_id from the first connector obtained from dynamic routing service", - )?; - connectors.push(routing_types::RoutableConnectorChoice { - choice_kind: routing_types::RoutableChoiceKind::FullStruct, - connector: common_enums::RoutableConnectors::from_str(connector) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to infer routable_connector from connector")?, - merchant_connector_id: Some( - id_type::MerchantConnectorAccountId::wrap(merchant_connector_id.to_string()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to infer routable_connector from connector")?, - ), - }); - } - Ok(connectors) - } else { - Ok(routable_connectors) - } -} - /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] From 49a3aebe770aa89ada84fb9e75d408eee6f1b1b3 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Fri, 18 Oct 2024 16:47:56 +0530 Subject: [PATCH 14/19] revert development.toml changes --- config/development.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/development.toml b/config/development.toml index 6df98056178..f0e26172514 100644 --- a/config/development.toml +++ b/config/development.toml @@ -752,7 +752,3 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" - -[grpc_client.dynamic_routing_client] -host = "localhost" -port = 7000 From 52aa17e622f5b1467473ac556585657793a8ec63 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Sun, 20 Oct 2024 01:27:13 +0530 Subject: [PATCH 15/19] address comments --- crates/router/src/core/payments.rs | 35 ++++----- .../payments/operations/payment_response.rs | 4 +- crates/router/src/core/payments/routing.rs | 29 ++++---- crates/router/src/core/routing.rs | 12 ++-- crates/router/src/core/routing/helpers.rs | 71 ++++++++++--------- 5 files changed, 77 insertions(+), 74 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e4af8ce4e55..c1bbe2446c0 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4579,40 +4579,35 @@ where .await .change_context(errors::ApiErrorResponse::InternalServerError)?; + let connectors = routing::perform_eligibility_analysis_with_fallback( + &state.clone(), + key_store, + connectors, + &TransactionData::Payment(transaction_data), + eligible_connectors, + business_profile, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed eligibility analysis and fallback")?; + // dynamic success based connector selection #[cfg(all(feature = "v1", feature = "dynamic_routing"))] let connectors = { business_profile .dynamic_routing_algorithm .clone() - .async_map(|dynamic_routing_algorithm| { - routing::perform_success_based_routing( - state, - connectors.clone(), - business_profile, - dynamic_routing_algorithm, - ) + .async_map(|_| { + routing::perform_success_based_routing(state, connectors.clone(), business_profile) }) .await .transpose() - .map_err(|e| logger::error!(dynamic_routing_connector_error=?e)) + .map_err(|e| logger::error!(success_rate_routing_error=?e)) .ok() .flatten() .unwrap_or(connectors) }; - let connectors = routing::perform_eligibility_analysis_with_fallback( - &state.clone(), - key_store, - connectors, - &TransactionData::Payment(transaction_data), - eligible_connectors, - business_profile, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("failed eligibility analysis and fallback")?; - let connector_data = connectors .into_iter() .map(|conn| { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 6a5299db8c4..1d4261645ae 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1713,8 +1713,7 @@ async fn payment_response_update_tracker( #[cfg(all(feature = "v1", feature = "dynamic_routing"))] { - if let Some(dynamic_routing_algorithm) = business_profile.dynamic_routing_algorithm.clone() - { + if let Some(_) = business_profile.dynamic_routing_algorithm.clone() { let state = state.clone(); let business_profile = business_profile.clone(); let payment_attempt = payment_attempt.clone(); @@ -1725,7 +1724,6 @@ async fn payment_response_update_tracker( &payment_attempt, routable_connectors, &business_profile, - dynamic_routing_algorithm, ) .await .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 74fd8d5e376..d4e0e347d5f 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1215,7 +1215,6 @@ pub async fn perform_success_based_routing( state: &SessionState, routable_connectors: Vec, business_profile: &domain::Profile, - dynamic_routing_algorithm: serde_json::Value, ) -> RoutingResult> { let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = business_profile @@ -1224,12 +1223,10 @@ pub async fn perform_success_based_routing( .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) .transpose() .change_context(errors::RoutingError::DeserializationError { - from: "dynamic_routing_algorithm".to_string(), + from: "JSON".to_string(), to: "DynamicRoutingAlgorithmRef".to_string(), }) - .attach_printable( - "unable to deserialize dynamic_routing_algorithm from business profile to DynamicRoutingAlgorithmRef", - )? + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? .unwrap_or_default(); let success_based_algo_ref = success_based_dynamic_routing_algo_ref @@ -1253,16 +1250,23 @@ pub async fn perform_success_based_routing( let success_based_routing_configs = routing::helpers::fetch_success_based_routing_configs( state, business_profile, - dynamic_routing_algorithm, + success_based_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::RoutingError::GenericNotFoundError { + field: "success_based_routing_algorithm_id".to_string(), + }) + .attach_printable( + "success_based_routing_algorithm_id not found in business_profile", + )?, ) .await .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) .attach_printable("unable to fetch success_rate based dynamic routing configs")?; - let tenant_business_profile_id = format!( - "{}:{}", - state.tenant.redis_key_prefix, - business_profile.get_id().get_string_repr() + 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 success_based_connectors: CalSuccessRateResponse = client @@ -1283,7 +1287,7 @@ pub async fn perform_success_based_routing( .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 first connector obtained from dynamic routing service", + "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, @@ -1292,7 +1296,7 @@ pub async fn perform_success_based_routing( from: "String".to_string(), to: "RoutableConnectors".to_string(), }) - .attach_printable("unable to convert routable_connector from connector")?, + .attach_printable("unable to convert String to RoutableConnectors")?, merchant_connector_id: Some( common_utils::id_type::MerchantConnectorAccountId::wrap( merchant_connector_id.to_string(), @@ -1305,6 +1309,7 @@ pub async fn perform_success_based_routing( ), }); } + logger::debug!(success_based_routing_connectors=?connectors); Ok(connectors) } else { Ok(routable_connectors) diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index ba50bd006fa..35c4b8d9621 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1228,7 +1228,8 @@ pub async fn toggle_success_based_routing( if algo_with_timestamp.enabled_feature == feature_to_enable { // algorithm already has the required feature Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Success based routing is already enabled".to_string(), + message: "Success rate based routing is already enabled" + .to_string(), })? } else { // enable the requested feature for the algorithm @@ -1323,11 +1324,8 @@ pub async fn toggle_success_based_routing( cache_entries_to_redact, ) .await - .map_err(|e| { - logger::error!( - "unable to publish into the redact channel for evicting the success based routing config cache {e:?}" - ) - }); + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to publish into the redact channel for evicting the success based routing config cache")?; let record = db .find_routing_algorithm_by_profile_id_algorithm_id( @@ -1363,7 +1361,7 @@ pub async fn toggle_success_based_routing( } } None => Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Algorithm is already inactive".to_string(), + message: "Success rate based routing is already disabled".to_string(), })?, } } diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 970365b683e..2d9e2f1674e 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -598,29 +598,8 @@ pub async fn refresh_success_based_routing_cache( pub async fn fetch_success_based_routing_configs( state: &SessionState, business_profile: &domain::Profile, - dynamic_routing_algorithm: serde_json::Value, + success_based_routing_id: id_type::RoutingId, ) -> RouterResult { - let dynamic_routing_algorithm_ref = dynamic_routing_algorithm - .parse_value::("DynamicRoutingAlgorithmRef") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to parse dynamic_routing_algorithm_ref")?; - - let success_based_routing_id = dynamic_routing_algorithm_ref - .success_based_algorithm - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_based_algorithm not found in dynamic_routing_algorithm_ref" - .to_string(), - })? - .algorithm_id_with_timestamp - .algorithm_id - // error can be possible when the feature is toggled off. - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: format!( - "unable to find algorithm_id in success based algorithm config as the feature is disabled for profile_id: {}", - business_profile.get_id().get_string_repr() - ), - })?; - let key = format!( "{}_{}", business_profile.get_id().get_string_repr(), @@ -662,8 +641,22 @@ pub async fn push_metrics_for_success_based_routing( payment_attempt: &storage::PaymentAttempt, routable_connectors: Vec, business_profile: &domain::Profile, - dynamic_routing_algorithm: serde_json::Value, ) -> RouterResult<()> { + let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("to deserialize DynamicRoutingAlgorithmRef from JSON")? + .unwrap_or_default(); + + let success_based_algo_ref = success_based_dynamic_routing_algo_ref + .success_based_algorithm + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; + let client = state .grpc_client .dynamic_routing @@ -679,16 +672,22 @@ pub async fn push_metrics_for_success_based_routing( }, )?; - let success_based_routing_configs = - fetch_success_based_routing_configs(state, business_profile, dynamic_routing_algorithm) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + let success_based_routing_configs = fetch_success_based_routing_configs( + state, + business_profile, + success_based_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 tenant_business_profile_id = format!( - "{}:{}", - state.tenant.redis_key_prefix, - business_profile.get_id().get_string_repr() + let tenant_business_profile_id = generate_tenant_business_profile_id( + &state.tenant.redis_key_prefix, + business_profile.get_id().get_string_repr(), ); let success_based_connectors = client @@ -881,6 +880,14 @@ fn get_success_based_metrics_outcome_for_payment( } } +/// generates cache key with tenant's redis key prefix and profile_id +pub fn generate_tenant_business_profile_id( + redis_key_prefix: &str, + business_profile_id: &str, +) -> String { + format!("{}:{}", redis_key_prefix, business_profile_id) +} + /// default config setup for success_based_routing #[cfg(feature = "v1")] #[instrument(skip_all)] From 3c5f31c7e54c56ae29bbe6312e5558f304fe6515 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Sun, 20 Oct 2024 02:00:38 +0530 Subject: [PATCH 16/19] remove dynamic_routing from default features --- crates/router/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 8eccb4da00a..5becef839d5 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 = ["dynamic_routing", "kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] +common_default = ["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"] From c3427d6bca265617cba23e00bc0b872c504381c9 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 21 Oct 2024 00:52:22 +0530 Subject: [PATCH 17/19] fix clippy lints --- crates/router/src/core/payments.rs | 20 ++++++++----------- .../payments/operations/payment_response.rs | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c1bbe2446c0..568fcb20902 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4594,18 +4594,14 @@ where // dynamic success based connector selection #[cfg(all(feature = "v1", feature = "dynamic_routing"))] let connectors = { - business_profile - .dynamic_routing_algorithm - .clone() - .async_map(|_| { - routing::perform_success_based_routing(state, connectors.clone(), business_profile) - }) - .await - .transpose() - .map_err(|e| logger::error!(success_rate_routing_error=?e)) - .ok() - .flatten() - .unwrap_or(connectors) + if business_profile.dynamic_routing_algorithm.is_some() { + routing::perform_success_based_routing(state, connectors.clone(), business_profile) + .await + .map_err(|e| logger::error!(success_rate_routing_error=?e)) + .unwrap_or(connectors) + } else { + connectors + } }; let connector_data = connectors diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 1d4261645ae..8a6fad6a60a 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1713,7 +1713,7 @@ async fn payment_response_update_tracker( #[cfg(all(feature = "v1", feature = "dynamic_routing"))] { - if let Some(_) = business_profile.dynamic_routing_algorithm.clone() { + if business_profile.dynamic_routing_algorithm.is_some() { let state = state.clone(); let business_profile = business_profile.clone(); let payment_attempt = payment_attempt.clone(); From 5154745494724f471e678f2cb45a6f374889bd92 Mon Sep 17 00:00:00 2001 From: prajjwalkumar17 Date: Mon, 21 Oct 2024 17:57:16 +0530 Subject: [PATCH 18/19] add const for algorithm naming --- crates/router/src/core/routing/helpers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 2d9e2f1674e..638af9d74c4 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -35,6 +35,7 @@ use crate::{ }; #[cfg(feature = "v1")] use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; +pub const DYNAMIC_ALGORITHM_NAME: &str = "Dynamic routing algorithm"; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them @@ -649,7 +650,7 @@ pub async fn push_metrics_for_success_based_routing( .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("to deserialize DynamicRoutingAlgorithmRef from JSON")? + .attach_printable("Failed to deserialize DynamicRoutingAlgorithmRef from JSON")? .unwrap_or_default(); let success_based_algo_ref = success_based_dynamic_routing_algo_ref @@ -909,7 +910,7 @@ pub async fn default_success_based_routing_setup( algorithm_id: algorithm_id.clone(), profile_id: profile_id.clone(), merchant_id, - name: "Dynamic routing algorithm".to_string(), + name: DYNAMIC_ALGORITHM_NAME.to_string(), description: None, kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, algorithm_data: serde_json::json!(default_success_based_routing_config), From 714ead5b4681e184d0322e7bd915dab08724d364 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:28:58 +0000 Subject: [PATCH 19/19] chore: run formatter --- crates/router/src/core/routing/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 638af9d74c4..43826eb5ca0 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -35,7 +35,7 @@ use crate::{ }; #[cfg(feature = "v1")] use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; -pub const DYNAMIC_ALGORITHM_NAME: &str = "Dynamic routing algorithm"; +pub const DYNAMIC_ALGORITHM_NAME: &str = "Dynamic routing algorithm"; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them