Refactor(core): interpolate success_based_routing config params with their specific values (#6448)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prajjwal Kumar
2024-11-08 17:29:44 +05:30
committed by GitHub
parent 21d3071f31
commit d9ce42fd0c
8 changed files with 188 additions and 46 deletions

View File

@ -615,8 +615,11 @@ impl Default for SuccessBasedRoutingConfig {
pub enum SuccessBasedRoutingConfigParams { pub enum SuccessBasedRoutingConfigParams {
PaymentMethod, PaymentMethod,
PaymentMethodType, PaymentMethodType,
Currency,
AuthenticationType, AuthenticationType,
Currency,
Country,
CardNetwork,
CardBin,
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]

View File

@ -5,7 +5,6 @@ use std::{fmt::Debug, sync::Arc};
#[cfg(feature = "dynamic_routing")] #[cfg(feature = "dynamic_routing")]
use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy}; use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy};
use router_env::logger;
use serde; use serde;
/// Struct contains all the gRPC Clients /// Struct contains all the gRPC Clients
@ -38,8 +37,6 @@ impl GrpcClientSettings {
.await .await
.expect("Failed to establish a connection with the Dynamic Routing Server"); .expect("Failed to establish a connection with the Dynamic Routing Server");
logger::info!("Connection established with gRPC Server");
Arc::new(GrpcClients { Arc::new(GrpcClients {
#[cfg(feature = "dynamic_routing")] #[cfg(feature = "dynamic_routing")]
dynamic_routing: dynamic_routing_connection, dynamic_routing: dynamic_routing_connection,

View File

@ -9,6 +9,7 @@ use error_stack::ResultExt;
use http_body_util::combinators::UnsyncBoxBody; use http_body_util::combinators::UnsyncBoxBody;
use hyper::body::Bytes; use hyper::body::Bytes;
use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::connect::HttpConnector;
use router_env::logger;
use serde; use serde;
use success_rate::{ use success_rate::{
success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig,
@ -80,6 +81,7 @@ impl DynamicRoutingClientConfig {
let success_rate_client = match self { let success_rate_client = match self {
Self::Enabled { host, port } => { Self::Enabled { host, port } => {
let uri = format!("http://{}:{}", host, port).parse::<tonic::transport::Uri>()?; let uri = format!("http://{}:{}", host, port).parse::<tonic::transport::Uri>()?;
logger::info!("Connection established with dynamic routing gRPC Server");
Some(SuccessRateCalculatorClient::with_origin(client, uri)) Some(SuccessRateCalculatorClient::with_origin(client, uri))
} }
Self::Disabled => None, Self::Disabled => None,
@ -98,6 +100,7 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
&self, &self,
id: String, id: String,
success_rate_based_config: SuccessBasedRoutingConfig, success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
label_input: Vec<RoutableConnectorChoice>, label_input: Vec<RoutableConnectorChoice>,
) -> DynamicRoutingResult<CalSuccessRateResponse>; ) -> DynamicRoutingResult<CalSuccessRateResponse>;
/// To update the success rate with the given label /// To update the success rate with the given label
@ -105,6 +108,7 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
&self, &self,
id: String, id: String,
success_rate_based_config: SuccessBasedRoutingConfig, success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
response: Vec<RoutableConnectorChoiceWithStatus>, response: Vec<RoutableConnectorChoiceWithStatus>,
) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse>; ) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse>;
} }
@ -115,24 +119,9 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
&self, &self,
id: String, id: String,
success_rate_based_config: SuccessBasedRoutingConfig, success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
label_input: Vec<RoutableConnectorChoice>, label_input: Vec<RoutableConnectorChoice>,
) -> DynamicRoutingResult<CalSuccessRateResponse> { ) -> DynamicRoutingResult<CalSuccessRateResponse> {
let params = success_rate_based_config
.params
.map(|vec| {
vec.into_iter().fold(String::new(), |mut acc_str, params| {
if !acc_str.is_empty() {
acc_str.push(':')
}
acc_str.push_str(params.to_string().as_str());
acc_str
})
})
.get_required_value("params")
.change_context(DynamicRoutingError::MissingRequiredField {
field: "params".to_string(),
})?;
let labels = label_input let labels = label_input
.into_iter() .into_iter()
.map(|conn_choice| conn_choice.to_string()) .map(|conn_choice| conn_choice.to_string())
@ -167,6 +156,7 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
&self, &self,
id: String, id: String,
success_rate_based_config: SuccessBasedRoutingConfig, success_rate_based_config: SuccessBasedRoutingConfig,
params: String,
label_input: Vec<RoutableConnectorChoiceWithStatus>, label_input: Vec<RoutableConnectorChoiceWithStatus>,
) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse> { ) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse> {
let config = success_rate_based_config let config = success_rate_based_config
@ -182,22 +172,6 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
}) })
.collect(); .collect();
let params = success_rate_based_config
.params
.map(|vec| {
vec.into_iter().fold(String::new(), |mut acc_str, params| {
if !acc_str.is_empty() {
acc_str.push(':')
}
acc_str.push_str(params.to_string().as_str());
acc_str
})
})
.get_required_value("params")
.change_context(DynamicRoutingError::MissingRequiredField {
field: "params".to_string(),
})?;
let request = tonic::Request::new(UpdateSuccessRateWindowRequest { let request = tonic::Request::new(UpdateSuccessRateWindowRequest {
id, id,
params, params,

View File

@ -330,6 +330,8 @@ pub enum RoutingError {
MetadataParsingError, MetadataParsingError,
#[error("Unable to retrieve success based routing config")] #[error("Unable to retrieve success based routing config")]
SuccessBasedRoutingConfigError, SuccessBasedRoutingConfigError,
#[error("Params not found in success based routing config")]
SuccessBasedRoutingParamsNotFoundError,
#[error("Unable to calculate success based routing config from dynamic routing service")] #[error("Unable to calculate success based routing config from dynamic routing service")]
SuccessRateCalculationError, SuccessRateCalculationError,
#[error("Success rate client from dynamic routing gRPC service not initialized")] #[error("Success rate client from dynamic routing gRPC service not initialized")]

View File

@ -72,6 +72,8 @@ use super::{
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use crate::core::fraud_check as frm_core; use crate::core::fraud_check as frm_core;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::core::routing::helpers as routing_helpers;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::types::api::convert_connector_data_to_routable_connectors; use crate::types::api::convert_connector_data_to_routable_connectors;
use crate::{ use crate::{
configs::settings::{ApplePayPreDecryptFlow, PaymentMethodTypeTokenFilter}, configs::settings::{ApplePayPreDecryptFlow, PaymentMethodTypeTokenFilter},
@ -5580,10 +5582,46 @@ where
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
let connectors = { let connectors = {
if business_profile.dynamic_routing_algorithm.is_some() { if business_profile.dynamic_routing_algorithm.is_some() {
routing::perform_success_based_routing(state, connectors.clone(), business_profile) let success_based_routing_config_params_interpolator =
.await routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new(
.map_err(|e| logger::error!(success_rate_routing_error=?e)) payment_data.get_payment_attempt().payment_method,
.unwrap_or(connectors) payment_data.get_payment_attempt().payment_method_type,
payment_data.get_payment_attempt().authentication_type,
payment_data.get_payment_attempt().currency,
payment_data
.get_billing_address()
.and_then(|address| address.address)
.and_then(|address| address.country),
payment_data
.get_payment_attempt()
.payment_method_data
.as_ref()
.and_then(|data| data.as_object())
.and_then(|card| card.get("card"))
.and_then(|data| data.as_object())
.and_then(|card| card.get("card_network"))
.and_then(|network| network.as_str())
.map(|network| network.to_string()),
payment_data
.get_payment_attempt()
.payment_method_data
.as_ref()
.and_then(|data| data.as_object())
.and_then(|card| card.get("card"))
.and_then(|data| data.as_object())
.and_then(|card| card.get("card_isin"))
.and_then(|card_isin| card_isin.as_str())
.map(|card_isin| card_isin.to_string()),
);
routing::perform_success_based_routing(
state,
connectors.clone(),
business_profile,
success_based_routing_config_params_interpolator,
)
.await
.map_err(|e| logger::error!(success_rate_routing_error=?e))
.unwrap_or(connectors)
} else { } else {
connectors connectors
} }

View File

@ -21,7 +21,7 @@ use tracing_futures::Instrument;
use super::{Operation, OperationSessionSetters, PostUpdateTracker}; use super::{Operation, OperationSessionSetters, PostUpdateTracker};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::core::routing::helpers::push_metrics_for_success_based_routing; use crate::core::routing::helpers as routing_helpers;
use crate::{ use crate::{
connector::utils::PaymentResponseRouterData, connector::utils::PaymentResponseRouterData,
consts, consts,
@ -1968,13 +1968,44 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
let state = state.clone(); let state = state.clone();
let business_profile = business_profile.clone(); let business_profile = business_profile.clone();
let payment_attempt = payment_attempt.clone(); let payment_attempt = payment_attempt.clone();
let success_based_routing_config_params_interpolator =
routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new(
payment_attempt.payment_method,
payment_attempt.payment_method_type,
payment_attempt.authentication_type,
payment_attempt.currency,
payment_data
.address
.get_payment_billing()
.and_then(|address| address.clone().address)
.and_then(|address| address.country),
payment_attempt
.payment_method_data
.as_ref()
.and_then(|data| data.as_object())
.and_then(|card| card.get("card"))
.and_then(|data| data.as_object())
.and_then(|card| card.get("card_network"))
.and_then(|network| network.as_str())
.map(|network| network.to_string()),
payment_attempt
.payment_method_data
.as_ref()
.and_then(|data| data.as_object())
.and_then(|card| card.get("card"))
.and_then(|data| data.as_object())
.and_then(|card| card.get("card_isin"))
.and_then(|card_isin| card_isin.as_str())
.map(|card_isin| card_isin.to_string()),
);
tokio::spawn( tokio::spawn(
async move { async move {
push_metrics_for_success_based_routing( routing_helpers::push_metrics_with_update_window_for_success_based_routing(
&state, &state,
&payment_attempt, &payment_attempt,
routable_connectors, routable_connectors,
&business_profile, &business_profile,
success_based_routing_config_params_interpolator,
) )
.await .await
.map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e))
@ -1984,6 +2015,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
); );
} }
} }
payment_data.payment_intent = payment_intent; payment_data.payment_intent = payment_intent;
payment_data.payment_attempt = payment_attempt; payment_data.payment_attempt = payment_attempt;
router_data.payment_method_status.and_then(|status| { router_data.payment_method_status.and_then(|status| {

View File

@ -1240,6 +1240,7 @@ pub async fn perform_success_based_routing(
state: &SessionState, state: &SessionState,
routable_connectors: Vec<api_routing::RoutableConnectorChoice>, routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
business_profile: &domain::Profile, business_profile: &domain::Profile,
success_based_routing_config_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator,
) -> RoutingResult<Vec<api_routing::RoutableConnectorChoice>> { ) -> RoutingResult<Vec<api_routing::RoutableConnectorChoice>> {
let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef =
business_profile business_profile
@ -1293,6 +1294,14 @@ pub async fn perform_success_based_routing(
.change_context(errors::RoutingError::SuccessBasedRoutingConfigError) .change_context(errors::RoutingError::SuccessBasedRoutingConfigError)
.attach_printable("unable to fetch success_rate based dynamic routing configs")?; .attach_printable("unable to fetch success_rate based dynamic routing configs")?;
let success_based_routing_config_params = success_based_routing_config_params_interpolator
.get_string_val(
success_based_routing_configs
.params
.as_ref()
.ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)?,
);
let tenant_business_profile_id = routing::helpers::generate_tenant_business_profile_id( let tenant_business_profile_id = routing::helpers::generate_tenant_business_profile_id(
&state.tenant.redis_key_prefix, &state.tenant.redis_key_prefix,
business_profile.get_id().get_string_repr(), business_profile.get_id().get_string_repr(),
@ -1302,6 +1311,7 @@ pub async fn perform_success_based_routing(
.calculate_success_rate( .calculate_success_rate(
tenant_business_profile_id, tenant_business_profile_id,
success_based_routing_configs, success_based_routing_configs,
success_based_routing_config_params,
routable_connectors, routable_connectors,
) )
.await .await

View File

@ -640,11 +640,12 @@ pub async fn fetch_success_based_routing_configs(
/// metrics for success based dynamic routing /// metrics for success based dynamic routing
#[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn push_metrics_for_success_based_routing( pub async fn push_metrics_with_update_window_for_success_based_routing(
state: &SessionState, state: &SessionState,
payment_attempt: &storage::PaymentAttempt, payment_attempt: &storage::PaymentAttempt,
routable_connectors: Vec<routing_types::RoutableConnectorChoice>, routable_connectors: Vec<routing_types::RoutableConnectorChoice>,
business_profile: &domain::Profile, business_profile: &domain::Profile,
success_based_routing_config_params_interpolator: SuccessBasedRoutingConfigParamsInterpolator,
) -> RouterResult<()> { ) -> RouterResult<()> {
let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef =
business_profile business_profile
@ -697,10 +698,20 @@ pub async fn push_metrics_for_success_based_routing(
business_profile.get_id().get_string_repr(), business_profile.get_id().get_string_repr(),
); );
let success_based_routing_config_params = success_based_routing_config_params_interpolator
.get_string_val(
success_based_routing_configs
.params
.as_ref()
.ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)
.change_context(errors::ApiErrorResponse::InternalServerError)?,
);
let success_based_connectors = client let success_based_connectors = client
.calculate_success_rate( .calculate_success_rate(
tenant_business_profile_id.clone(), tenant_business_profile_id.clone(),
success_based_routing_configs.clone(), success_based_routing_configs.clone(),
success_based_routing_config_params.clone(),
routable_connectors.clone(), routable_connectors.clone(),
) )
.await .await
@ -725,9 +736,10 @@ pub async fn push_metrics_for_success_based_routing(
let (first_success_based_connector, merchant_connector_id) = first_success_based_connector_label let (first_success_based_connector, merchant_connector_id) = first_success_based_connector_label
.split_once(':') .split_once(':')
.ok_or(errors::ApiErrorResponse::InternalServerError) .ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable( .attach_printable(format!(
"unable to split connector_name and mca_id from the first connector obtained from dynamic routing service", "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service",
)?; first_success_based_connector_label
))?;
let outcome = get_success_based_metrics_outcome_for_payment( let outcome = get_success_based_metrics_outcome_for_payment(
&payment_status_attribute, &payment_status_attribute,
@ -802,6 +814,7 @@ pub async fn push_metrics_for_success_based_routing(
.update_success_rate( .update_success_rate(
tenant_business_profile_id, tenant_business_profile_id,
success_based_routing_configs, success_based_routing_configs,
success_based_routing_config_params,
vec![routing_types::RoutableConnectorChoiceWithStatus::new( vec![routing_types::RoutableConnectorChoiceWithStatus::new(
routing_types::RoutableConnectorChoice { routing_types::RoutableConnectorChoice {
choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
@ -956,3 +969,76 @@ pub async fn default_success_based_routing_setup(
); );
Ok(ApplicationResponse::Json(new_record)) Ok(ApplicationResponse::Json(new_record))
} }
pub struct SuccessBasedRoutingConfigParamsInterpolator {
pub payment_method: Option<common_enums::PaymentMethod>,
pub payment_method_type: Option<common_enums::PaymentMethodType>,
pub authentication_type: Option<common_enums::AuthenticationType>,
pub currency: Option<common_enums::Currency>,
pub country: Option<common_enums::CountryAlpha2>,
pub card_network: Option<String>,
pub card_bin: Option<String>,
}
impl SuccessBasedRoutingConfigParamsInterpolator {
pub fn new(
payment_method: Option<common_enums::PaymentMethod>,
payment_method_type: Option<common_enums::PaymentMethodType>,
authentication_type: Option<common_enums::AuthenticationType>,
currency: Option<common_enums::Currency>,
country: Option<common_enums::CountryAlpha2>,
card_network: Option<String>,
card_bin: Option<String>,
) -> Self {
Self {
payment_method,
payment_method_type,
authentication_type,
currency,
country,
card_network,
card_bin,
}
}
pub fn get_string_val(
&self,
params: &Vec<routing_types::SuccessBasedRoutingConfigParams>,
) -> String {
let mut parts: Vec<String> = Vec::new();
for param in params {
let val = match param {
routing_types::SuccessBasedRoutingConfigParams::PaymentMethod => self
.payment_method
.as_ref()
.map_or(String::new(), |pm| pm.to_string()),
routing_types::SuccessBasedRoutingConfigParams::PaymentMethodType => self
.payment_method_type
.as_ref()
.map_or(String::new(), |pmt| pmt.to_string()),
routing_types::SuccessBasedRoutingConfigParams::AuthenticationType => self
.authentication_type
.as_ref()
.map_or(String::new(), |at| at.to_string()),
routing_types::SuccessBasedRoutingConfigParams::Currency => self
.currency
.as_ref()
.map_or(String::new(), |cur| cur.to_string()),
routing_types::SuccessBasedRoutingConfigParams::Country => self
.country
.as_ref()
.map_or(String::new(), |cn| cn.to_string()),
routing_types::SuccessBasedRoutingConfigParams::CardNetwork => {
self.card_network.clone().unwrap_or_default()
}
routing_types::SuccessBasedRoutingConfigParams::CardBin => {
self.card_bin.clone().unwrap_or_default()
}
};
if !val.is_empty() {
parts.push(val);
}
}
parts.join(":")
}
}