mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat(router): Create a merchant config for enable processor agnostic MIT (#4025)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -11,7 +11,7 @@ pub use euclid::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::enums::{self, RoutableConnectors, TransactionType};
|
use crate::enums::{RoutableConnectors, TransactionType};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
|
||||||
@ -299,34 +299,12 @@ impl From<RoutableConnectorChoice> for ast::ConnectorChoice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
pub struct DetailedConnectorChoice {
|
pub struct DetailedConnectorChoice {
|
||||||
pub connector: RoutableConnectors,
|
pub enabled: bool,
|
||||||
pub business_label: Option<String>,
|
|
||||||
pub business_country: Option<enums::CountryAlpha2>,
|
|
||||||
pub business_sub_label: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetailedConnectorChoice {
|
impl common_utils::events::ApiEventMetric for DetailedConnectorChoice {}
|
||||||
pub fn get_connector_label(&self) -> Option<String> {
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)]
|
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
|||||||
@ -9,7 +9,6 @@ use api_models::{
|
|||||||
};
|
};
|
||||||
#[cfg(not(feature = "business_profile_routing"))]
|
#[cfg(not(feature = "business_profile_routing"))]
|
||||||
use common_utils::ext_traits::{Encode, StringExt};
|
use common_utils::ext_traits::{Encode, StringExt};
|
||||||
#[cfg(not(feature = "business_profile_routing"))]
|
|
||||||
use diesel_models::configs;
|
use diesel_models::configs;
|
||||||
#[cfg(feature = "business_profile_routing")]
|
#[cfg(feature = "business_profile_routing")]
|
||||||
use diesel_models::routing_algorithm::RoutingAlgorithm;
|
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<routing_types::DetailedConnectorChoice> {
|
||||||
|
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(
|
pub async fn retrieve_default_routing_config_for_profiles(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
merchant_account: domain::MerchantAccount,
|
merchant_account: domain::MerchantAccount,
|
||||||
|
|||||||
@ -273,9 +273,9 @@ pub async fn update_business_profile_active_algorithm_ref(
|
|||||||
|
|
||||||
pub async fn get_merchant_connector_agnostic_mandate_config(
|
pub async fn get_merchant_connector_agnostic_mandate_config(
|
||||||
db: &dyn StorageInterface,
|
db: &dyn StorageInterface,
|
||||||
merchant_id: &str,
|
business_profile_id: &str,
|
||||||
) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> {
|
) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> {
|
||||||
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;
|
let maybe_config = db.find_config_by_key(&key).await;
|
||||||
|
|
||||||
match maybe_config {
|
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<routing_types::DetailedConnectorChoice>,
|
|
||||||
) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> {
|
|
||||||
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(
|
pub async fn validate_connectors_in_routing_config(
|
||||||
db: &dyn StorageInterface,
|
db: &dyn StorageInterface,
|
||||||
key_store: &domain::MerchantKeyStore,
|
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
|
/// Provides the identifier for the specific merchant's agnostic_mandate_config
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_pg_agnostic_mandate_config_key(merchant_id: &str) -> String {
|
pub fn get_pg_agnostic_mandate_config_key(business_profile_id: &str) -> String {
|
||||||
format!("pg_agnostic_mandate_{merchant_id}")
|
format!("pg_agnostic_mandate_{business_profile_id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides the identifier for the specific merchant's default_config
|
/// Provides the identifier for the specific merchant's default_config
|
||||||
|
|||||||
@ -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(
|
.service(
|
||||||
web::resource("/default")
|
web::resource("/default")
|
||||||
.route(web::get().to(|state, req| {
|
.route(web::get().to(|state, req| {
|
||||||
|
|||||||
@ -225,6 +225,7 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::ReconTokenRequest
|
| Flow::ReconTokenRequest
|
||||||
| Flow::ReconServiceRequest
|
| Flow::ReconServiceRequest
|
||||||
| Flow::ReconVerifyToken => Self::Recon,
|
| Flow::ReconVerifyToken => Self::Recon,
|
||||||
|
Flow::CreateConnectorAgnosticMandateConfig => Self::Routing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
json_payload: web::Json<routing_types::DetailedConnectorChoice>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
) -> 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")]
|
#[cfg(feature = "olap")]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn routing_retrieve_default_config_for_profiles(
|
pub async fn routing_retrieve_default_config_for_profiles(
|
||||||
|
|||||||
@ -206,6 +206,8 @@ pub enum Flow {
|
|||||||
RoutingRetrieveConfig,
|
RoutingRetrieveConfig,
|
||||||
/// Routing retrieve active config
|
/// Routing retrieve active config
|
||||||
RoutingRetrieveActiveConfig,
|
RoutingRetrieveActiveConfig,
|
||||||
|
/// Update connector agnostic mandate config
|
||||||
|
CreateConnectorAgnosticMandateConfig,
|
||||||
/// Routing retrieve default config
|
/// Routing retrieve default config
|
||||||
RoutingRetrieveDefaultConfig,
|
RoutingRetrieveDefaultConfig,
|
||||||
/// Routing retrieve dictionary
|
/// Routing retrieve dictionary
|
||||||
|
|||||||
Reference in New Issue
Block a user