From 3fea00c43ee597c9b786da6636e245cb848cdb97 Mon Sep 17 00:00:00 2001 From: Amisha Prabhat <55580080+Aprabhat19@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:28:19 +0530 Subject: [PATCH] refactor(routing): Refactor api v2 routes for deactivating and retrieving the routing config (#5478) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sanchith Hegde Co-authored-by: Arun Raj M --- crates/api_models/src/routing.rs | 9 ++ crates/router/src/core/routing.rs | 159 ++++++++++++++++++++-------- crates/router/src/routes/app.rs | 22 ++-- crates/router/src/routes/routing.rs | 36 ++++++- 4 files changed, 168 insertions(+), 58 deletions(-) diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 265d045111..aca7bfaebe 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -30,7 +30,16 @@ impl ConnectorSelection { } } } +#[cfg(all(feature = "v2", feature = "routing_v2"))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct RoutingConfigRequest { + pub name: String, + pub description: String, + pub algorithm: RoutingAlgorithm, + pub profile_id: String, +} +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RoutingConfigRequest { pub name: Option, diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 2451156e87..cb7cf344e8 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -1,15 +1,12 @@ pub mod helpers; pub mod transformers; +#[cfg(all(feature = "v2", feature = "routing_v2"))] +use api_models::routing::RoutingConfigRequest; use api_models::{ enums, - routing::{ - self as routing_types, RoutingAlgorithmId, RoutingRetrieveLinkQuery, RoutingRetrieveQuery, - }, + routing::{self as routing_types, RoutingRetrieveLinkQuery, RoutingRetrieveQuery}, }; -#[cfg(all(feature = "v2", feature = "routing_v2"))] -use diesel_models::routing_algorithm::RoutingAlgorithm; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))] use diesel_models::routing_algorithm::RoutingAlgorithm; use error_stack::ResultExt; #[cfg(all(feature = "v2", feature = "routing_v2"))] @@ -19,11 +16,8 @@ use rustc_hash::FxHashSet; use super::payments; #[cfg(feature = "payouts")] use super::payouts; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))] -use crate::consts; -#[cfg(all(feature = "v2", feature = "routing_v2"))] -use crate::{consts, core::errors::RouterResult, db::StorageInterface}; use crate::{ + consts, core::{ errors::{self, RouterResponse, StorageErrorExt}, metrics, utils as core_utils, @@ -36,6 +30,8 @@ use crate::{ }, utils::{self, OptionExt, ValueExt}, }; +#[cfg(all(feature = "v2", feature = "routing_v2"))] +use crate::{core::errors::RouterResult, db::StorageInterface}; pub enum TransactionData<'a, F> where F: Clone, @@ -51,10 +47,8 @@ struct RoutingAlgorithmUpdate(RoutingAlgorithm); #[cfg(all(feature = "v2", feature = "routing_v2"))] impl RoutingAlgorithmUpdate { pub fn create_new_routing_algorithm( - algorithm: routing_types::RoutingAlgorithm, + request: &RoutingConfigRequest, merchant_id: &common_utils::id_type::MerchantId, - name: String, - description: String, profile_id: String, transaction_type: &enums::TransactionType, ) -> Self { @@ -67,10 +61,10 @@ impl RoutingAlgorithmUpdate { algorithm_id, profile_id, merchant_id: merchant_id.clone(), - name, - description: Some(description), - kind: algorithm.get_kind().foreign_into(), - algorithm_data: serde_json::json!(algorithm), + name: request.name.clone(), + description: Some(request.description.clone()), + kind: request.algorithm.get_kind().foreign_into(), + algorithm_data: serde_json::json!(request.algorithm), created_at: timestamp, modified_at: timestamp, algorithm_for: transaction_type.to_owned(), @@ -150,36 +144,15 @@ pub async fn create_routing_config( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - request: routing_types::RoutingConfigRequest, + request: RoutingConfigRequest, transaction_type: &enums::TransactionType, ) -> RouterResponse { metrics::ROUTING_CREATE_REQUEST_RECEIVED.add(&metrics::CONTEXT, 1, &[]); let db = &*state.store; - let name = request - .name - .get_required_value("name") - .change_context(errors::ApiErrorResponse::MissingRequiredField { field_name: "name" }) - .attach_printable("Name of config not given")?; - - let description = request - .description - .get_required_value("description") - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "description", - }) - .attach_printable("Description of config not given")?; - - let algorithm = request - .algorithm - .get_required_value("algorithm") - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "algorithm", - }) - .attach_printable("Algorithm of config not given")?; let business_profile = core_utils::validate_and_get_business_profile( db, - request.profile_id.as_ref(), + Some(&request.profile_id), merchant_account.get_id(), ) .await? @@ -205,16 +178,14 @@ pub async fn create_routing_config( let algorithm_helper = helpers::RoutingAlgorithmHelpers { name_mca_id_set, name_set, - routing_algorithm: &algorithm, + routing_algorithm: &request.algorithm, }; algorithm_helper.validate_connectors_in_routing_config()?; let algo = RoutingAlgorithmUpdate::create_new_routing_algorithm( - algorithm, + &request, merchant_account.get_id(), - name, - description, business_profile.profile_id, transaction_type, ); @@ -327,11 +298,13 @@ pub async fn link_routing_config( let routing_algorithm = RoutingAlgorithmUpdate::fetch_routing_algo(merchant_account.get_id(), &algorithm_id, db) .await?; + utils::when(routing_algorithm.0.profile_id != profile_id, || { Err(errors::ApiErrorResponse::PreconditionFailed { message: "Profile Id is invalid for the routing config".to_string(), }) })?; + let business_profile = core_utils::validate_and_get_business_profile( db, Some(&profile_id), @@ -348,6 +321,7 @@ pub async fn link_routing_config( .unwrap_or_default(); routing_algorithm.update_routing_ref_with_algorithm_id(transaction_type, &mut routing_ref)?; + // TODO move to business profile helpers::update_business_profile_active_algorithm_ref( db, @@ -432,10 +406,41 @@ pub async fn link_routing_config( )) } +#[cfg(all(feature = "v2", feature = "routing_v2"))] pub async fn retrieve_routing_config( state: SessionState, merchant_account: domain::MerchantAccount, - algorithm_id: RoutingAlgorithmId, + algorithm_id: routing_types::RoutingAlgorithmId, +) -> RouterResponse { + metrics::ROUTING_RETRIEVE_CONFIG.add(&metrics::CONTEXT, 1, &[]); + let db = state.store.as_ref(); + + let routing_algorithm = + RoutingAlgorithmUpdate::fetch_routing_algo(merchant_account.get_id(), &algorithm_id.0, db) + .await?; + // TODO: Move to domain types of Business Profile + core_utils::validate_and_get_business_profile( + db, + Some(&routing_algorithm.0.profile_id), + merchant_account.get_id(), + ) + .await? + .get_required_value("BusinessProfile") + .change_context(errors::ApiErrorResponse::ResourceIdNotFound)?; + + let response = routing_types::MerchantRoutingAlgorithm::foreign_try_from(routing_algorithm.0) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to parse routing algorithm")?; + + metrics::ROUTING_RETRIEVE_CONFIG_SUCCESS_RESPONSE.add(&metrics::CONTEXT, 1, &[]); + Ok(service_api::ApplicationResponse::Json(response)) +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))] +pub async fn retrieve_routing_config( + state: SessionState, + merchant_account: domain::MerchantAccount, + algorithm_id: routing_types::RoutingAlgorithmId, ) -> RouterResponse { metrics::ROUTING_RETRIEVE_CONFIG.add(&metrics::CONTEXT, 1, &[]); let db = state.store.as_ref(); @@ -465,6 +470,70 @@ pub async fn retrieve_routing_config( Ok(service_api::ApplicationResponse::Json(response)) } +#[cfg(all(feature = "v2", feature = "routing_v2"))] +pub async fn unlink_routing_config( + state: SessionState, + merchant_account: domain::MerchantAccount, + profile_id: String, + transaction_type: &enums::TransactionType, +) -> RouterResponse { + metrics::ROUTING_UNLINK_CONFIG.add(&metrics::CONTEXT, 1, &[]); + let db = state.store.as_ref(); + + let business_profile = core_utils::validate_and_get_business_profile( + db, + Some(&profile_id), + merchant_account.get_id(), + ) + .await? + .get_required_value("BusinessProfile")?; + + let routing_algo_ref = routing_types::RoutingAlgorithmRef::parse_routing_algorithm( + match transaction_type { + enums::TransactionType::Payment => business_profile.routing_algorithm.clone(), + #[cfg(feature = "payouts")] + enums::TransactionType::Payout => business_profile.payout_routing_algorithm.clone(), + } + .map(Secret::new), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize routing algorithm ref from merchant account")? + .unwrap_or_default(); + + if let Some(algorithm_id) = routing_algo_ref.algorithm_id { + let timestamp = common_utils::date_time::now_unix_timestamp(); + let routing_algorithm: routing_types::RoutingAlgorithmRef = + routing_types::RoutingAlgorithmRef { + algorithm_id: None, + timestamp, + config_algo_id: routing_algo_ref.config_algo_id.clone(), + surcharge_config_algo_id: routing_algo_ref.surcharge_config_algo_id, + }; + let record = RoutingAlgorithmUpdate::fetch_routing_algo( + merchant_account.get_id(), + &algorithm_id, + db, + ) + .await?; + let response = record.0.foreign_into(); + helpers::update_business_profile_active_algorithm_ref( + db, + business_profile, + routing_algorithm, + transaction_type, + ) + .await?; + + metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add(&metrics::CONTEXT, 1, &[]); + Ok(service_api::ApplicationResponse::Json(response)) + } else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? + } +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "routing_v2")))] pub async fn unlink_routing_config( state: SessionState, merchant_account: domain::MerchantAccount, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0a40d5b6a1..0b42c71944 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1455,18 +1455,16 @@ impl BusinessProfile { }, )), ) - .service( - web::resource("/deactivate_routing_algorithm").route(web::post().to( - |state, req, path| { - routing::routing_unlink_config( - state, - req, - path, - &TransactionType::Payment, - ) - }, - )), - ), + .service(web::resource("/deactivate_routing_algorithm").route( + web::patch().to(|state, req, path| { + routing::routing_unlink_config( + state, + req, + path, + &TransactionType::Payment, + ) + }), + )), ) } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index b4e180cf71..8e37589837 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -200,7 +200,41 @@ pub async fn list_routing_configs( .await } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v2", feature = "routing_v2"))] +#[instrument(skip_all)] +pub async fn routing_unlink_config( + state: web::Data, + req: HttpRequest, + path: web::Path, + transaction_type: &enums::TransactionType, +) -> impl Responder { + let flow = Flow::RoutingUnlinkConfig; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + path.into_inner(), + |state, auth: auth::AuthenticationData, path, _| { + routing::unlink_routing_config(state, auth.merchant_account, path, transaction_type) + }, + #[cfg(not(feature = "release"))] + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), + #[cfg(feature = "release")] + &auth::JWTAuth(Permission::RoutingWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all( + feature = "olap", + any(feature = "v1", feature = "v2"), + not(feature = "routing_v2") +))] #[instrument(skip_all)] pub async fn routing_unlink_config( state: web::Data,