feat(router): Default configs and toggle api for dynamic routing feature (#5830)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Prajjwal Kumar
2024-09-16 12:23:32 +05:30
committed by GitHub
parent 4137d7b48a
commit 9f9a414042
26 changed files with 995 additions and 40 deletions

View File

@ -2,12 +2,13 @@ pub mod helpers;
pub mod transformers;
use api_models::{
enums, mandates as mandates_api,
enums, mandates as mandates_api, routing,
routing::{self as routing_types, RoutingRetrieveQuery},
};
use diesel_models::routing_algorithm::RoutingAlgorithm;
use error_stack::ResultExt;
use hyperswitch_domain_models::{mandates, payment_address};
use router_env::metrics::add_attributes;
use rustc_hash::FxHashSet;
#[cfg(feature = "payouts")]
@ -411,42 +412,89 @@ pub async fn link_routing_config(
core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;
let mut routing_ref: routing_types::RoutingAlgorithmRef = business_profile
.routing_algorithm
.clone()
.map(|val| val.parse_value("RoutingAlgorithmRef"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to deserialize routing algorithm ref from merchant account")?
.unwrap_or_default();
match routing_algorithm.kind {
diesel_models::enums::RoutingAlgorithmKind::Dynamic => {
let mut dynamic_routing_ref: routing_types::DynamicRoutingAlgorithmRef =
business_profile
.dynamic_routing_algorithm
.clone()
.map(|val| val.parse_value("DynamicRoutingAlgorithmRef"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to deserialize Dynamic routing algorithm ref from business profile",
)?
.unwrap_or_default();
utils::when(routing_algorithm.algorithm_for != *transaction_type, || {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: format!(
"Cannot use {}'s routing algorithm for {} operation",
routing_algorithm.algorithm_for, transaction_type
),
})
})?;
utils::when(
matches!(
dynamic_routing_ref.success_based_algorithm,
Some(routing_types::DynamicAlgorithmWithTimestamp {
algorithm_id: Some(ref id),
timestamp: _
}) if id == &algorithm_id
),
|| {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Algorithm is already active".to_string(),
})
},
)?;
utils::when(
routing_ref.algorithm_id == Some(algorithm_id.clone()),
|| {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Algorithm is already active".to_string(),
})
},
)?;
routing_ref.update_algorithm_id(algorithm_id);
helpers::update_business_profile_active_algorithm_ref(
db,
key_manager_state,
&key_store,
business_profile,
routing_ref,
transaction_type,
)
.await?;
dynamic_routing_ref.update_algorithm_id(algorithm_id);
helpers::update_business_profile_active_dynamic_algorithm_ref(
db,
key_manager_state,
&key_store,
business_profile,
dynamic_routing_ref,
)
.await?;
}
diesel_models::enums::RoutingAlgorithmKind::Single
| diesel_models::enums::RoutingAlgorithmKind::Priority
| diesel_models::enums::RoutingAlgorithmKind::Advanced
| diesel_models::enums::RoutingAlgorithmKind::VolumeSplit => {
let mut routing_ref: routing_types::RoutingAlgorithmRef = business_profile
.routing_algorithm
.clone()
.map(|val| val.parse_value("RoutingAlgorithmRef"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to deserialize routing algorithm ref from business profile",
)?
.unwrap_or_default();
utils::when(routing_algorithm.algorithm_for != *transaction_type, || {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: format!(
"Cannot use {}'s routing algorithm for {} operation",
routing_algorithm.algorithm_for, transaction_type
),
})
})?;
utils::when(
routing_ref.algorithm_id == Some(algorithm_id.clone()),
|| {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Algorithm is already active".to_string(),
})
},
)?;
routing_ref.update_algorithm_id(algorithm_id);
helpers::update_business_profile_active_algorithm_ref(
db,
key_manager_state,
&key_store,
business_profile,
routing_ref,
transaction_type,
)
.await?;
}
};
metrics::ROUTING_LINK_CONFIG_SUCCESS_RESPONSE.add(&metrics::CONTEXT, 1, &[]);
Ok(service_api::ApplicationResponse::Json(
@ -1111,3 +1159,192 @@ pub async fn update_default_routing_config_for_profile(
},
))
}
#[cfg(feature = "v1")]
pub async fn toggle_success_based_routing(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
status: bool,
profile_id: common_utils::id_type::ProfileId,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
metrics::ROUTING_CREATE_REQUEST_RECEIVED.add(
&metrics::CONTEXT,
1,
&add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]),
);
let db = state.store.as_ref();
let key_manager_state = &(&state).into();
let business_profile: domain::BusinessProfile = 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.get_string_repr().to_owned(),
})?;
let mut success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef =
business_profile
.dynamic_routing_algorithm
.clone()
.map(|val| val.parse_value("DynamicRoutingAlgorithmRef"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to deserialize dynamic routing algorithm ref from business profile",
)?
.unwrap_or_default();
if status {
let default_success_based_routing_config = routing::SuccessBasedRoutingConfig::default();
let algorithm_id = common_utils::generate_routing_id_of_default_length();
let timestamp = common_utils::date_time::now();
let algo = RoutingAlgorithm {
algorithm_id: algorithm_id.clone(),
profile_id: business_profile.get_id().to_owned(),
merchant_id: merchant_account.get_id().to_owned(),
name: "Dynamic routing algorithm".to_string(),
description: None,
kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic,
algorithm_data: serde_json::json!(default_success_based_routing_config),
created_at: timestamp,
modified_at: timestamp,
algorithm_for: common_enums::TransactionType::Payment,
};
let record = db
.insert_routing_algorithm(algo)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to insert record in routing algorithm table")?;
success_based_dynamic_routing_algo_ref.update_algorithm_id(algorithm_id);
helpers::update_business_profile_active_dynamic_algorithm_ref(
db,
key_manager_state,
&key_store,
business_profile,
success_based_dynamic_routing_algo_ref,
)
.await?;
let new_record = record.foreign_into();
metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add(
&metrics::CONTEXT,
1,
&add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]),
);
Ok(service_api::ApplicationResponse::Json(new_record))
} else {
let timestamp = common_utils::date_time::now_unix_timestamp();
match success_based_dynamic_routing_algo_ref.success_based_algorithm {
Some(algorithm_ref) => {
if let Some(algorithm_id) = algorithm_ref.algorithm_id {
let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef {
success_based_algorithm: Some(
routing_types::DynamicAlgorithmWithTimestamp {
algorithm_id: None,
timestamp,
},
),
};
let record = db
.find_routing_algorithm_by_profile_id_algorithm_id(
business_profile.get_id(),
&algorithm_id,
)
.await
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
let response = record.foreign_into();
helpers::update_business_profile_active_dynamic_algorithm_ref(
db,
key_manager_state,
&key_store,
business_profile,
dynamic_routing_algorithm,
)
.await?;
metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add(
&metrics::CONTEXT,
1,
&add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]),
);
Ok(service_api::ApplicationResponse::Json(response))
} else {
Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Algorithm is already inactive".to_string(),
})?
}
}
None => Err(errors::ApiErrorResponse::PreconditionFailed {
message: "Algorithm is already inactive".to_string(),
})?,
}
}
}
#[cfg(feature = "v1")]
pub async fn success_based_routing_update_configs(
state: SessionState,
request: routing_types::SuccessBasedRoutingConfig,
algorithm_id: common_utils::id_type::RoutingId,
profile_id: common_utils::id_type::ProfileId,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE.add(
&metrics::CONTEXT,
1,
&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
.to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;
let mut config_to_update: routing::SuccessBasedRoutingConfig = dynamic_routing_algo_to_update
.algorithm_data
.parse_value::<routing::SuccessBasedRoutingConfig>("SuccessBasedRoutingConfig")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to deserialize algorithm data from routing table into SuccessBasedRoutingConfig")?;
config_to_update.update(request);
let algorithm_id = common_utils::generate_routing_id_of_default_length();
let timestamp = common_utils::date_time::now();
let algo = RoutingAlgorithm {
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,
description: dynamic_routing_algo_to_update.description,
kind: dynamic_routing_algo_to_update.kind,
algorithm_data: serde_json::json!(config_to_update),
created_at: timestamp,
modified_at: timestamp,
algorithm_for: dynamic_routing_algo_to_update.algorithm_for,
};
let record = db
.insert_routing_algorithm(algo)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to insert record in routing algorithm table")?;
let new_record = record.foreign_into();
metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE_SUCCESS_RESPONSE.add(
&metrics::CONTEXT,
1,
&add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]),
);
Ok(service_api::ApplicationResponse::Json(new_record))
}