mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(router): profile specific fallback derivation while routing payments (#2806)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Aprabhat19 <amishaprabhat@gmail.com> Co-authored-by: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com>
This commit is contained in:
@ -13,13 +13,14 @@ use diesel_models::routing_algorithm::RoutingAlgorithm;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
#[cfg(feature = "business_profile_routing")]
|
||||
use crate::core::utils::validate_and_get_business_profile;
|
||||
#[cfg(feature = "business_profile_routing")]
|
||||
use crate::types::transformers::{ForeignInto, ForeignTryInto};
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors::{RouterResponse, StorageErrorExt},
|
||||
core::{
|
||||
errors::{RouterResponse, StorageErrorExt},
|
||||
utils as core_utils,
|
||||
},
|
||||
routes::AppState,
|
||||
types::domain,
|
||||
utils::{self, OptionExt, ValueExt},
|
||||
@ -111,8 +112,12 @@ pub async fn create_routing_config(
|
||||
})
|
||||
.attach_printable("Profile_id not provided")?;
|
||||
|
||||
validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
|
||||
.await?;
|
||||
core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
helpers::validate_connectors_in_routing_config(
|
||||
db,
|
||||
@ -229,7 +234,7 @@ pub async fn link_routing_config(
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::ResourceIdNotFound)?;
|
||||
|
||||
let business_profile = validate_and_get_business_profile(
|
||||
let business_profile = core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&routing_algorithm.profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
@ -332,7 +337,7 @@ pub async fn retrieve_routing_config(
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
|
||||
|
||||
validate_and_get_business_profile(
|
||||
core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&routing_algorithm.profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
@ -401,9 +406,12 @@ pub async fn unlink_routing_config(
|
||||
field_name: "profile_id",
|
||||
})
|
||||
.attach_printable("Profile_id not provided")?;
|
||||
let business_profile =
|
||||
validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
|
||||
.await?;
|
||||
let business_profile = core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await?;
|
||||
match business_profile {
|
||||
Some(business_profile) => {
|
||||
let routing_algo_ref: routing_types::RoutingAlgorithmRef = business_profile
|
||||
@ -622,13 +630,15 @@ pub async fn retrieve_linked_routing_config(
|
||||
#[cfg(feature = "business_profile_routing")]
|
||||
{
|
||||
let business_profiles = if let Some(profile_id) = query_params.profile_id {
|
||||
validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
|
||||
.await?
|
||||
.map(|profile| vec![profile])
|
||||
.get_required_value("BusinessProfile")
|
||||
.change_context(errors::ApiErrorResponse::BusinessProfileNotFound {
|
||||
id: profile_id,
|
||||
})?
|
||||
core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await?
|
||||
.map(|profile| vec![profile])
|
||||
.get_required_value("BusinessProfile")
|
||||
.change_context(errors::ApiErrorResponse::BusinessProfileNotFound { id: profile_id })?
|
||||
} else {
|
||||
db.list_business_profile_by_merchant_id(&merchant_account.merchant_id)
|
||||
.await
|
||||
@ -711,3 +721,118 @@ pub async fn retrieve_linked_routing_config(
|
||||
Ok(service_api::ApplicationResponse::Json(response))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn retrieve_default_routing_config_for_profiles(
|
||||
state: AppState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
) -> RouterResponse<Vec<routing_types::ProfileDefaultRoutingConfig>> {
|
||||
let db = state.store.as_ref();
|
||||
|
||||
let all_profiles = db
|
||||
.list_business_profile_by_merchant_id(&merchant_account.merchant_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)
|
||||
.attach_printable("error retrieving all business profiles for merchant")?;
|
||||
|
||||
let retrieve_config_futures = all_profiles
|
||||
.iter()
|
||||
.map(|prof| helpers::get_merchant_default_config(db, &prof.profile_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let configs = futures::future::join_all(retrieve_config_futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let default_configs = configs
|
||||
.into_iter()
|
||||
.zip(all_profiles.iter().map(|prof| prof.profile_id.clone()))
|
||||
.map(
|
||||
|(config, profile_id)| routing_types::ProfileDefaultRoutingConfig {
|
||||
profile_id,
|
||||
connectors: config,
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(default_configs))
|
||||
}
|
||||
|
||||
pub async fn update_default_routing_config_for_profile(
|
||||
state: AppState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
updated_config: Vec<routing_types::RoutableConnectorChoice>,
|
||||
profile_id: String,
|
||||
) -> RouterResponse<routing_types::ProfileDefaultRoutingConfig> {
|
||||
let db = state.store.as_ref();
|
||||
|
||||
let business_profile = core_utils::validate_and_get_business_profile(
|
||||
db,
|
||||
Some(&profile_id),
|
||||
&merchant_account.merchant_id,
|
||||
)
|
||||
.await?
|
||||
.get_required_value("BusinessProfile")
|
||||
.change_context(errors::ApiErrorResponse::BusinessProfileNotFound { id: profile_id })?;
|
||||
let default_config =
|
||||
helpers::get_merchant_default_config(db, &business_profile.profile_id).await?;
|
||||
|
||||
utils::when(default_config.len() != updated_config.len(), || {
|
||||
Err(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "current config and updated config have different lengths".to_string(),
|
||||
})
|
||||
.into_report()
|
||||
})?;
|
||||
|
||||
let existing_set = FxHashSet::from_iter(default_config.iter().map(|c| {
|
||||
(
|
||||
c.connector.to_string(),
|
||||
#[cfg(feature = "connector_choice_mca_id")]
|
||||
c.merchant_connector_id.as_ref(),
|
||||
#[cfg(not(feature = "connector_choice_mca_id"))]
|
||||
c.sub_label.as_ref(),
|
||||
)
|
||||
}));
|
||||
|
||||
let updated_set = FxHashSet::from_iter(updated_config.iter().map(|c| {
|
||||
(
|
||||
c.connector.to_string(),
|
||||
#[cfg(feature = "connector_choice_mca_id")]
|
||||
c.merchant_connector_id.as_ref(),
|
||||
#[cfg(not(feature = "connector_choice_mca_id"))]
|
||||
c.sub_label.as_ref(),
|
||||
)
|
||||
}));
|
||||
|
||||
let symmetric_diff = existing_set
|
||||
.symmetric_difference(&updated_set)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
utils::when(!symmetric_diff.is_empty(), || {
|
||||
let error_str = symmetric_diff
|
||||
.into_iter()
|
||||
.map(|(connector, ident)| format!("'{connector}:{ident:?}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: format!("connector mismatch between old and new configs ({error_str})"),
|
||||
})
|
||||
.into_report()
|
||||
})?;
|
||||
|
||||
helpers::update_merchant_default_config(
|
||||
db,
|
||||
&business_profile.profile_id,
|
||||
updated_config.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
routing_types::ProfileDefaultRoutingConfig {
|
||||
profile_id: business_profile.profile_id,
|
||||
connectors: updated_config,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user