mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(routing): success based routing metrics (#5951)
Co-authored-by: Aprabhat19 <amishaprabhat@gmail.com> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com>
This commit is contained in:
@ -259,6 +259,16 @@ pub struct RoutableConnectorChoiceWithStatus {
|
||||
pub routable_connector_choice: RoutableConnectorChoice,
|
||||
pub status: bool,
|
||||
}
|
||||
|
||||
impl RoutableConnectorChoiceWithStatus {
|
||||
pub fn new(routable_connector_choice: RoutableConnectorChoice, status: bool) -> Self {
|
||||
Self {
|
||||
routable_connector_choice,
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
|
||||
@ -3219,6 +3219,28 @@ pub enum DeleteStatus {
|
||||
Redacted,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, strum::Display, Hash,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum SuccessBasedRoutingConclusiveState {
|
||||
// pc: payment connector
|
||||
// sc: success based routing outcome/first connector
|
||||
// status: payment status
|
||||
//
|
||||
// status = success && pc == sc
|
||||
TruePositive,
|
||||
// status = failed && pc == sc
|
||||
FalsePositive,
|
||||
// status = failed && pc != sc
|
||||
TrueNegative,
|
||||
// status = success && pc != sc
|
||||
FalseNegative,
|
||||
// status = processing
|
||||
NonDeterministic,
|
||||
}
|
||||
|
||||
/// Whether 3ds authentication is requested or not
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema)]
|
||||
pub enum External3dsAuthenticationRequest {
|
||||
|
||||
@ -38,7 +38,7 @@ v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain
|
||||
customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"]
|
||||
payment_v2 = ["api_models/payment_v2", "diesel_models/payment_v2", "hyperswitch_domain_models/payment_v2", "storage_impl/payment_v2"]
|
||||
payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"]
|
||||
dynamic_routing = ["external_services/dynamic_routing"]
|
||||
dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing"]
|
||||
|
||||
# Partial Auth
|
||||
# The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request.
|
||||
|
||||
@ -83,6 +83,7 @@ counter_metric!(
|
||||
ROUTING_RETRIEVE_CONFIG_FOR_PROFILE_SUCCESS_RESPONSE,
|
||||
GLOBAL_METER
|
||||
);
|
||||
counter_metric!(DYNAMIC_SUCCESS_BASED_ROUTING, GLOBAL_METER);
|
||||
|
||||
#[cfg(feature = "partial-auth")]
|
||||
counter_metric!(PARTIAL_AUTH_FAILURE, GLOBAL_METER);
|
||||
|
||||
@ -70,6 +70,8 @@ use super::{
|
||||
};
|
||||
#[cfg(feature = "frm")]
|
||||
use crate::core::fraud_check as frm_core;
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
use crate::types::api::convert_connector_data_to_routable_connectors;
|
||||
use crate::{
|
||||
configs::settings::{ApplePayPreDecryptFlow, PaymentMethodTypeTokenFilter},
|
||||
connector::utils::missing_field_err,
|
||||
@ -294,6 +296,11 @@ where
|
||||
};
|
||||
payment_data = match connector_details {
|
||||
ConnectorCallType::PreDetermined(connector) => {
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
let routable_connectors =
|
||||
convert_connector_data_to_routable_connectors(&[connector.clone()])
|
||||
.map_err(|e| logger::error!(routable_connector_error=?e))
|
||||
.unwrap_or_default();
|
||||
let schedule_time = if should_add_task_to_process_tracker {
|
||||
payment_sync::get_sync_process_schedule_time(
|
||||
&*state.store,
|
||||
@ -361,6 +368,10 @@ where
|
||||
&key_store,
|
||||
merchant_account.storage_scheme,
|
||||
&locale,
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
routable_connectors,
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
&business_profile,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -383,6 +394,12 @@ where
|
||||
}
|
||||
|
||||
ConnectorCallType::Retryable(connectors) => {
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
let routable_connectors =
|
||||
convert_connector_data_to_routable_connectors(&connectors)
|
||||
.map_err(|e| logger::error!(routable_connector_error=?e))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut connectors = connectors.into_iter();
|
||||
|
||||
let connector_data = get_connector_data(&mut connectors)?;
|
||||
@ -486,6 +503,10 @@ where
|
||||
&key_store,
|
||||
merchant_account.storage_scheme,
|
||||
&locale,
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
routable_connectors,
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
&business_profile,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@ pub mod payments_incremental_authorization;
|
||||
pub mod tax_calculation;
|
||||
|
||||
use api_models::enums::FrmSuggestion;
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
use api_models::routing::RoutableConnectorChoice;
|
||||
use async_trait::async_trait;
|
||||
use error_stack::{report, ResultExt};
|
||||
use router_env::{instrument, tracing};
|
||||
@ -263,6 +265,10 @@ pub trait PostUpdateTracker<F, D, R: Send>: Send {
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<D>
|
||||
where
|
||||
F: 'b + Send + Sync;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
use api_models::routing::RoutableConnectorChoice;
|
||||
use async_trait::async_trait;
|
||||
use common_enums::AuthorizationStatus;
|
||||
use common_utils::{
|
||||
@ -15,6 +17,8 @@ use storage_impl::DataModelExt;
|
||||
use tracing_futures::Instrument;
|
||||
|
||||
use super::{Operation, OperationSessionSetters, PostUpdateTracker};
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
use crate::core::routing::helpers::push_metrics_for_success_based_routing;
|
||||
use crate::{
|
||||
connector::utils::PaymentResponseRouterData,
|
||||
consts,
|
||||
@ -72,6 +76,10 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b,
|
||||
@ -88,6 +96,10 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -345,6 +357,11 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsIncrementalAu
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
_locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] _routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
_business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -496,6 +513,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -508,6 +529,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@ -552,6 +577,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData>
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -564,6 +593,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData>
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -589,6 +622,11 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SdkPaymentsSessionUpd
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
_storage_scheme: enums::MerchantStorageScheme,
|
||||
_locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] _routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
_business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -659,6 +697,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData>
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -671,6 +713,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData>
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -690,6 +736,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -702,6 +752,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -723,6 +777,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsApproveData>
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -735,6 +793,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsApproveData>
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -754,6 +816,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsRejectData> f
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -766,6 +832,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsRejectData> f
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -791,6 +861,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -808,6 +882,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await?;
|
||||
|
||||
@ -894,6 +972,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::CompleteAuthorizeData
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connector: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>>
|
||||
where
|
||||
F: 'b + Send,
|
||||
@ -906,6 +988,10 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::CompleteAuthorizeData
|
||||
key_store,
|
||||
storage_scheme,
|
||||
locale,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
routable_connector,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
business_profile,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@ -952,6 +1038,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
#[instrument(skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
state: &SessionState,
|
||||
_payment_id: &api::PaymentIdType,
|
||||
@ -960,8 +1047,13 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
storage_scheme: enums::MerchantStorageScheme,
|
||||
locale: &Option<String>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] routable_connectors: Vec<
|
||||
RoutableConnectorChoice,
|
||||
>,
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] business_profile: &domain::Profile,
|
||||
) -> RouterResult<PaymentData<F>> {
|
||||
// Update additional payment data with the payment method response that we received from connector
|
||||
|
||||
let additional_payment_method_data = match payment_data.payment_method_data.clone() {
|
||||
Some(payment_method_data) => match payment_method_data {
|
||||
hyperswitch_domain_models::payment_method_data::PaymentMethodData::Card(_)
|
||||
@ -1616,6 +1708,26 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
|
||||
utils::flatten_join_error(payment_attempt_fut)
|
||||
)?;
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||
{
|
||||
let state = state.clone();
|
||||
let business_profile = business_profile.clone();
|
||||
let payment_attempt = payment_attempt.clone();
|
||||
tokio::spawn(
|
||||
async move {
|
||||
push_metrics_for_success_based_routing(
|
||||
&state,
|
||||
&payment_attempt,
|
||||
routable_connectors,
|
||||
&business_profile,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| logger::error!(dynamic_routing_metrics_error=?e))
|
||||
.ok();
|
||||
}
|
||||
.in_current_span(),
|
||||
);
|
||||
}
|
||||
payment_data.payment_intent = payment_intent;
|
||||
payment_data.payment_attempt = payment_attempt;
|
||||
router_data.payment_method_status.and_then(|status| {
|
||||
|
||||
@ -8,8 +8,12 @@ use api_models::{
|
||||
use diesel_models::routing_algorithm::RoutingAlgorithm;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{mandates, payment_address};
|
||||
#[cfg(feature = "v1")]
|
||||
use router_env::logger;
|
||||
use router_env::metrics::add_attributes;
|
||||
use rustc_hash::FxHashSet;
|
||||
#[cfg(feature = "v1")]
|
||||
use storage_impl::redis::cache;
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
use super::payouts;
|
||||
@ -34,6 +38,7 @@ use crate::{
|
||||
},
|
||||
utils::{self, OptionExt},
|
||||
};
|
||||
|
||||
pub enum TransactionData<'a> {
|
||||
Payment(PaymentsDslInput<'a>),
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1298,6 +1303,7 @@ pub async fn success_based_routing_update_configs(
|
||||
&add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]),
|
||||
);
|
||||
let db = state.store.as_ref();
|
||||
|
||||
let dynamic_routing_algo_to_update = db
|
||||
.find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, &algorithm_id)
|
||||
.await
|
||||
@ -1311,10 +1317,10 @@ pub async fn success_based_routing_update_configs(
|
||||
|
||||
config_to_update.update(request);
|
||||
|
||||
let algorithm_id = common_utils::generate_routing_id_of_default_length();
|
||||
let updated_algorithm_id = common_utils::generate_routing_id_of_default_length();
|
||||
let timestamp = common_utils::date_time::now();
|
||||
let algo = RoutingAlgorithm {
|
||||
algorithm_id,
|
||||
algorithm_id: updated_algorithm_id,
|
||||
profile_id: dynamic_routing_algo_to_update.profile_id,
|
||||
merchant_id: dynamic_routing_algo_to_update.merchant_id,
|
||||
name: dynamic_routing_algo_to_update.name,
|
||||
@ -1331,6 +1337,22 @@ pub async fn success_based_routing_update_configs(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to insert record in routing algorithm table")?;
|
||||
|
||||
// redact cache for success based routing configs
|
||||
let cache_key = format!(
|
||||
"{}_{}",
|
||||
profile_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 redact the success based routing config cache {e:?}"));
|
||||
|
||||
let new_record = record.foreign_into();
|
||||
|
||||
metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE_SUCCESS_RESPONSE.add(
|
||||
|
||||
@ -2,10 +2,21 @@
|
||||
//!
|
||||
//! 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;
|
||||
use common_utils::{ext_traits::Encode, types::keymanager::KeyManagerState};
|
||||
#[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;
|
||||
use error_stack::ResultExt;
|
||||
#[cfg(feature = "dynamic_routing")]
|
||||
use external_services::grpc_client::dynamic_routing::SuccessBasedDynamicRouting;
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
use router_env::{instrument, metrics::add_attributes, tracing};
|
||||
use rustc_hash::FxHashSet;
|
||||
use storage_impl::redis::cache;
|
||||
|
||||
@ -18,6 +29,8 @@ use crate::{
|
||||
types::{domain, storage},
|
||||
utils::StringExt,
|
||||
};
|
||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||
use crate::{core::metrics as core_metrics, routes::metrics};
|
||||
|
||||
/// Provides us with all the configured configs of the Merchant in the ascending time configured
|
||||
/// manner and chooses the first of them
|
||||
@ -273,10 +286,7 @@ pub struct RoutingAlgorithmHelpers<'h> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConnectNameAndMCAIdForProfile<'a>(
|
||||
pub FxHashSet<(
|
||||
&'a String,
|
||||
common_utils::id_type::MerchantConnectorAccountId,
|
||||
)>,
|
||||
pub FxHashSet<(&'a String, id_type::MerchantConnectorAccountId)>,
|
||||
);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a String>);
|
||||
@ -288,7 +298,7 @@ pub struct MerchantConnectorAccounts(pub Vec<MerchantConnectorAccount>);
|
||||
#[cfg(feature = "v2")]
|
||||
impl MerchantConnectorAccounts {
|
||||
pub async fn get_all_mcas(
|
||||
merchant_id: &common_utils::id_type::MerchantId,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
state: &SessionState,
|
||||
) -> RouterResult<Self> {
|
||||
@ -327,7 +337,7 @@ impl MerchantConnectorAccounts {
|
||||
|
||||
pub fn filter_by_profile<'a, T>(
|
||||
&'a self,
|
||||
profile_id: &'a common_utils::id_type::ProfileId,
|
||||
profile_id: &'a id_type::ProfileId,
|
||||
func: impl Fn(&'a MerchantConnectorAccount) -> T,
|
||||
) -> FxHashSet<T>
|
||||
where
|
||||
@ -422,8 +432,8 @@ impl<'h> RoutingAlgorithmHelpers<'h> {
|
||||
pub async fn validate_connectors_in_routing_config(
|
||||
state: &SessionState,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
merchant_id: &common_utils::id_type::MerchantId,
|
||||
profile_id: &common_utils::id_type::ProfileId,
|
||||
merchant_id: &id_type::MerchantId,
|
||||
profile_id: &id_type::ProfileId,
|
||||
routing_algorithm: &routing_types::RoutingAlgorithm,
|
||||
) -> RouterResult<()> {
|
||||
let all_mcas = &*state
|
||||
@ -543,3 +553,331 @@ pub fn get_default_config_key(
|
||||
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<Arc<routing_types::SuccessBasedRoutingConfig>> {
|
||||
cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE
|
||||
.get_val::<Arc<routing_types::SuccessBasedRoutingConfig>>(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<routing_types::SuccessBasedRoutingConfig> {
|
||||
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,
|
||||
) -> RouterResult<routing_types::SuccessBasedRoutingConfig> {
|
||||
let dynamic_routing_algorithm = business_profile.dynamic_routing_algorithm.clone().ok_or(
|
||||
errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "unable to find dynamic_routing_algorithm in business profile".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let dynamic_routing_algorithm_ref = dynamic_routing_algorithm
|
||||
.parse_value::<routing_types::DynamicRoutingAlgorithmRef>("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
|
||||
.ok_or(errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "unable to find algorithm id in success based algorithm config".to_string(),
|
||||
})?;
|
||||
|
||||
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::<routing_types::SuccessBasedRoutingConfig>("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<routing_types::RoutableConnectorChoice>,
|
||||
business_profile: &domain::Profile,
|
||||
) -> 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 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)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("unable to retrieve success_rate 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")?;
|
||||
|
||||
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
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("payment currency not found in payment_attempt")?
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"payment_method",
|
||||
payment_attempt
|
||||
.payment_method
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("payment method not found in payment_attempt")?
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"payment_method_type",
|
||||
payment_attempt
|
||||
.payment_method_type
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("payment method type not found in payment_attempt")?
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"capture_method",
|
||||
payment_attempt
|
||||
.capture_method
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("capture method not found in payment_attempt")?
|
||||
.to_string(),
|
||||
),
|
||||
(
|
||||
"authentication_type",
|
||||
payment_attempt
|
||||
.authentication_type
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("authentication type not found in payment_attempt")?
|
||||
.to_string(),
|
||||
),
|
||||
("payment_status", payment_attempt.status.to_string()),
|
||||
("conclusive_classification", outcome.to_string()),
|
||||
]),
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1009,7 +1009,6 @@ pub async fn toggle_success_based_routing(
|
||||
wrapper.profile_id,
|
||||
)
|
||||
},
|
||||
#[cfg(not(feature = "release"))]
|
||||
auth::auth_type(
|
||||
&auth::HeaderAuth(auth::ApiKeyAuth),
|
||||
&auth::JWTAuthProfileFromRoute {
|
||||
@ -1019,12 +1018,6 @@ pub async fn toggle_success_based_routing(
|
||||
},
|
||||
req.headers(),
|
||||
),
|
||||
#[cfg(feature = "release")]
|
||||
&auth::JWTAuthProfileFromRoute {
|
||||
profile_id: wrapper.profile_id,
|
||||
required_permission: Permission::RoutingWrite,
|
||||
minimum_entity_level: EntityType::Merchant,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
|
||||
@ -38,6 +38,8 @@ pub mod refunds_v2;
|
||||
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
use api_models::routing::{self as api_routing, RoutableConnectorChoice};
|
||||
use common_enums::RoutableConnectors;
|
||||
use error_stack::{report, ResultExt};
|
||||
pub use hyperswitch_domain_models::router_flow_types::{
|
||||
access_token_auth::AccessTokenAuth, mandate_revoke::MandateRevoke,
|
||||
@ -58,6 +60,7 @@ pub use self::{
|
||||
payment_link::*, payment_methods::*, payments::*, poll::*, refunds::*, refunds_v2::*,
|
||||
webhooks::*,
|
||||
};
|
||||
use super::transformers::ForeignTryFrom;
|
||||
use crate::{
|
||||
configs::settings::Connectors,
|
||||
connector,
|
||||
@ -68,7 +71,6 @@ use crate::{
|
||||
services::{connector_integration_interface::ConnectorEnum, ConnectorRedirectResponse},
|
||||
types::{self, api::enums as api_enums},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ConnectorCallType {
|
||||
PreDetermined(ConnectorData),
|
||||
@ -228,6 +230,31 @@ impl SessionConnectorData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_connector_data_to_routable_connectors(
|
||||
connectors: &[ConnectorData],
|
||||
) -> CustomResult<Vec<RoutableConnectorChoice>, common_utils::errors::ValidationError> {
|
||||
connectors
|
||||
.iter()
|
||||
.map(|connector_data| RoutableConnectorChoice::foreign_try_from(connector_data.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<ConnectorData> for RoutableConnectorChoice {
|
||||
type Error = error_stack::Report<common_utils::errors::ValidationError>;
|
||||
fn foreign_try_from(from: ConnectorData) -> Result<Self, Self::Error> {
|
||||
match RoutableConnectors::foreign_try_from(from.connector_name) {
|
||||
Ok(connector) => Ok(Self {
|
||||
choice_kind: api_routing::RoutableChoiceKind::FullStruct,
|
||||
connector,
|
||||
merchant_connector_id: from.merchant_connector_id,
|
||||
}),
|
||||
Err(e) => Err(common_utils::errors::ValidationError::InvalidValue {
|
||||
message: format!("This is not a routable connector: {:?}", e),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Session Surcharge type
|
||||
pub enum SessionSurchargeDetails {
|
||||
/// Surcharge is calculated by hyperswitch
|
||||
|
||||
@ -9,6 +9,7 @@ license.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["olap", "oltp"]
|
||||
dynamic_routing = []
|
||||
oltp = []
|
||||
olap = ["hyperswitch_domain_models/olap"]
|
||||
payouts = ["hyperswitch_domain_models/payouts"]
|
||||
|
||||
@ -72,6 +72,16 @@ pub static PM_FILTERS_CGRAPH_CACHE: Lazy<Cache> = Lazy::new(|| {
|
||||
)
|
||||
});
|
||||
|
||||
/// Dynamic Algorithm Cache
|
||||
pub static SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy<Cache> = Lazy::new(|| {
|
||||
Cache::new(
|
||||
"SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE",
|
||||
CACHE_TTL,
|
||||
CACHE_TTI,
|
||||
Some(MAX_CAPACITY),
|
||||
)
|
||||
});
|
||||
|
||||
/// Trait which defines the behaviour of types that's gonna be stored in Cache
|
||||
pub trait Cacheable: Any + Send + Sync + DynClone {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
@ -91,6 +101,7 @@ pub enum CacheKind<'a> {
|
||||
DecisionManager(Cow<'a, str>),
|
||||
Surcharge(Cow<'a, str>),
|
||||
CGraph(Cow<'a, str>),
|
||||
SuccessBasedDynamicRoutingCache(Cow<'a, str>),
|
||||
PmFiltersCGraph(Cow<'a, str>),
|
||||
All(Cow<'a, str>),
|
||||
}
|
||||
|
||||
@ -6,7 +6,8 @@ use router_env::{logger, tracing::Instrument};
|
||||
|
||||
use crate::redis::cache::{
|
||||
CacheKey, CacheKind, CacheRedact, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE,
|
||||
DECISION_MANAGER_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, SURCHARGE_CACHE,
|
||||
DECISION_MANAGER_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE,
|
||||
SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -137,6 +138,15 @@ impl PubSubInterface for std::sync::Arc<redis_interface::RedisConnectionPool> {
|
||||
.await;
|
||||
key
|
||||
}
|
||||
CacheKind::SuccessBasedDynamicRoutingCache(key) => {
|
||||
SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE
|
||||
.remove(CacheKey {
|
||||
key: key.to_string(),
|
||||
prefix: message.tenant.clone(),
|
||||
})
|
||||
.await;
|
||||
key
|
||||
}
|
||||
CacheKind::Routing(key) => {
|
||||
ROUTING_CACHE
|
||||
.remove(CacheKey {
|
||||
@ -189,6 +199,12 @@ impl PubSubInterface for std::sync::Arc<redis_interface::RedisConnectionPool> {
|
||||
prefix: message.tenant.clone(),
|
||||
})
|
||||
.await;
|
||||
SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE
|
||||
.remove(CacheKey {
|
||||
key: key.to_string(),
|
||||
prefix: message.tenant.clone(),
|
||||
})
|
||||
.await;
|
||||
ROUTING_CACHE
|
||||
.remove(CacheKey {
|
||||
key: key.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user