refactor(core): Refactor fallback routing behaviour in payments for v2 (#5642)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Amisha Prabhat
2024-08-22 15:56:15 +05:30
committed by GitHub
parent 5be0c2bfd2
commit 22743ac370
14 changed files with 368 additions and 241 deletions

View File

@ -7,7 +7,7 @@ use api_models::{
use base64::Engine;
use common_utils::{
date_time,
ext_traits::{AsyncExt, Encode, ValueExt},
ext_traits::{AsyncExt, Encode, OptionExt, ValueExt},
id_type, pii, type_name,
types::keymanager::{self as km_types, KeyManagerState},
};
@ -21,12 +21,6 @@ use regex::Regex;
use router_env::metrics::add_attributes;
use uuid::Uuid;
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
use crate::core::routing;
#[cfg(any(feature = "v1", feature = "v2"))]
use crate::types::transformers::ForeignFrom;
use crate::{
@ -37,8 +31,7 @@ use crate::{
payment_methods::{cards, transformers},
payments::helpers,
pm_auth::helpers::PaymentAuthConnectorDataExt,
routing::helpers as routing_helpers,
utils as core_utils,
routing, utils as core_utils,
},
db::StorageInterface,
routes::{metrics, SessionState},
@ -55,6 +48,7 @@ use crate::{
},
utils,
};
const IBAN_MAX_LENGTH: usize = 34;
const BACS_SORT_CODE_LENGTH: usize = 6;
const BACS_MAX_ACCOUNT_NUMBER_LENGTH: usize = 8;
@ -1871,23 +1865,40 @@ impl<'a> ConnectorTypeAndConnectorName<'a> {
Ok(routable_connector)
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2",))
))]
struct MerchantDefaultConfigUpdate<'a> {
routable_connector: &'a Option<api_enums::RoutableConnectors>,
merchant_connector_id: &'a String,
store: &'a dyn StorageInterface,
merchant_id: &'a id_type::MerchantId,
default_routing_config: &'a Vec<api_models::routing::RoutableConnectorChoice>,
default_routing_config_for_profile: &'a Vec<api_models::routing::RoutableConnectorChoice>,
profile_id: &'a String,
transaction_type: &'a api_enums::TransactionType,
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2",))
))]
impl<'a> MerchantDefaultConfigUpdate<'a> {
async fn update_merchant_default_config(&self) -> RouterResult<()> {
let mut default_routing_config = self.default_routing_config.to_owned();
let mut default_routing_config_for_profile =
self.default_routing_config_for_profile.to_owned();
async fn retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists(
&self,
) -> RouterResult<()> {
let mut default_routing_config = routing::helpers::get_merchant_default_config(
self.store,
self.merchant_id.get_string_repr(),
self.transaction_type,
)
.await?;
let mut default_routing_config_for_profile = routing::helpers::get_merchant_default_config(
self.store,
self.profile_id,
self.transaction_type,
)
.await?;
if let Some(routable_connector_val) = self.routable_connector {
let choice = routing_types::RoutableConnectorChoice {
choice_kind: routing_types::RoutableChoiceKind::FullStruct,
@ -1896,7 +1907,7 @@ impl<'a> MerchantDefaultConfigUpdate<'a> {
};
if !default_routing_config.contains(&choice) {
default_routing_config.push(choice.clone());
routing_helpers::update_merchant_default_config(
routing::helpers::update_merchant_default_config(
self.store,
self.merchant_id.get_string_repr(),
default_routing_config.clone(),
@ -1906,7 +1917,7 @@ impl<'a> MerchantDefaultConfigUpdate<'a> {
}
if !default_routing_config_for_profile.contains(&choice.clone()) {
default_routing_config_for_profile.push(choice);
routing_helpers::update_merchant_default_config(
routing::helpers::update_merchant_default_config(
self.store,
self.profile_id,
default_routing_config_for_profile.clone(),
@ -1918,7 +1929,53 @@ impl<'a> MerchantDefaultConfigUpdate<'a> {
Ok(())
}
}
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2",
))]
struct DefaultFallbackRoutingConfigUpdate<'a> {
routable_connector: &'a Option<api_enums::RoutableConnectors>,
merchant_connector_id: &'a String,
store: &'a dyn StorageInterface,
business_profile: domain::BusinessProfile,
key_store: hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
key_manager_state: &'a KeyManagerState,
}
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
impl<'a> DefaultFallbackRoutingConfigUpdate<'a> {
async fn retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists(
&self,
) -> RouterResult<()> {
let profile_wrapper = BusinessProfileWrapper::new(self.business_profile.clone());
let default_routing_config_for_profile =
&mut profile_wrapper.get_default_fallback_list_of_connector_under_profile()?;
if let Some(routable_connector_val) = self.routable_connector {
let choice = routing_types::RoutableConnectorChoice {
choice_kind: routing_types::RoutableChoiceKind::FullStruct,
connector: *routable_connector_val,
merchant_connector_id: Some(self.merchant_connector_id.clone()),
};
if !default_routing_config_for_profile.contains(&choice.clone()) {
default_routing_config_for_profile.push(choice);
profile_wrapper
.update_default_fallback_routing_of_connectors_under_profile(
self.store,
&default_routing_config_for_profile,
self.key_manager_state,
&self.key_store,
)
.await?
}
}
Ok(())
}
}
#[cfg(any(feature = "v1", feature = "v2", feature = "olap"))]
#[async_trait::async_trait]
trait MerchantConnectorAccountUpdateBridge {
@ -2219,14 +2276,13 @@ trait MerchantConnectorAccountCreateBridge {
key_manager_state: &KeyManagerState,
) -> RouterResult<domain::MerchantConnectorAccount>;
async fn validate_and_get_profile_id(
async fn validate_and_get_business_profile(
self,
merchant_account: &domain::MerchantAccount,
db: &dyn StorageInterface,
key_manager_state: &KeyManagerState,
key_store: &domain::MerchantKeyStore,
should_validate: bool,
) -> RouterResult<String>;
) -> RouterResult<domain::BusinessProfile>;
}
#[cfg(all(
@ -2360,27 +2416,28 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
})
}
async fn validate_and_get_profile_id(
async fn validate_and_get_business_profile(
self,
merchant_account: &domain::MerchantAccount,
db: &dyn StorageInterface,
key_manager_state: &KeyManagerState,
key_store: &domain::MerchantKeyStore,
should_validate: bool,
) -> RouterResult<String> {
) -> RouterResult<domain::BusinessProfile> {
let profile_id = self.profile_id;
// Check whether this business profile belongs to the merchant
if should_validate {
let _ = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
key_store,
Some(&profile_id),
merchant_account.get_id(),
)
.await?;
}
Ok(profile_id.clone())
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
key_store,
Some(&profile_id),
merchant_account.get_id(),
)
.await?
.get_required_value("BusinessProfile")
.change_context(errors::ApiErrorResponse::BusinessProfileNotFound { id: profile_id })?;
Ok(business_profile)
}
}
@ -2533,28 +2590,31 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
/// If profile_id is not passed, use default profile if available, or
/// If business_details (business_country and business_label) are passed, get the business_profile
/// or return a `MissingRequiredField` error
async fn validate_and_get_profile_id(
async fn validate_and_get_business_profile(
self,
merchant_account: &domain::MerchantAccount,
db: &dyn StorageInterface,
key_manager_state: &KeyManagerState,
key_store: &domain::MerchantKeyStore,
should_validate: bool,
) -> RouterResult<String> {
) -> RouterResult<domain::BusinessProfile> {
match self.profile_id.or(merchant_account.default_profile.clone()) {
Some(profile_id) => {
// Check whether this business profile belongs to the merchant
if should_validate {
let _ = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
key_store,
Some(&profile_id),
merchant_account.get_id(),
)
.await?;
}
Ok(profile_id.clone())
let business_profile = core_utils::validate_and_get_business_profile(
db,
key_manager_state,
key_store,
Some(&profile_id),
merchant_account.get_id(),
)
.await?
.get_required_value("BusinessProfile")
.change_context(
errors::ApiErrorResponse::BusinessProfileNotFound { id: profile_id },
)?;
Ok(business_profile)
}
None => match self.business_country.zip(self.business_label) {
Some((business_country, business_label)) => {
@ -2571,7 +2631,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
errors::ApiErrorResponse::BusinessProfileNotFound { id: profile_name },
)?;
Ok(business_profile.profile_id)
Ok(business_profile)
}
_ => Err(report!(errors::ApiErrorResponse::MissingRequiredField {
field_name: "profile_id or business_country, business_label"
@ -2627,15 +2687,9 @@ pub async fn create_connector(
&merchant_account,
)?;
let profile_id = req
let business_profile = req
.clone()
.validate_and_get_profile_id(
&merchant_account,
store,
key_manager_state,
&key_store,
true,
)
.validate_and_get_business_profile(&merchant_account, store, key_manager_state, &key_store)
.await?;
let pm_auth_config_validation = PMAuthConfigValidation {
@ -2643,20 +2697,12 @@ pub async fn create_connector(
pm_auth_config: &req.pm_auth_config,
db: store,
merchant_id,
profile_id: &profile_id.clone(),
profile_id: &business_profile.profile_id,
key_store: &key_store,
key_manager_state,
};
pm_auth_config_validation.validate_pm_auth_config().await?;
let business_profile = state
.store
.find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id)
.await
.to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
id: profile_id.to_owned(),
})?;
let connector_type_and_connector_enum = ConnectorTypeAndConnectorName {
connector_type: &req.connector_type,
connector_name: &req.connector_name,
@ -2687,22 +2733,6 @@ pub async fn create_connector(
)
.await?;
let transaction_type = req.get_transaction_type();
let mut default_routing_config = routing_helpers::get_merchant_default_config(
&*state.store,
merchant_id.get_string_repr(),
&transaction_type,
)
.await?;
let mut default_routing_config_for_profile = routing_helpers::get_merchant_default_config(
&*state.clone().store,
&profile_id,
&transaction_type,
)
.await?;
let mca = state
.store
.insert_merchant_connector_account(
@ -2713,27 +2743,44 @@ pub async fn create_connector(
.await
.to_duplicate_response(
errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
profile_id: profile_id.clone(),
profile_id: business_profile.profile_id.clone(),
connector_label: merchant_connector_account
.connector_label
.unwrap_or_default(),
},
)?;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2",))
))]
//update merchant default config
let merchant_default_config_update = MerchantDefaultConfigUpdate {
routable_connector: &routable_connector,
merchant_connector_id: &mca.get_id(),
store,
merchant_id,
default_routing_config: &mut default_routing_config,
default_routing_config_for_profile: &mut default_routing_config_for_profile,
profile_id: &profile_id,
transaction_type: &transaction_type,
profile_id: &business_profile.profile_id,
transaction_type: &req.get_transaction_type(),
};
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2",
))]
//update merchant default config
let merchant_default_config_update = DefaultFallbackRoutingConfigUpdate {
routable_connector: &routable_connector,
merchant_connector_id: &mca.get_id(),
store,
business_profile,
key_store,
key_manager_state,
};
merchant_default_config_update
.update_merchant_default_config()
.retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists()
.await?;
metrics::MCA_CREATE.add(
@ -4033,31 +4080,23 @@ impl BusinessProfileWrapper {
Ok(())
}
pub fn get_profile_id_and_routing_algorithm_id<F>(
&self,
transaction_data: &routing::TransactionData<'_, F>,
) -> (Option<String>, Option<String>)
pub fn get_routing_algorithm_id<'a, F>(
&'a self,
transaction_data: &'a routing::TransactionData<'_, F>,
) -> Option<String>
where
F: Send + Clone,
{
match transaction_data {
routing::TransactionData::Payment(payment_data) => (
payment_data.payment_intent.profile_id.clone(),
self.profile.routing_algorithm_id.clone(),
),
routing::TransactionData::Payment(_) => self.profile.routing_algorithm_id.clone(),
#[cfg(feature = "payouts")]
routing::TransactionData::Payout(payout_data) => (
Some(payout_data.payout_attempt.profile_id.clone()),
self.profile.payout_routing_algorithm_id.clone(),
),
routing::TransactionData::Payout(_) => self.profile.payout_routing_algorithm_id.clone(),
}
}
pub fn get_default_fallback_list_of_connector_under_profile(
&self,
) -> RouterResult<Vec<routing_types::RoutableConnectorChoice>> {
use common_utils::ext_traits::OptionExt;
use masking::ExposeOptionInterface;
self.profile
.default_fallback_routing
.clone()
@ -4080,7 +4119,7 @@ impl BusinessProfileWrapper {
})
}
pub async fn update_default_routing_for_profile(
pub async fn update_default_fallback_routing_of_connectors_under_profile(
self,
db: &dyn StorageInterface,
updated_config: &Vec<routing_types::RoutableConnectorChoice>,

View File

@ -2435,10 +2435,17 @@ pub async fn list_payment_methods(
)
.await?;
let profile_id = profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Profile id not found".to_string(),
})?;
// filter out payment connectors based on profile_id
let filtered_mcas = helpers::filter_mca_based_on_profile_and_connector_type(
all_mcas.clone(),
profile_id.as_ref(),
&profile_id,
ConnectorType::PaymentProcessor,
);
@ -2447,12 +2454,6 @@ pub async fn list_payment_methods(
let mut response: Vec<ResponsePaymentMethodIntermediate> = vec![];
// Key creation for storing PM_FILTER_CGRAPH
let key = {
let profile_id = profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::ApiErrorResponse::GenericNotFoundError {
message: "Profile id not found".to_string(),
})?;
format!(
"pm_filters_cgraph_{}_{}",
merchant_account.get_id().get_string_repr(),

View File

@ -3523,6 +3523,7 @@ where
.merchant_connector_id
.clone()
.as_ref(),
business_profile.clone(),
)
.await?;
@ -3555,15 +3556,13 @@ where
.attach_printable("Failed execution of straight through routing")?;
if check_eligibility {
let profile_id = payment_data.payment_intent.profile_id.clone();
connectors = routing::perform_eligibility_analysis_with_fallback(
&state.clone(),
key_store,
connectors,
&TransactionData::Payment(payment_data),
eligible_connectors,
profile_id,
business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
@ -3604,15 +3603,13 @@ where
.attach_printable("Failed execution of straight through routing")?;
if check_eligibility {
let profile_id = payment_data.payment_intent.profile_id.clone();
connectors = routing::perform_eligibility_analysis_with_fallback(
&state,
key_store,
connectors,
&TransactionData::Payment(payment_data),
eligible_connectors,
profile_id,
business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
@ -3978,6 +3975,7 @@ where
feature = "routing_v2",
feature = "business_profile_v2"
))]
#[allow(clippy::too_many_arguments)]
pub async fn route_connector_v1<F>(
state: &SessionState,
merchant_account: &domain::MerchantAccount,
@ -3991,14 +3989,14 @@ pub async fn route_connector_v1<F>(
where
F: Send + Clone,
{
let (profile_id, routing_algorithm_id) =
super::admin::BusinessProfileWrapper::new(business_profile.clone())
.get_profile_id_and_routing_algorithm_id(&transaction_data);
let profile_wrapper = super::admin::BusinessProfileWrapper::new(business_profile.clone());
let routing_algorithm_id = profile_wrapper.get_routing_algorithm_id(&transaction_data);
let connectors = routing::perform_static_routing_v1(
state,
merchant_account.get_id(),
routing_algorithm_id,
business_profile,
&transaction_data,
)
.await
@ -4010,7 +4008,7 @@ where
connectors,
&transaction_data,
eligible_connectors,
profile_id,
business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
@ -4079,17 +4077,12 @@ pub async fn route_connector_v1<F>(
where
F: Send + Clone,
{
let (profile_id, routing_algorithm_id) = {
let (profile_id, routing_algorithm) = match &transaction_data {
TransactionData::Payment(payment_data) => (
payment_data.payment_intent.profile_id.clone(),
business_profile.routing_algorithm.clone(),
),
let routing_algorithm_id = {
let routing_algorithm = match &transaction_data {
TransactionData::Payment(_) => business_profile.routing_algorithm.clone(),
#[cfg(feature = "payouts")]
TransactionData::Payout(payout_data) => (
Some(payout_data.payout_attempt.profile_id.clone()),
business_profile.payout_routing_algorithm.clone(),
),
TransactionData::Payout(_) => business_profile.payout_routing_algorithm.clone(),
};
let algorithm_ref = routing_algorithm
@ -4098,13 +4091,14 @@ where
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not decode merchant routing algorithm ref")?
.unwrap_or_default();
(profile_id, algorithm_ref.algorithm_id)
algorithm_ref.algorithm_id
};
let connectors = routing::perform_static_routing_v1(
state,
merchant_account.get_id(),
routing_algorithm_id,
business_profile,
&transaction_data,
)
.await
@ -4115,7 +4109,7 @@ where
connectors,
&transaction_data,
eligible_connectors,
profile_id,
business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)

View File

@ -45,6 +45,12 @@ use super::{
operations::{BoxedOperation, Operation, PaymentResponse},
CustomerDetails, PaymentData,
};
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
use crate::core::admin as core_admin;
use crate::{
configs::settings::{ConnectorRequestReferenceIdConfig, TempLockerEnableConfig},
connector,
@ -124,15 +130,12 @@ pub fn create_certificate(
pub fn filter_mca_based_on_profile_and_connector_type(
merchant_connector_accounts: Vec<domain::MerchantConnectorAccount>,
profile_id: Option<&String>,
profile_id: &String,
connector_type: ConnectorType,
) -> Vec<domain::MerchantConnectorAccount> {
merchant_connector_accounts
.into_iter()
.filter(|mca| {
profile_id.map_or(true, |id| &mca.profile_id == id)
&& mca.connector_type == connector_type
})
.filter(|mca| &mca.profile_id == profile_id && mca.connector_type == connector_type)
.collect()
}
@ -4269,18 +4272,12 @@ pub async fn get_apple_pay_retryable_connectors<F>(
key_store: &domain::MerchantKeyStore,
pre_routing_connector_data_list: &[api::ConnectorData],
merchant_connector_id: Option<&String>,
business_profile: domain::BusinessProfile,
) -> CustomResult<Option<Vec<api::ConnectorData>>, errors::ApiErrorResponse>
where
F: Send + Clone,
{
let profile_id = &payment_data
.payment_intent
.profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::ApiErrorResponse::MissingRequiredField {
field_name: "profile_id",
})?;
let profile_id = &business_profile.profile_id;
let pre_decided_connector_data_first = pre_routing_connector_data_list
.first()
@ -4318,7 +4315,7 @@ where
let profile_specific_merchant_connector_account_list =
filter_mca_based_on_profile_and_connector_type(
merchant_connector_account_list,
Some(profile_id),
profile_id,
ConnectorType::PaymentProcessor,
);
@ -4349,7 +4346,10 @@ where
}
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2"))
))]
let fallback_connetors_list = crate::core::routing::helpers::get_merchant_default_config(
&*state.clone().store,
profile_id,
@ -4359,6 +4359,16 @@ where
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get merchant default fallback connectors config")?;
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
let fallback_connetors_list = core_admin::BusinessProfileWrapper::new(business_profile)
.get_default_fallback_list_of_connector_under_profile()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to get merchant default fallback connectors config")?;
let mut routing_connector_data_list = Vec::new();
pre_routing_connector_data_list.iter().for_each(|pre_val| {

View File

@ -378,7 +378,7 @@ where
let filtered_connector_accounts = helpers::filter_mca_based_on_profile_and_connector_type(
all_connector_accounts,
Some(&profile_id),
&profile_id,
common_enums::ConnectorType::PaymentProcessor,
);

View File

@ -34,12 +34,18 @@ use rand::{
use rustc_hash::FxHashMap;
use storage_impl::redis::cache::{CacheKey, CGRAPH_CACHE, ROUTING_CACHE};
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
use crate::core::admin;
#[cfg(feature = "payouts")]
use crate::core::payouts;
use crate::{
core::{
errors, errors as oss_errors, payments as payments_oss,
routing::{self, helpers as routing_helpers},
routing::{self},
},
logger,
types::{
@ -75,7 +81,7 @@ pub struct SessionRoutingPmTypeInput<'a> {
routing_algorithm: &'a MerchantAccountRoutingAlgorithm,
backend_input: dsl_inputs::BackendInput,
allowed_connectors: FxHashMap<String, api::GetToken>,
profile_id: Option<String>,
profile_id: String,
}
type RoutingResult<O> = oss_errors::CustomResult<O, errors::RoutingError>;
@ -273,28 +279,31 @@ pub async fn perform_static_routing_v1<F: Clone>(
state: &SessionState,
merchant_id: &common_utils::id_type::MerchantId,
algorithm_id: Option<String>,
business_profile: &domain::BusinessProfile,
transaction_data: &routing::TransactionData<'_, F>,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
let profile_id = match transaction_data {
routing::TransactionData::Payment(payment_data) => payment_data
.payment_intent
.profile_id
.as_ref()
.get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?,
#[cfg(feature = "payouts")]
routing::TransactionData::Payout(payout_data) => &payout_data.payout_attempt.profile_id,
};
let algorithm_id = if let Some(id) = algorithm_id {
id
} else {
let fallback_config = routing_helpers::get_merchant_default_config(
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2"))
))]
let fallback_config = routing::helpers::get_merchant_default_config(
&*state.clone().store,
profile_id,
&business_profile.profile_id,
&api_enums::TransactionType::from(transaction_data),
)
.await
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
let fallback_config = admin::BusinessProfileWrapper::new(business_profile.clone())
.get_default_fallback_list_of_connector_under_profile()
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
return Ok(fallback_config);
};
@ -302,7 +311,7 @@ pub async fn perform_static_routing_v1<F: Clone>(
state,
merchant_id,
&algorithm_id,
Some(profile_id).cloned(),
business_profile.profile_id.clone(),
&api_enums::TransactionType::from(transaction_data),
)
.await?;
@ -333,15 +342,10 @@ async fn ensure_algorithm_cached_v1(
state: &SessionState,
merchant_id: &common_utils::id_type::MerchantId,
algorithm_id: &str,
profile_id: Option<String>,
profile_id: String,
transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<CachedAlgorithm>> {
let key = {
let profile_id = profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?;
match transaction_type {
common_enums::TransactionType::Payment => {
format!(
@ -421,15 +425,12 @@ pub async fn refresh_routing_cache_v1(
state: &SessionState,
key: String,
algorithm_id: &str,
profile_id: Option<String>,
profile_id: String,
) -> RoutingResult<Arc<CachedAlgorithm>> {
let algorithm = {
let algorithm = state
.store
.find_routing_algorithm_by_profile_id_algorithm_id(
&profile_id.unwrap_or_default(),
algorithm_id,
)
.find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, algorithm_id)
.await
.change_context(errors::RoutingError::DslMissingInDb)?;
let algorithm: routing_types::RoutingAlgorithm = algorithm
@ -506,16 +507,12 @@ pub fn perform_volume_split(
pub async fn get_merchant_cgraph<'a>(
state: &SessionState,
key_store: &domain::MerchantKeyStore,
profile_id: Option<String>,
profile_id: String,
transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<euclid_dir::DirValue>>> {
let merchant_id = &key_store.merchant_id;
let key = {
let profile_id = profile_id
.clone()
.get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?;
match transaction_type {
api_enums::TransactionType::Payment => {
format!("cgraph_{}_{}", merchant_id.get_string_repr(), profile_id)
@ -549,7 +546,7 @@ pub async fn refresh_cgraph_cache<'a>(
state: &SessionState,
key_store: &domain::MerchantKeyStore,
key: String,
profile_id: Option<String>,
profile_id: String,
transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Arc<hyperswitch_constraint_graph::ConstraintGraph<euclid_dir::DirValue>>> {
let mut merchant_connector_accounts = state
@ -588,7 +585,7 @@ pub async fn refresh_cgraph_cache<'a>(
let merchant_connector_accounts =
payments_oss::helpers::filter_mca_based_on_profile_and_connector_type(
merchant_connector_accounts,
profile_id.as_ref(),
&profile_id,
connector_type,
);
@ -648,7 +645,7 @@ async fn perform_cgraph_filtering(
chosen: Vec<routing_types::RoutableConnectorChoice>,
backend_input: dsl_inputs::BackendInput,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
profile_id: Option<String>,
profile_id: String,
transaction_type: &api_enums::TransactionType,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
let context = euclid_graph::AnalysisContext::from_dir_values(
@ -692,7 +689,7 @@ pub async fn perform_eligibility_analysis<F: Clone>(
chosen: Vec<routing_types::RoutableConnectorChoice>,
transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
profile_id: Option<String>,
profile_id: String,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
let backend_input = match transaction_data {
routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?,
@ -717,9 +714,13 @@ pub async fn perform_fallback_routing<F: Clone>(
key_store: &domain::MerchantKeyStore,
transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<&Vec<api_enums::RoutableConnectors>>,
profile_id: Option<String>,
business_profile: &domain::BusinessProfile,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
let fallback_config = routing_helpers::get_merchant_default_config(
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2"))
))]
let fallback_config = routing::helpers::get_merchant_default_config(
&*state.store,
match transaction_data {
routing::TransactionData::Payment(payment_data) => payment_data
@ -735,7 +736,14 @@ pub async fn perform_fallback_routing<F: Clone>(
)
.await
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
let fallback_config = admin::BusinessProfileWrapper::new(business_profile.clone())
.get_default_fallback_list_of_connector_under_profile()
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
let backend_input = match transaction_data {
routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?,
#[cfg(feature = "payouts")]
@ -748,7 +756,7 @@ pub async fn perform_fallback_routing<F: Clone>(
fallback_config,
backend_input,
eligible_connectors,
profile_id,
business_profile.profile_id.clone(),
&api_enums::TransactionType::from(transaction_data),
)
.await
@ -760,7 +768,7 @@ pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
chosen: Vec<routing_types::RoutableConnectorChoice>,
transaction_data: &routing::TransactionData<'_, F>,
eligible_connectors: Option<Vec<api_enums::RoutableConnectors>>,
profile_id: Option<String>,
business_profile: &domain::BusinessProfile,
) -> RoutingResult<Vec<routing_types::RoutableConnectorChoice>> {
let mut final_selection = perform_eligibility_analysis(
state,
@ -768,7 +776,7 @@ pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
chosen,
transaction_data,
eligible_connectors.as_ref(),
profile_id.clone(),
business_profile.profile_id.clone(),
)
.await?;
@ -777,7 +785,7 @@ pub async fn perform_eligibility_analysis_with_fallback<F: Clone>(
key_store,
transaction_data,
eligible_connectors.as_ref(),
profile_id,
business_profile,
)
.await;
@ -831,7 +839,7 @@ pub async fn perform_session_flow_routing(
feature = "business_profile_v2"
))]
let routing_algorithm =
MerchantAccountRoutingAlgorithm::V1(business_profile.routing_algorithm_id);
MerchantAccountRoutingAlgorithm::V1(business_profile.routing_algorithm_id.clone());
#[cfg(all(
any(feature = "v1", feature = "v2"),
@ -929,11 +937,14 @@ pub async fn perform_session_flow_routing(
routing_algorithm: &routing_algorithm,
backend_input: backend_input.clone(),
allowed_connectors,
profile_id: session_input.payment_intent.profile_id.clone(),
profile_id: profile_id.clone(),
};
let routable_connector_choice_option =
perform_session_routing_for_pm_type(&session_pm_input, transaction_type).await?;
let routable_connector_choice_option = perform_session_routing_for_pm_type(
&session_pm_input,
transaction_type,
&business_profile,
)
.await?;
if let Some(routable_connector_choice) = routable_connector_choice_option {
let mut session_routing_choice: Vec<routing_types::SessionRoutingChoice> = Vec::new();
@ -964,26 +975,20 @@ pub async fn perform_session_flow_routing(
Ok(result)
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2"))
))]
async fn perform_session_routing_for_pm_type(
session_pm_input: &SessionRoutingPmTypeInput<'_>,
transaction_type: &api_enums::TransactionType,
_business_profile: &domain::BusinessProfile,
) -> RoutingResult<Option<Vec<api_models::routing::RoutableConnectorChoice>>> {
let merchant_id = &session_pm_input.key_store.merchant_id;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "routing_v2", feature = "business_profile_v2"))
))]
let algorithm_id = match session_pm_input.routing_algorithm {
MerchantAccountRoutingAlgorithm::V1(algorithm_ref) => &algorithm_ref.algorithm_id,
};
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
let algorithm_id = match session_pm_input.routing_algorithm {
MerchantAccountRoutingAlgorithm::V1(algorithm_id) => algorithm_id,
};
let chosen_connectors = if let Some(ref algorithm_id) = algorithm_id {
let cached_algorithm = ensure_algorithm_cached_v1(
@ -1008,13 +1013,9 @@ async fn perform_session_routing_for_pm_type(
)?,
}
} else {
routing_helpers::get_merchant_default_config(
routing::helpers::get_merchant_default_config(
&*session_pm_input.state.clone().store,
session_pm_input
.profile_id
.as_ref()
.get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?,
&session_pm_input.profile_id,
transaction_type,
)
.await
@ -1033,13 +1034,9 @@ async fn perform_session_routing_for_pm_type(
.await?;
if final_selection.is_empty() {
let fallback = routing_helpers::get_merchant_default_config(
let fallback = routing::helpers::get_merchant_default_config(
&*session_pm_input.state.clone().store,
session_pm_input
.profile_id
.as_ref()
.get_required_value("profile_id")
.change_context(errors::RoutingError::ProfileIdMissing)?,
&session_pm_input.profile_id,
transaction_type,
)
.await
@ -1064,6 +1061,83 @@ async fn perform_session_routing_for_pm_type(
}
}
#[cfg(all(
feature = "v2",
feature = "routing_v2",
feature = "business_profile_v2"
))]
async fn perform_session_routing_for_pm_type(
session_pm_input: &SessionRoutingPmTypeInput<'_>,
transaction_type: &api_enums::TransactionType,
business_profile: &domain::BusinessProfile,
) -> RoutingResult<Option<Vec<api_models::routing::RoutableConnectorChoice>>> {
let merchant_id = &session_pm_input.key_store.merchant_id;
let MerchantAccountRoutingAlgorithm::V1(algorithm_id) = session_pm_input.routing_algorithm;
let profile_wrapper = admin::BusinessProfileWrapper::new(business_profile.clone());
let chosen_connectors = if let Some(ref algorithm_id) = algorithm_id {
let cached_algorithm = ensure_algorithm_cached_v1(
&session_pm_input.state.clone(),
merchant_id,
algorithm_id,
session_pm_input.profile_id.clone(),
transaction_type,
)
.await?;
match cached_algorithm.as_ref() {
CachedAlgorithm::Single(conn) => vec![(**conn).clone()],
CachedAlgorithm::Priority(plist) => plist.clone(),
CachedAlgorithm::VolumeSplit(splits) => {
perform_volume_split(splits.to_vec(), Some(session_pm_input.attempt_id))
.change_context(errors::RoutingError::ConnectorSelectionFailed)?
}
CachedAlgorithm::Advanced(interpreter) => execute_dsl_and_get_connector_v1(
session_pm_input.backend_input.clone(),
interpreter,
)?,
}
} else {
profile_wrapper
.get_default_fallback_list_of_connector_under_profile()
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?
};
let mut final_selection = perform_cgraph_filtering(
&session_pm_input.state.clone(),
session_pm_input.key_store,
chosen_connectors,
session_pm_input.backend_input.clone(),
None,
session_pm_input.profile_id.clone(),
transaction_type,
)
.await?;
if final_selection.is_empty() {
let fallback = profile_wrapper
.get_default_fallback_list_of_connector_under_profile()
.change_context(errors::RoutingError::FallbackConfigFetchFailed)?;
final_selection = perform_cgraph_filtering(
&session_pm_input.state.clone(),
session_pm_input.key_store,
fallback,
session_pm_input.backend_input.clone(),
None,
session_pm_input.profile_id.clone(),
transaction_type,
)
.await?;
}
if final_selection.is_empty() {
Ok(None)
} else {
Ok(Some(final_selection))
}
}
pub fn make_dsl_input_for_surcharge(
payment_attempt: &oss_storage::PaymentAttempt,
payment_intent: &oss_storage::PaymentIntent,

View File

@ -300,7 +300,7 @@ pub async fn filter_payout_methods(
// Filter MCAs based on profile_id and connector_type
let filtered_mcas = helpers::filter_mca_based_on_profile_and_connector_type(
all_mcas,
Some(&payout.profile_id),
&payout.profile_id,
common_enums::ConnectorType::PayoutProcessor,
);
let address = payout

View File

@ -33,6 +33,7 @@ use crate::{
route_connector_v1, routing, CustomerDetails,
},
routing::TransactionData,
utils as core_utils,
},
db::StorageInterface,
routes::{metrics, SessionState},
@ -745,6 +746,17 @@ pub async fn decide_payout_connector(
return Ok(api::ConnectorCallType::PreDetermined(connector_data));
}
// Validate and get the business_profile from payout_attempt
let business_profile = core_utils::validate_and_get_business_profile(
state.store.as_ref(),
&(state).into(),
key_store,
Some(&payout_attempt.profile_id),
merchant_account.get_id(),
)
.await?
.get_required_value("BusinessProfile")?;
// 2. Check routing algorithm passed in the request
if let Some(routing_algorithm) = request_straight_through {
let (mut connectors, check_eligibility) =
@ -759,7 +771,7 @@ pub async fn decide_payout_connector(
connectors,
&TransactionData::<()>::Payout(payout_data),
eligible_connectors,
Some(payout_attempt.profile_id.clone()),
&business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
@ -807,7 +819,7 @@ pub async fn decide_payout_connector(
connectors,
&TransactionData::<()>::Payout(payout_data),
eligible_connectors,
Some(payout_attempt.profile_id.clone()),
&business_profile,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)

View File

@ -706,7 +706,7 @@ pub async fn update_default_fallback_routing(
},
)?;
profile_wrapper
.update_default_routing_for_profile(
.update_default_fallback_routing_of_connectors_under_profile(
db,
&updated_list_of_connectors,
key_manager_state,
@ -949,7 +949,7 @@ pub async fn retrieve_linked_routing_config(
routing_types::LinkedRoutingConfigRetrieveResponse::ProfileBased(active_algorithms),
))
}
// List all the default fallback algorithms under all the profile under a merchant
pub async fn retrieve_default_routing_config_for_profiles(
state: SessionState,
merchant_account: domain::MerchantAccount,