Skip to content

Commit

Permalink
feat(euclid): added routes for profile based fallbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
prajjwalkumar17 committed Nov 8, 2023
1 parent 555b4da commit 83acfcf
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 10 deletions.
6 changes: 6 additions & 0 deletions crates/api_models/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ pub struct RoutingConfigRequest {
pub profile_id: Option<String>,
}

#[derive(Debug, serde::Serialize)]
pub struct ProfileDefaultRoutingConfig {
pub profile_id: String,
pub connectors: Vec<RoutableConnectorChoice>,
}

#[cfg(feature = "business_profile_routing")]
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct RoutingRetrieveQuery {
Expand Down
19 changes: 17 additions & 2 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -916,13 +916,19 @@ pub async fn create_payment_connector(
let mut default_routing_config =
routing_helpers::get_merchant_default_config(&*state.store, merchant_id).await?;

let mut default_routing_config_for_profile = routing_helpers::get_merchant_default_config(
&*state.clone().store,
&profile_id,
)
.await?;

let mca = state
.store
.insert_merchant_connector_account(merchant_connector_account, &key_store)
.await
.to_duplicate_response(
errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
profile_id,
profile_id: profile_id.clone(),
connector_name: req.connector_name.to_string(),
},
)?;
Expand All @@ -939,14 +945,23 @@ pub async fn create_payment_connector(
};

if !default_routing_config.contains(&choice) {
default_routing_config.push(choice);
default_routing_config.push(choice.clone());
routing_helpers::update_merchant_default_config(
&*state.store,
merchant_id,
default_routing_config,
)
.await?;
}
if !default_routing_config_for_profile.contains(&choice.clone()) {
default_routing_config_for_profile.push(choice);
routing_helpers::update_merchant_default_config(
&*state.store,
&profile_id.clone(),
default_routing_config_for_profile,
)
.await?;
}
}

metrics::MCA_CREATE.add(
Expand Down
129 changes: 121 additions & 8 deletions crates/router/src/core/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ 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},
Expand Down Expand Up @@ -111,7 +109,7 @@ 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)
core_utils::validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
.await?;

helpers::validate_connectors_in_routing_config(
Expand Down Expand Up @@ -229,7 +227,7 @@ pub async fn link_routing_config(
.await
.change_context(errors::ApiErrorResponse::ResourceIdNotFound)?;

let business_profile = validate_and_get_business_profile(
let business_profile = care_utils::validate_and_get_business_profile(
db,
Some(&routing_algorithm.profile_id),
&merchant_account.merchant_id,
Expand Down Expand Up @@ -332,7 +330,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,
Expand Down Expand Up @@ -402,7 +400,7 @@ pub async fn unlink_routing_config(
})
.attach_printable("Profile_id not provided")?;
let business_profile =
validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
core_utils::validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
.await?;
match business_profile {
Some(business_profile) => {
Expand Down Expand Up @@ -622,7 +620,7 @@ 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)
core_utils::validate_and_get_business_profile(db, Some(&profile_id), &merchant_account.merchant_id)
.await?
.map(|profile| vec![profile])
.get_required_value("BusinessProfile")
Expand Down Expand Up @@ -711,3 +709,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,
},
))
}
10 changes: 10 additions & 0 deletions crates/router/src/routes/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,16 @@ impl Routing {
web::resource("/{algorithm_id}/activate")
.route(web::post().to(cloud_routing::routing_link_config)),
)
.service(
web::resource("/default/profile/{profile_id}").route(
web::post().to(cloud_routing::routing_update_default_config_for_profile),
),
)
.service(
web::resource("/default/profile").route(
web::get().to(cloud_routing::routing_retrieve_default_config_for_profiles),
),
)
}
}

Expand Down
54 changes: 54 additions & 0 deletions crates/router/src/routes/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,57 @@ pub async fn routing_retrieve_linked_config(
.await
}
}

#[cfg(feature = "olap")]
#[instrument(skip_all)]
pub async fn routing_retrieve_default_config_for_profiles(
state: web::Data<AppState>,
req: HttpRequest,
) -> impl Responder {
oss_api::server_wrap(
Flow::RoutingRetrieveDefaultConfig,
state,
&req,
(),
|state, auth: oss_auth::AuthenticationData, _| {
routing::retrieve_default_routing_config_for_profiles(state, auth.merchant_account)
},
#[cfg(not(feature = "release"))]
auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
#[cfg(feature = "release")]
&auth::JWTAuth,
api_locking::LockAction::NotApplicable,
)
.await
}

#[cfg(feature = "olap")]
#[instrument(skip_all)]
pub async fn routing_update_default_config_for_profile(
state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<String>,
json_payload: web::Json<Vec<routing_types::RoutableConnectorChoice>>,
) -> impl Responder {
oss_api::server_wrap(
Flow::RoutingUpdateDefaultConfig,
state,
&req,
(json_payload.into_inner(), path.into_inner()),
|state, auth: oss_auth::AuthenticationData, (updated_config, profile_id)| {
routing::update_default_routing_config_for_profile(
state,
auth.merchant_account,
updated_config,
profile_id,
)
},
#[cfg(not(feature = "release"))]
auth::auth_type(&oss_auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
#[cfg(feature = "release")]
&auth::JWTAuth,
api_locking::LockAction::NotApplicable,
)
.await
}

0 comments on commit 83acfcf

Please sign in to comment.