//! Analysis for usage of all helper functions for use case of routing //! //! 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"))] use std::sync::Arc; use api_models::routing as routing_types; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use common_utils::ext_traits::ValueExt; use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState}; use diesel_models::configs; #[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::SuccessBasedDynamicRouting; #[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::logger; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, metrics::add_attributes, tracing}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; #[cfg(feature = "v2")] use crate::types::domain::MerchantConnectorAccount; use crate::{ core::errors::{self, RouterResult}, db::StorageInterface, routes::SessionState, types::{domain, storage}, utils::StringExt, }; #[cfg(feature = "v1")] 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"; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them pub async fn get_merchant_default_config( db: &dyn StorageInterface, // Cannot make this as merchant id domain type because, we are passing profile id also here merchant_id: &str, transaction_type: &storage::enums::TransactionType, ) -> RouterResult> { let key = get_default_config_key(merchant_id, transaction_type); let maybe_config = db.find_config_by_key(&key).await; match maybe_config { Ok(config) => config .config .parse_struct("Vec") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Merchant default config has invalid structure"), Err(e) if e.current_context().is_db_not_found() => { let new_config_conns = Vec::::new(); let serialized = new_config_conns .encode_to_string_of_json() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( "Error while creating and serializing new merchant default config", )?; let new_config = configs::ConfigNew { key, config: serialized, }; db.insert_config(new_config) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error inserting new default routing config into DB")?; Ok(new_config_conns) } Err(e) => Err(e) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error fetching default config for merchant"), } } /// Merchant's already created config can be updated and this change will be reflected /// in DB as well for the particular updated config pub async fn update_merchant_default_config( db: &dyn StorageInterface, merchant_id: &str, connectors: Vec, transaction_type: &storage::enums::TransactionType, ) -> RouterResult<()> { let key = get_default_config_key(merchant_id, transaction_type); let config_str = connectors .encode_to_string_of_json() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to serialize merchant default routing config during update")?; let config_update = configs::ConfigUpdate::Update { config: Some(config_str), }; db.update_config_by_key(&key, config_update) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error updating the default routing config in DB")?; Ok(()) } pub async fn update_merchant_routing_dictionary( db: &dyn StorageInterface, merchant_id: &str, dictionary: routing_types::RoutingDictionary, ) -> RouterResult<()> { let key = get_routing_dictionary_key(merchant_id); let dictionary_str = dictionary .encode_to_string_of_json() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to serialize routing dictionary during update")?; let config_update = configs::ConfigUpdate::Update { config: Some(dictionary_str), }; db.update_config_by_key(&key, config_update) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error saving routing dictionary to DB")?; Ok(()) } /// This will help make one of all configured algorithms to be in active state for a particular /// merchant #[cfg(feature = "v1")] pub async fn update_merchant_active_algorithm_ref( state: &SessionState, key_store: &domain::MerchantKeyStore, config_key: cache::CacheKind<'_>, algorithm_id: routing_types::RoutingAlgorithmRef, ) -> RouterResult<()> { let ref_value = algorithm_id .encode_to_value() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed converting routing algorithm ref to json value")?; let merchant_account_update = storage::MerchantAccountUpdate::Update { merchant_name: None, merchant_details: None, return_url: None, webhook_details: None, sub_merchants_enabled: None, parent_merchant_id: None, enable_payment_response_hash: None, payment_response_hash_key: None, redirect_to_merchant_with_http_post: None, publishable_key: None, locker_id: None, metadata: None, routing_algorithm: Some(ref_value), primary_business_details: None, intent_fulfillment_time: None, frm_routing_algorithm: None, payout_routing_algorithm: None, default_profile: None, payment_link_config: None, pm_collect_link_config: None, }; let db = &*state.store; db.update_specific_fields_in_merchant( &state.into(), &key_store.merchant_id, merchant_account_update, key_store, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update routing algorithm ref in merchant account")?; cache::publish_into_redact_channel(db.get_cache_store().as_ref(), [config_key]) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to invalidate the config cache")?; Ok(()) } #[cfg(feature = "v2")] pub async fn update_merchant_active_algorithm_ref( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _config_key: cache::CacheKind<'_>, _algorithm_id: routing_types::RoutingAlgorithmRef, ) -> RouterResult<()> { // TODO: handle updating the active routing algorithm for v2 in merchant account todo!() } #[cfg(feature = "v1")] pub async fn update_profile_active_algorithm_ref( db: &dyn StorageInterface, key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, current_business_profile: domain::Profile, algorithm_id: routing_types::RoutingAlgorithmRef, transaction_type: &storage::enums::TransactionType, ) -> RouterResult<()> { let ref_val = algorithm_id .encode_to_value() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert routing ref to value")?; let merchant_id = current_business_profile.merchant_id.clone(); let profile_id = current_business_profile.get_id().to_owned(); let routing_cache_key = cache::CacheKind::Routing( format!( "routing_config_{}_{}", merchant_id.get_string_repr(), profile_id.get_string_repr(), ) .into(), ); let (routing_algorithm, payout_routing_algorithm) = match transaction_type { storage::enums::TransactionType::Payment => (Some(ref_val), None), #[cfg(feature = "payouts")] storage::enums::TransactionType::Payout => (None, Some(ref_val)), }; let business_profile_update = domain::ProfileUpdate::RoutingAlgorithmUpdate { routing_algorithm, payout_routing_algorithm, }; db.update_profile_by_profile_id( key_manager_state, merchant_key_store, current_business_profile, business_profile_update, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update routing algorithm ref in business profile")?; cache::publish_into_redact_channel(db.get_cache_store().as_ref(), [routing_cache_key]) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to invalidate routing cache")?; Ok(()) } #[cfg(feature = "v1")] pub async fn update_business_profile_active_dynamic_algorithm_ref( db: &dyn StorageInterface, key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, current_business_profile: domain::Profile, dynamic_routing_algorithm: routing_types::DynamicRoutingAlgorithmRef, ) -> RouterResult<()> { let ref_val = dynamic_routing_algorithm .encode_to_value() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert dynamic routing ref to value")?; let business_profile_update = domain::ProfileUpdate::DynamicRoutingAlgorithmUpdate { dynamic_routing_algorithm: Some(ref_val), }; db.update_profile_by_profile_id( key_manager_state, merchant_key_store, current_business_profile, business_profile_update, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update dynamic routing algorithm ref in business profile")?; Ok(()) } #[cfg(feature = "v2")] #[derive(Clone, Debug)] pub struct RoutingAlgorithmHelpers<'h> { pub name_mca_id_set: ConnectNameAndMCAIdForProfile<'h>, pub name_set: ConnectNameForProfile<'h>, pub routing_algorithm: &'h routing_types::RoutingAlgorithm, } #[derive(Clone, Debug)] pub struct ConnectNameAndMCAIdForProfile<'a>( pub FxHashSet<(&'a String, id_type::MerchantConnectorAccountId)>, ); #[derive(Clone, Debug)] pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a String>); #[cfg(feature = "v2")] #[derive(Clone, Debug)] pub struct MerchantConnectorAccounts(pub Vec); #[cfg(feature = "v2")] impl MerchantConnectorAccounts { pub async fn get_all_mcas( merchant_id: &id_type::MerchantId, key_store: &domain::MerchantKeyStore, state: &SessionState, ) -> RouterResult { let db = &*state.store; let key_manager_state = &state.into(); Ok(Self( db.find_merchant_connector_account_by_merchant_id_and_disabled_list( key_manager_state, merchant_id, true, key_store, ) .await .change_context( errors::ApiErrorResponse::MerchantConnectorAccountNotFound { id: merchant_id.get_string_repr().to_owned(), }, )?, )) } fn filter_and_map<'a, T>( &'a self, filter: impl Fn(&'a MerchantConnectorAccount) -> bool, func: impl Fn(&'a MerchantConnectorAccount) -> T, ) -> FxHashSet where T: std::hash::Hash + Eq, { self.0 .iter() .filter(|mca| filter(mca)) .map(func) .collect::>() } pub fn filter_by_profile<'a, T>( &'a self, profile_id: &'a id_type::ProfileId, func: impl Fn(&'a MerchantConnectorAccount) -> T, ) -> FxHashSet where T: std::hash::Hash + Eq, { self.filter_and_map(|mca| mca.profile_id == *profile_id, func) } } #[cfg(feature = "v2")] impl<'h> RoutingAlgorithmHelpers<'h> { fn connector_choice( &self, choice: &routing_types::RoutableConnectorChoice, ) -> RouterResult<()> { if let Some(ref mca_id) = choice.merchant_connector_id { error_stack::ensure!( self.name_mca_id_set.0.contains(&(&choice.connector.to_string(), mca_id.clone())), errors::ApiErrorResponse::InvalidRequestData { message: format!( "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile", choice.connector, mca_id, ) } ); } else { error_stack::ensure!( self.name_set.0.contains(&choice.connector.to_string()), errors::ApiErrorResponse::InvalidRequestData { message: format!( "connector with name '{}' not found for the given profile", choice.connector, ) } ); }; Ok(()) } pub fn validate_connectors_in_routing_config(&self) -> RouterResult<()> { match self.routing_algorithm { routing_types::RoutingAlgorithm::Single(choice) => { self.connector_choice(choice)?; } routing_types::RoutingAlgorithm::Priority(list) => { for choice in list { self.connector_choice(choice)?; } } routing_types::RoutingAlgorithm::VolumeSplit(splits) => { for split in splits { self.connector_choice(&split.connector)?; } } routing_types::RoutingAlgorithm::Advanced(program) => { let check_connector_selection = |selection: &routing_types::ConnectorSelection| -> RouterResult<()> { match selection { routing_types::ConnectorSelection::VolumeSplit(splits) => { for split in splits { self.connector_choice(&split.connector)?; } } routing_types::ConnectorSelection::Priority(list) => { for choice in list { self.connector_choice(choice)?; } } } Ok(()) }; check_connector_selection(&program.default_selection)?; for rule in &program.rules { check_connector_selection(&rule.connector_selection)?; } } } Ok(()) } } #[cfg(feature = "v1")] pub async fn validate_connectors_in_routing_config( state: &SessionState, key_store: &domain::MerchantKeyStore, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, routing_algorithm: &routing_types::RoutingAlgorithm, ) -> RouterResult<()> { let all_mcas = &*state .store .find_merchant_connector_account_by_merchant_id_and_disabled_list( &state.into(), merchant_id, true, key_store, ) .await .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { id: merchant_id.get_string_repr().to_owned(), })?; let name_mca_id_set = all_mcas .iter() .filter(|mca| mca.profile_id == *profile_id) .map(|mca| (&mca.connector_name, mca.get_id())) .collect::>(); let name_set = all_mcas .iter() .filter(|mca| mca.profile_id == *profile_id) .map(|mca| &mca.connector_name) .collect::>(); let connector_choice = |choice: &routing_types::RoutableConnectorChoice| { if let Some(ref mca_id) = choice.merchant_connector_id { error_stack::ensure!( name_mca_id_set.contains(&(&choice.connector.to_string(), mca_id.clone())), errors::ApiErrorResponse::InvalidRequestData { message: format!( "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile", choice.connector, mca_id, ) } ); } else { error_stack::ensure!( name_set.contains(&choice.connector.to_string()), errors::ApiErrorResponse::InvalidRequestData { message: format!( "connector with name '{}' not found for the given profile", choice.connector, ) } ); } Ok(()) }; match routing_algorithm { routing_types::RoutingAlgorithm::Single(choice) => { connector_choice(choice)?; } routing_types::RoutingAlgorithm::Priority(list) => { for choice in list { connector_choice(choice)?; } } routing_types::RoutingAlgorithm::VolumeSplit(splits) => { for split in splits { connector_choice(&split.connector)?; } } routing_types::RoutingAlgorithm::Advanced(program) => { let check_connector_selection = |selection: &routing_types::ConnectorSelection| -> RouterResult<()> { match selection { routing_types::ConnectorSelection::VolumeSplit(splits) => { for split in splits { connector_choice(&split.connector)?; } } routing_types::ConnectorSelection::Priority(list) => { for choice in list { connector_choice(choice)?; } } } Ok(()) }; check_connector_selection(&program.default_selection)?; for rule in &program.rules { check_connector_selection(&rule.connector_selection)?; } } } Ok(()) } /// Provides the identifier for the specific merchant's routing_dictionary_key #[inline(always)] pub fn get_routing_dictionary_key(merchant_id: &str) -> String { format!("routing_dict_{merchant_id}") } /// Provides the identifier for the specific merchant's default_config #[inline(always)] pub fn get_default_config_key( merchant_id: &str, transaction_type: &storage::enums::TransactionType, ) -> String { match transaction_type { storage::enums::TransactionType::Payment => format!("routing_default_{merchant_id}"), #[cfg(feature = "payouts")] storage::enums::TransactionType::Payout => format!("routing_default_po_{merchant_id}"), } } /// Retrieves cached success_based routing configs specific to tenant and profile #[cfg(all(feature = "v1", feature = "dynamic_routing"))] pub async fn get_cached_success_based_routing_config_for_profile<'a>( state: &SessionState, key: &str, ) -> Option> { cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE .get_val::>(cache::CacheKey { key: key.to_string(), prefix: state.tenant.redis_key_prefix.clone(), }) .await } /// Refreshes the cached success_based routing configs specific to tenant and profile #[cfg(feature = "v1")] pub async fn refresh_success_based_routing_cache( state: &SessionState, key: &str, success_based_routing_config: routing_types::SuccessBasedRoutingConfig, ) -> Arc { let config = Arc::new(success_based_routing_config); cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE .push( cache::CacheKey { key: key.to_string(), prefix: state.tenant.redis_key_prefix.clone(), }, config.clone(), ) .await; config } /// Checked fetch of success based routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn fetch_success_based_routing_configs( state: &SessionState, business_profile: &domain::Profile, success_based_routing_id: id_type::RoutingId, ) -> RouterResult { let key = format!( "{}_{}", business_profile.get_id().get_string_repr(), success_based_routing_id.get_string_repr() ); if let Some(config) = get_cached_success_based_routing_config_for_profile(state, key.as_str()).await { Ok(config.as_ref().clone()) } else { let success_rate_algorithm = state .store .find_routing_algorithm_by_profile_id_algorithm_id( business_profile.get_id(), &success_based_routing_id, ) .await .change_context(errors::ApiErrorResponse::ResourceIdNotFound) .attach_printable("unable to retrieve success_rate_algorithm for profile from db")?; let success_rate_config = success_rate_algorithm .algorithm_data .parse_value::("SuccessBasedRoutingConfig") .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to parse success_based_routing_config struct")?; refresh_success_based_routing_cache(state, key.as_str(), success_rate_config.clone()).await; Ok(success_rate_config) } } /// metrics for success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn push_metrics_for_success_based_routing( state: &SessionState, payment_attempt: &storage::PaymentAttempt, routable_connectors: Vec, business_profile: &domain::Profile, ) -> 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("Failed 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")?; if success_based_algo_ref.enabled_feature != routing_types::SuccessBasedRoutingFeatures::None { 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 payment_connector = &payment_attempt.connector.clone().ok_or( errors::ApiErrorResponse::GenericNotFoundError { message: "unable to derive payment connector from payment attempt".to_string(), }, )?; 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 = generate_tenant_business_profile_id( &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", )?; let payment_status_attribute = get_desired_payment_status_for_success_routing_metrics(&payment_attempt.status); let first_success_based_connector_label = &success_based_connectors .labels_with_score .first() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( "unable to fetch the first connector from list of connectors obtained from dynamic routing service", )? .label .to_string(); let (first_success_based_connector, merchant_connector_id) = first_success_based_connector_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", )?; let outcome = get_success_based_metrics_outcome_for_payment( &payment_status_attribute, payment_connector.to_string(), first_success_based_connector.to_string(), ); core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( &metrics::CONTEXT, 1, &add_attributes([ ("tenant", state.tenant.name.clone()), ( "merchant_id", payment_attempt.merchant_id.get_string_repr().to_string(), ), ( "profile_id", payment_attempt.profile_id.get_string_repr().to_string(), ), ("merchant_connector_id", merchant_connector_id.to_string()), ( "payment_id", payment_attempt.payment_id.get_string_repr().to_string(), ), ( "success_based_routing_connector", first_success_based_connector.to_string(), ), ("payment_connector", payment_connector.to_string()), ( "currency", payment_attempt .currency .map_or_else(|| "None".to_string(), |currency| currency.to_string()), ), ( "payment_method", payment_attempt.payment_method.map_or_else( || "None".to_string(), |payment_method| payment_method.to_string(), ), ), ( "payment_method_type", payment_attempt.payment_method_type.map_or_else( || "None".to_string(), |payment_method_type| payment_method_type.to_string(), ), ), ( "capture_method", payment_attempt.capture_method.map_or_else( || "None".to_string(), |capture_method| capture_method.to_string(), ), ), ( "authentication_type", payment_attempt.authentication_type.map_or_else( || "None".to_string(), |authentication_type| authentication_type.to_string(), ), ), ("payment_status", payment_attempt.status.to_string()), ("conclusive_classification", outcome.to_string()), ]), ); logger::debug!("successfully pushed success_based_routing metrics"); client .update_success_rate( tenant_business_profile_id, success_based_routing_configs, vec![routing_types::RoutableConnectorChoiceWithStatus::new( routing_types::RoutableConnectorChoice { choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, connector: common_enums::RoutableConnectors::from_str( payment_connector.as_str(), ) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to infer routable_connector from connector")?, merchant_connector_id: payment_attempt.merchant_connector_id.clone(), }, payment_status_attribute == common_enums::AttemptStatus::Charged, )], ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( "unable to update success based routing window in dynamic routing service", )?; Ok(()) } else { Ok(()) } } #[cfg(all(feature = "v1", feature = "dynamic_routing"))] fn get_desired_payment_status_for_success_routing_metrics( attempt_status: &common_enums::AttemptStatus, ) -> common_enums::AttemptStatus { match attempt_status { common_enums::AttemptStatus::Charged | common_enums::AttemptStatus::Authorized | common_enums::AttemptStatus::PartialCharged | common_enums::AttemptStatus::PartialChargedAndChargeable => { common_enums::AttemptStatus::Charged } common_enums::AttemptStatus::Failure | common_enums::AttemptStatus::AuthorizationFailed | common_enums::AttemptStatus::AuthenticationFailed | common_enums::AttemptStatus::CaptureFailed | common_enums::AttemptStatus::RouterDeclined => common_enums::AttemptStatus::Failure, common_enums::AttemptStatus::Started | common_enums::AttemptStatus::AuthenticationPending | common_enums::AttemptStatus::AuthenticationSuccessful | common_enums::AttemptStatus::Authorizing | common_enums::AttemptStatus::CodInitiated | common_enums::AttemptStatus::Voided | common_enums::AttemptStatus::VoidInitiated | common_enums::AttemptStatus::CaptureInitiated | common_enums::AttemptStatus::VoidFailed | common_enums::AttemptStatus::AutoRefunded | common_enums::AttemptStatus::Unresolved | common_enums::AttemptStatus::Pending | common_enums::AttemptStatus::PaymentMethodAwaited | common_enums::AttemptStatus::ConfirmationAwaited | common_enums::AttemptStatus::DeviceDataCollectionPending => { common_enums::AttemptStatus::Pending } } } #[cfg(all(feature = "v1", feature = "dynamic_routing"))] fn get_success_based_metrics_outcome_for_payment( payment_status_attribute: &common_enums::AttemptStatus, payment_connector: String, first_success_based_connector: String, ) -> common_enums::SuccessBasedRoutingConclusiveState { match payment_status_attribute { common_enums::AttemptStatus::Charged if *first_success_based_connector == *payment_connector => { common_enums::SuccessBasedRoutingConclusiveState::TruePositive } common_enums::AttemptStatus::Failure if *first_success_based_connector == *payment_connector => { common_enums::SuccessBasedRoutingConclusiveState::FalsePositive } common_enums::AttemptStatus::Failure if *first_success_based_connector != *payment_connector => { common_enums::SuccessBasedRoutingConclusiveState::TrueNegative } common_enums::AttemptStatus::Charged if *first_success_based_connector != *payment_connector => { common_enums::SuccessBasedRoutingConclusiveState::FalseNegative } _ => common_enums::SuccessBasedRoutingConclusiveState::NonDeterministic, } } /// 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)] 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: 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 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)) }