mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(routing): elimination routing switch for toggling the feature (#6568)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -26,6 +26,8 @@ use router_env::{instrument, metrics::add_attributes, tracing}; | ||||
| use rustc_hash::FxHashSet; | ||||
| use storage_impl::redis::cache; | ||||
|  | ||||
| #[cfg(all(feature = "dynamic_routing", feature = "v1"))] | ||||
| use crate::db::errors::StorageErrorExt; | ||||
| #[cfg(feature = "v2")] | ||||
| use crate::types::domain::MerchantConnectorAccount; | ||||
| use crate::{ | ||||
| @ -39,6 +41,8 @@ use crate::{ | ||||
| use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; | ||||
| pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = | ||||
|     "Success rate based dynamic routing algorithm"; | ||||
| pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = | ||||
|     "Elimination based 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 | ||||
| @ -263,9 +267,9 @@ pub async fn update_business_profile_active_dynamic_algorithm_ref( | ||||
|     key_manager_state: &KeyManagerState, | ||||
|     merchant_key_store: &domain::MerchantKeyStore, | ||||
|     current_business_profile: domain::Profile, | ||||
|     dynamic_routing_algorithm: routing_types::DynamicRoutingAlgorithmRef, | ||||
|     dynamic_routing_algorithm_ref: routing_types::DynamicRoutingAlgorithmRef, | ||||
| ) -> RouterResult<()> { | ||||
|     let ref_val = dynamic_routing_algorithm | ||||
|     let ref_val = dynamic_routing_algorithm_ref | ||||
|         .encode_to_value() | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to convert dynamic routing ref to value")?; | ||||
| @ -662,7 +666,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( | ||||
|         .ok_or(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; | ||||
|  | ||||
|     if success_based_algo_ref.enabled_feature != routing_types::SuccessBasedRoutingFeatures::None { | ||||
|     if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { | ||||
|         let client = state | ||||
|             .grpc_client | ||||
|             .dynamic_routing | ||||
| @ -912,34 +916,303 @@ pub fn generate_tenant_business_profile_id( | ||||
|     format!("{}:{}", redis_key_prefix, business_profile_id) | ||||
| } | ||||
|  | ||||
| /// default config setup for success_based_routing | ||||
| #[cfg(feature = "v1")] | ||||
| #[instrument(skip_all)] | ||||
| pub async fn default_success_based_routing_setup( | ||||
| #[cfg(all(feature = "v1", feature = "dynamic_routing"))] | ||||
| pub async fn disable_dynamic_routing_algorithm( | ||||
|     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, | ||||
|     dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, | ||||
|     dynamic_routing_type: routing_types::DynamicRoutingType, | ||||
| ) -> RouterResult<ApplicationResponse<routing_types::RoutingDictionaryRecord>> { | ||||
|     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 timestamp = common_utils::date_time::now_unix_timestamp(); | ||||
|     let profile_id = business_profile | ||||
|         .get_id() | ||||
|         .clone() | ||||
|         .get_string_repr() | ||||
|         .to_owned(); | ||||
|     let (algorithm_id, dynamic_routing_algorithm, cache_entries_to_redact) = | ||||
|         match dynamic_routing_type { | ||||
|             routing_types::DynamicRoutingType::SuccessRateBasedRouting => { | ||||
|                 let Some(algorithm_ref) = dynamic_routing_algo_ref.success_based_algorithm else { | ||||
|                     Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                         message: "Success rate based routing is already disabled".to_string(), | ||||
|                     })? | ||||
|                 }; | ||||
|                 let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id | ||||
|                 else { | ||||
|                     Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                         message: "Algorithm is already inactive".to_string(), | ||||
|                     })? | ||||
|                 }; | ||||
|  | ||||
|                 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(), | ||||
|                     )]; | ||||
|                 ( | ||||
|                     algorithm_id, | ||||
|                     routing_types::DynamicRoutingAlgorithmRef { | ||||
|                         success_based_algorithm: Some(routing_types::SuccessBasedAlgorithm { | ||||
|                             algorithm_id_with_timestamp: | ||||
|                                 routing_types::DynamicAlgorithmWithTimestamp { | ||||
|                                     algorithm_id: None, | ||||
|                                     timestamp, | ||||
|                                 }, | ||||
|                             enabled_feature: routing_types::DynamicRoutingFeatures::None, | ||||
|                         }), | ||||
|                         elimination_routing_algorithm: dynamic_routing_algo_ref | ||||
|                             .elimination_routing_algorithm, | ||||
|                     }, | ||||
|                     cache_entries_to_redact, | ||||
|                 ) | ||||
|             } | ||||
|             routing_types::DynamicRoutingType::EliminationRouting => { | ||||
|                 let Some(algorithm_ref) = dynamic_routing_algo_ref.elimination_routing_algorithm | ||||
|                 else { | ||||
|                     Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                         message: "Elimination routing is already disabled".to_string(), | ||||
|                     })? | ||||
|                 }; | ||||
|                 let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id | ||||
|                 else { | ||||
|                     Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                         message: "Algorithm is already inactive".to_string(), | ||||
|                     })? | ||||
|                 }; | ||||
|                 let cache_key = format!( | ||||
|                     "{}_{}", | ||||
|                     business_profile.get_id().get_string_repr(), | ||||
|                     algorithm_id.get_string_repr() | ||||
|                 ); | ||||
|                 let cache_entries_to_redact = | ||||
|                     vec![cache::CacheKind::EliminationBasedDynamicRoutingCache( | ||||
|                         cache_key.into(), | ||||
|                     )]; | ||||
|                 ( | ||||
|                     algorithm_id, | ||||
|                     routing_types::DynamicRoutingAlgorithmRef { | ||||
|                         success_based_algorithm: dynamic_routing_algo_ref.success_based_algorithm, | ||||
|                         elimination_routing_algorithm: Some( | ||||
|                             routing_types::EliminationRoutingAlgorithm { | ||||
|                                 algorithm_id_with_timestamp: | ||||
|                                     routing_types::DynamicAlgorithmWithTimestamp { | ||||
|                                         algorithm_id: None, | ||||
|                                         timestamp, | ||||
|                                     }, | ||||
|                                 enabled_feature: routing_types::DynamicRoutingFeatures::None, | ||||
|                             }, | ||||
|                         ), | ||||
|                     }, | ||||
|                     cache_entries_to_redact, | ||||
|                 ) | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|     // redact cache for dynamic routing config | ||||
|     let _ = cache::publish_into_redact_channel( | ||||
|         state.store.get_cache_store().as_ref(), | ||||
|         cache_entries_to_redact, | ||||
|     ) | ||||
|     .await | ||||
|     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|     .attach_printable( | ||||
|         "unable to publish into the redact channel for evicting the dynamic routing config cache", | ||||
|     )?; | ||||
|  | ||||
|     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(); | ||||
|     update_business_profile_active_dynamic_algorithm_ref( | ||||
|         db, | ||||
|         key_manager_state, | ||||
|         &key_store, | ||||
|         business_profile, | ||||
|         dynamic_routing_algorithm, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     core_metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( | ||||
|         &metrics::CONTEXT, | ||||
|         1, | ||||
|         &add_attributes([("profile_id", profile_id)]), | ||||
|     ); | ||||
|  | ||||
|     Ok(ApplicationResponse::Json(response)) | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "v1", feature = "dynamic_routing"))] | ||||
| pub async fn enable_dynamic_routing_algorithm( | ||||
|     state: &SessionState, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     business_profile: domain::Profile, | ||||
|     feature_to_enable: routing_types::DynamicRoutingFeatures, | ||||
|     dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, | ||||
|     dynamic_routing_type: routing_types::DynamicRoutingType, | ||||
| ) -> RouterResult<ApplicationResponse<routing_types::RoutingDictionaryRecord>> { | ||||
|     let dynamic_routing = dynamic_routing_algo_ref.clone(); | ||||
|     match dynamic_routing_type { | ||||
|         routing_types::DynamicRoutingType::SuccessRateBasedRouting => { | ||||
|             enable_specific_routing_algorithm( | ||||
|                 state, | ||||
|                 key_store, | ||||
|                 business_profile, | ||||
|                 feature_to_enable, | ||||
|                 dynamic_routing_algo_ref, | ||||
|                 dynamic_routing_type, | ||||
|                 dynamic_routing.success_based_algorithm, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|         routing_types::DynamicRoutingType::EliminationRouting => { | ||||
|             enable_specific_routing_algorithm( | ||||
|                 state, | ||||
|                 key_store, | ||||
|                 business_profile, | ||||
|                 feature_to_enable, | ||||
|                 dynamic_routing_algo_ref, | ||||
|                 dynamic_routing_type, | ||||
|                 dynamic_routing.elimination_routing_algorithm, | ||||
|             ) | ||||
|             .await | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "v1", feature = "dynamic_routing"))] | ||||
| pub async fn enable_specific_routing_algorithm<A>( | ||||
|     state: &SessionState, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     business_profile: domain::Profile, | ||||
|     feature_to_enable: routing_types::DynamicRoutingFeatures, | ||||
|     mut dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, | ||||
|     dynamic_routing_type: routing_types::DynamicRoutingType, | ||||
|     algo_type: Option<A>, | ||||
| ) -> RouterResult<ApplicationResponse<routing_types::RoutingDictionaryRecord>> | ||||
| where | ||||
|     A: routing_types::DynamicRoutingAlgoAccessor + Clone + std::fmt::Debug, | ||||
| { | ||||
|     // Algorithm wasn't created yet | ||||
|     let Some(mut algo_type) = algo_type else { | ||||
|         return default_specific_dynamic_routing_setup( | ||||
|             state, | ||||
|             key_store, | ||||
|             business_profile, | ||||
|             feature_to_enable, | ||||
|             dynamic_routing_algo_ref, | ||||
|             dynamic_routing_type, | ||||
|         ) | ||||
|         .await; | ||||
|     }; | ||||
|  | ||||
|     // Algorithm was in disabled state | ||||
|     let Some(algo_type_algorithm_id) = algo_type | ||||
|         .clone() | ||||
|         .get_algorithm_id_with_timestamp() | ||||
|         .algorithm_id | ||||
|     else { | ||||
|         return default_specific_dynamic_routing_setup( | ||||
|             state, | ||||
|             key_store, | ||||
|             business_profile, | ||||
|             feature_to_enable, | ||||
|             dynamic_routing_algo_ref, | ||||
|             dynamic_routing_type, | ||||
|         ) | ||||
|         .await; | ||||
|     }; | ||||
|     let db = state.store.as_ref(); | ||||
|     let profile_id = business_profile.get_id().clone(); | ||||
|     let algo_type_enabled_features = algo_type.get_enabled_features(); | ||||
|     if *algo_type_enabled_features == feature_to_enable { | ||||
|         // algorithm already has the required feature | ||||
|         return Err(errors::ApiErrorResponse::PreconditionFailed { | ||||
|             message: format!("{} is already enabled", dynamic_routing_type), | ||||
|         } | ||||
|         .into()); | ||||
|     }; | ||||
|     *algo_type_enabled_features = feature_to_enable.clone(); | ||||
|     dynamic_routing_algo_ref | ||||
|         .update_specific_ref(dynamic_routing_type.clone(), feature_to_enable.clone()); | ||||
|     update_business_profile_active_dynamic_algorithm_ref( | ||||
|         db, | ||||
|         &state.into(), | ||||
|         &key_store, | ||||
|         business_profile, | ||||
|         dynamic_routing_algo_ref.clone(), | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     let routing_algorithm = db | ||||
|         .find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, &algo_type_algorithm_id) | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; | ||||
|     let updated_routing_record = routing_algorithm.foreign_into(); | ||||
|     core_metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( | ||||
|         &metrics::CONTEXT, | ||||
|         1, | ||||
|         &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), | ||||
|     ); | ||||
|     Ok(ApplicationResponse::Json(updated_routing_record)) | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| #[instrument(skip_all)] | ||||
| pub async fn default_specific_dynamic_routing_setup( | ||||
|     state: &SessionState, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     business_profile: domain::Profile, | ||||
|     feature_to_enable: routing_types::DynamicRoutingFeatures, | ||||
|     mut dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, | ||||
|     dynamic_routing_type: routing_types::DynamicRoutingType, | ||||
| ) -> RouterResult<ApplicationResponse<routing_types::RoutingDictionaryRecord>> { | ||||
|     let db = state.store.as_ref(); | ||||
|     let key_manager_state = &state.into(); | ||||
|     let profile_id = business_profile.get_id().clone(); | ||||
|     let merchant_id = business_profile.merchant_id.clone(); | ||||
|     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: SUCCESS_BASED_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 algo = match dynamic_routing_type { | ||||
|         routing_types::DynamicRoutingType::SuccessRateBasedRouting => { | ||||
|             let default_success_based_routing_config = | ||||
|                 routing_types::SuccessBasedRoutingConfig::default(); | ||||
|             routing_algorithm::RoutingAlgorithm { | ||||
|                 algorithm_id: algorithm_id.clone(), | ||||
|                 profile_id: profile_id.clone(), | ||||
|                 merchant_id, | ||||
|                 name: SUCCESS_BASED_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, | ||||
|             } | ||||
|         } | ||||
|         routing_types::DynamicRoutingType::EliminationRouting => { | ||||
|             let default_elimination_routing_config = | ||||
|                 routing_types::EliminationRoutingConfig::default(); | ||||
|             routing_algorithm::RoutingAlgorithm { | ||||
|                 algorithm_id: algorithm_id.clone(), | ||||
|                 profile_id: profile_id.clone(), | ||||
|                 merchant_id, | ||||
|                 name: ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM.to_string(), | ||||
|                 description: None, | ||||
|                 kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, | ||||
|                 algorithm_data: serde_json::json!(default_elimination_routing_config), | ||||
|                 created_at: timestamp, | ||||
|                 modified_at: timestamp, | ||||
|                 algorithm_for: common_enums::TransactionType::Payment, | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let record = db | ||||
| @ -948,13 +1221,17 @@ pub async fn default_success_based_routing_setup( | ||||
|         .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); | ||||
|     dynamic_routing_algo_ref.update_algorithm_id( | ||||
|         algorithm_id, | ||||
|         feature_to_enable, | ||||
|         dynamic_routing_type, | ||||
|     ); | ||||
|     update_business_profile_active_dynamic_algorithm_ref( | ||||
|         db, | ||||
|         key_manager_state, | ||||
|         &key_store, | ||||
|         business_profile, | ||||
|         success_based_dynamic_routing_algo, | ||||
|         dynamic_routing_algo_ref, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
| @ -1001,35 +1278,35 @@ impl SuccessBasedRoutingConfigParamsInterpolator { | ||||
|  | ||||
|     pub fn get_string_val( | ||||
|         &self, | ||||
|         params: &Vec<routing_types::SuccessBasedRoutingConfigParams>, | ||||
|         params: &Vec<routing_types::DynamicRoutingConfigParams>, | ||||
|     ) -> String { | ||||
|         let mut parts: Vec<String> = Vec::new(); | ||||
|         for param in params { | ||||
|             let val = match param { | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::PaymentMethod => self | ||||
|                 routing_types::DynamicRoutingConfigParams::PaymentMethod => self | ||||
|                     .payment_method | ||||
|                     .as_ref() | ||||
|                     .map_or(String::new(), |pm| pm.to_string()), | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::PaymentMethodType => self | ||||
|                 routing_types::DynamicRoutingConfigParams::PaymentMethodType => self | ||||
|                     .payment_method_type | ||||
|                     .as_ref() | ||||
|                     .map_or(String::new(), |pmt| pmt.to_string()), | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::AuthenticationType => self | ||||
|                 routing_types::DynamicRoutingConfigParams::AuthenticationType => self | ||||
|                     .authentication_type | ||||
|                     .as_ref() | ||||
|                     .map_or(String::new(), |at| at.to_string()), | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::Currency => self | ||||
|                 routing_types::DynamicRoutingConfigParams::Currency => self | ||||
|                     .currency | ||||
|                     .as_ref() | ||||
|                     .map_or(String::new(), |cur| cur.to_string()), | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::Country => self | ||||
|                 routing_types::DynamicRoutingConfigParams::Country => self | ||||
|                     .country | ||||
|                     .as_ref() | ||||
|                     .map_or(String::new(), |cn| cn.to_string()), | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::CardNetwork => { | ||||
|                 routing_types::DynamicRoutingConfigParams::CardNetwork => { | ||||
|                     self.card_network.clone().unwrap_or_default() | ||||
|                 } | ||||
|                 routing_types::SuccessBasedRoutingConfigParams::CardBin => { | ||||
|                 routing_types::DynamicRoutingConfigParams::CardBin => { | ||||
|                     self.card_bin.clone().unwrap_or_default() | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Prajjwal Kumar
					Prajjwal Kumar