diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 4d32f1d0f8..723e6eccc3 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -11,7 +11,7 @@ pub use euclid::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::enums::{self, RoutableConnectors, TransactionType}; +use crate::enums::{RoutableConnectors, TransactionType}; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(tag = "type", content = "data", rename_all = "snake_case")] @@ -299,34 +299,12 @@ impl From for ast::ConnectorChoice { } } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct DetailedConnectorChoice { - pub connector: RoutableConnectors, - pub business_label: Option, - pub business_country: Option, - pub business_sub_label: Option, + pub enabled: bool, } -impl DetailedConnectorChoice { - pub fn get_connector_label(&self) -> Option { - self.business_country - .as_ref() - .zip(self.business_label.as_ref()) - .map(|(business_country, business_label)| { - let mut base_label = format!( - "{}_{:?}_{}", - self.connector, business_country, business_label - ); - - if let Some(ref sub_label) = self.business_sub_label { - base_label.push('_'); - base_label.push_str(sub_label); - } - - base_label - }) - } -} +impl common_utils::events::ApiEventMetric for DetailedConnectorChoice {} #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)] #[serde(rename_all = "snake_case")] diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 3182561739..bec90c51e9 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -9,7 +9,6 @@ use api_models::{ }; #[cfg(not(feature = "business_profile_routing"))] use common_utils::ext_traits::{Encode, StringExt}; -#[cfg(not(feature = "business_profile_routing"))] use diesel_models::configs; #[cfg(feature = "business_profile_routing")] use diesel_models::routing_algorithm::RoutingAlgorithm; @@ -807,6 +806,37 @@ pub async fn retrieve_linked_routing_config( } } +pub async fn upsert_connector_agnostic_mandate_config( + state: AppState, + business_profile_id: &str, + mandate_config: routing_types::DetailedConnectorChoice, +) -> RouterResponse { + let key = helpers::get_pg_agnostic_mandate_config_key(business_profile_id); + + let mandate_config_str = mandate_config.enabled.to_string(); + + let find_config = state + .store + .find_config_by_key_unwrap_or(&key, Some(mandate_config_str.clone())) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error saving pg agnostic mandate config to db")?; + + if find_config.config != mandate_config_str { + let config_update = configs::ConfigUpdate::Update { + config: Some(mandate_config_str), + }; + state + .store + .update_config_by_key(&key, config_update) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error saving pg agnostic mandate config to db")?; + } + + Ok(service_api::ApplicationResponse::Json(mandate_config)) +} + pub async fn retrieve_default_routing_config_for_profiles( state: AppState, merchant_account: domain::MerchantAccount, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index febd1cd86d..a725cc714e 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -273,9 +273,9 @@ pub async fn update_business_profile_active_algorithm_ref( pub async fn get_merchant_connector_agnostic_mandate_config( db: &dyn StorageInterface, - merchant_id: &str, + business_profile_id: &str, ) -> RouterResult> { - let key = get_pg_agnostic_mandate_config_key(merchant_id); + let key = get_pg_agnostic_mandate_config_key(business_profile_id); let maybe_config = db.find_config_by_key(&key).await; match maybe_config { @@ -312,29 +312,6 @@ pub async fn get_merchant_connector_agnostic_mandate_config( } } -pub async fn update_merchant_connector_agnostic_mandate_config( - db: &dyn StorageInterface, - merchant_id: &str, - mandate_config: Vec, -) -> RouterResult> { - let key = get_pg_agnostic_mandate_config_key(merchant_id); - let mandate_config_str = mandate_config - .encode_to_string_of_json() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to serialize pg agnostic mandate config during update")?; - - let config_update = configs::ConfigUpdate::Update { - config: Some(mandate_config_str), - }; - - db.update_config_by_key(&key, config_update) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("error saving pg agnostic mandate config to db")?; - - Ok(mandate_config) -} - pub async fn validate_connectors_in_routing_config( db: &dyn StorageInterface, key_store: &domain::MerchantKeyStore, @@ -465,8 +442,8 @@ pub fn get_routing_dictionary_key(merchant_id: &str) -> String { /// Provides the identifier for the specific merchant's agnostic_mandate_config #[inline(always)] -pub fn get_pg_agnostic_mandate_config_key(merchant_id: &str) -> String { - format!("pg_agnostic_mandate_{merchant_id}") +pub fn get_pg_agnostic_mandate_config_key(business_profile_id: &str) -> String { + format!("pg_agnostic_mandate_{business_profile_id}") } /// Provides the identifier for the specific merchant's default_config diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 01ed994a9d..7d9ee1670d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -437,6 +437,10 @@ impl Routing { ) })), ) + .service( + web::resource("/business_profile/{business_profile_id}/configs/pg_agnostic_mit") + .route(web::post().to(cloud_routing::upsert_connector_agnostic_mandate_config)), + ) .service( web::resource("/default") .route(web::get().to(|state, req| { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 7908b9dfdc..02a3a6841f 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -225,6 +225,7 @@ impl From for ApiIdentifier { | Flow::ReconTokenRequest | Flow::ReconServiceRequest | Flow::ReconVerifyToken => Self::Recon, + Flow::CreateConnectorAgnosticMandateConfig => Self::Routing, } } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 1476fc7b3d..28d65f66a8 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -566,6 +566,44 @@ pub async fn routing_retrieve_linked_config( } } +#[cfg(feature = "olap")] +#[instrument(skip_all)] +pub async fn upsert_connector_agnostic_mandate_config( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + use crate::services::authentication::AuthenticationData; + + let flow = Flow::CreateConnectorAgnosticMandateConfig; + let business_profile_id = path.into_inner(); + + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, _auth: AuthenticationData, mandate_config| { + Box::pin(routing::upsert_connector_agnostic_mandate_config( + state, + &business_profile_id, + mandate_config, + )) + }, + #[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(feature = "olap")] #[instrument(skip_all)] pub async fn routing_retrieve_default_config_for_profiles( diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index df322da942..ca8d75b332 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -206,6 +206,8 @@ pub enum Flow { RoutingRetrieveConfig, /// Routing retrieve active config RoutingRetrieveActiveConfig, + /// Update connector agnostic mandate config + CreateConnectorAgnosticMandateConfig, /// Routing retrieve default config RoutingRetrieveDefaultConfig, /// Routing retrieve dictionary