mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
refactor(dynamic_routing): add logic for creating merchant account in decision engine (#8191)
Co-authored-by: Shankar Singh C <shankar.singh@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -608,6 +608,8 @@ pub struct DynamicRoutingAlgorithmRef {
|
|||||||
pub dynamic_routing_volume_split: Option<u8>,
|
pub dynamic_routing_volume_split: Option<u8>,
|
||||||
pub elimination_routing_algorithm: Option<EliminationRoutingAlgorithm>,
|
pub elimination_routing_algorithm: Option<EliminationRoutingAlgorithm>,
|
||||||
pub contract_based_routing: Option<ContractRoutingAlgorithm>,
|
pub contract_based_routing: Option<ContractRoutingAlgorithm>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub is_merchant_created_in_decision_engine: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DynamicRoutingAlgoAccessor {
|
pub trait DynamicRoutingAlgoAccessor {
|
||||||
@ -717,6 +719,10 @@ impl DynamicRoutingAlgorithmRef {
|
|||||||
self.dynamic_routing_volume_split = volume
|
self.dynamic_routing_volume_split = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_merchant_creation_status_in_decision_engine(&mut self, is_created: bool) {
|
||||||
|
self.is_merchant_created_in_decision_engine = is_created;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_success_rate_routing_enabled(&self) -> bool {
|
pub fn is_success_rate_routing_enabled(&self) -> bool {
|
||||||
self.success_based_algorithm
|
self.success_based_algorithm
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@ -264,26 +264,6 @@ pub async fn create_merchant_account(
|
|||||||
.await
|
.await
|
||||||
.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?;
|
.to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?;
|
||||||
|
|
||||||
// Call to DE here
|
|
||||||
// Check if creation should be based on default profile
|
|
||||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
|
||||||
{
|
|
||||||
if state.conf.open_router.enabled {
|
|
||||||
merchant_account
|
|
||||||
.default_profile
|
|
||||||
.as_ref()
|
|
||||||
.async_map(|profile_id| {
|
|
||||||
routing::helpers::create_decision_engine_merchant(&state, profile_id)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.transpose()
|
|
||||||
.map_err(|err| {
|
|
||||||
crate::logger::error!("Failed to create merchant in Decision Engine {err:?}");
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
|
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
|
||||||
merchant_account.clone(),
|
merchant_account.clone(),
|
||||||
key_store.clone(),
|
key_store.clone(),
|
||||||
@ -3904,6 +3884,22 @@ impl ProfileCreateBridge for api::ProfileCreate {
|
|||||||
.map(CardTestingGuardConfig::foreign_from)
|
.map(CardTestingGuardConfig::foreign_from)
|
||||||
.or(Some(CardTestingGuardConfig::default()));
|
.or(Some(CardTestingGuardConfig::default()));
|
||||||
|
|
||||||
|
let mut dynamic_routing_algorithm_ref =
|
||||||
|
routing_types::DynamicRoutingAlgorithmRef::default();
|
||||||
|
|
||||||
|
if self.is_debit_routing_enabled == Some(true) {
|
||||||
|
routing::helpers::create_merchant_in_decision_engine_if_not_exists(
|
||||||
|
state,
|
||||||
|
&profile_id,
|
||||||
|
&mut dynamic_routing_algorithm_ref,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dynamic_routing_algorithm = serde_json::to_value(dynamic_routing_algorithm_ref)
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("error serializing dynamic_routing_algorithm_ref to JSON Value")?;
|
||||||
|
|
||||||
Ok(domain::Profile::from(domain::ProfileSetter {
|
Ok(domain::Profile::from(domain::ProfileSetter {
|
||||||
profile_id,
|
profile_id,
|
||||||
merchant_id: merchant_context.get_merchant_account().get_id().clone(),
|
merchant_id: merchant_context.get_merchant_account().get_id().clone(),
|
||||||
@ -3981,7 +3977,7 @@ impl ProfileCreateBridge for api::ProfileCreate {
|
|||||||
.always_collect_billing_details_from_wallet_connector,
|
.always_collect_billing_details_from_wallet_connector,
|
||||||
always_collect_shipping_details_from_wallet_connector: self
|
always_collect_shipping_details_from_wallet_connector: self
|
||||||
.always_collect_shipping_details_from_wallet_connector,
|
.always_collect_shipping_details_from_wallet_connector,
|
||||||
dynamic_routing_algorithm: None,
|
dynamic_routing_algorithm: Some(dynamic_routing_algorithm),
|
||||||
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
||||||
is_auto_retries_enabled: self.is_auto_retries_enabled.unwrap_or_default(),
|
is_auto_retries_enabled: self.is_auto_retries_enabled.unwrap_or_default(),
|
||||||
max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from),
|
max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from),
|
||||||
@ -4414,6 +4410,37 @@ impl ProfileUpdateBridge for api::ProfileUpdate {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let dynamic_routing_algo_ref = if self.is_debit_routing_enabled == Some(true) {
|
||||||
|
let mut 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();
|
||||||
|
|
||||||
|
routing::helpers::create_merchant_in_decision_engine_if_not_exists(
|
||||||
|
state,
|
||||||
|
business_profile.get_id(),
|
||||||
|
&mut dynamic_routing_algo_ref,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let dynamic_routing_algo_ref_value = serde_json::to_value(dynamic_routing_algo_ref)
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable(
|
||||||
|
"error serializing dynamic_routing_algorithm_ref to JSON Value",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Some(dynamic_routing_algo_ref_value)
|
||||||
|
} else {
|
||||||
|
self.dynamic_routing_algorithm
|
||||||
|
};
|
||||||
|
|
||||||
Ok(domain::ProfileUpdate::Update(Box::new(
|
Ok(domain::ProfileUpdate::Update(Box::new(
|
||||||
domain::ProfileGeneralUpdate {
|
domain::ProfileGeneralUpdate {
|
||||||
profile_name: self.profile_name,
|
profile_name: self.profile_name,
|
||||||
@ -4451,7 +4478,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate {
|
|||||||
.always_collect_shipping_details_from_wallet_connector,
|
.always_collect_shipping_details_from_wallet_connector,
|
||||||
tax_connector_id: self.tax_connector_id,
|
tax_connector_id: self.tax_connector_id,
|
||||||
is_tax_connector_enabled: self.is_tax_connector_enabled,
|
is_tax_connector_enabled: self.is_tax_connector_enabled,
|
||||||
dynamic_routing_algorithm: self.dynamic_routing_algorithm,
|
dynamic_routing_algorithm: dynamic_routing_algo_ref,
|
||||||
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
||||||
is_auto_retries_enabled: self.is_auto_retries_enabled,
|
is_auto_retries_enabled: self.is_auto_retries_enabled,
|
||||||
max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from),
|
max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from),
|
||||||
|
|||||||
@ -578,6 +578,7 @@ pub async fn link_routing_config(
|
|||||||
business_profile.get_id(),
|
business_profile.get_id(),
|
||||||
routing_algorithm.algorithm_data.clone(),
|
routing_algorithm.algorithm_data.clone(),
|
||||||
routing_types::DynamicRoutingType::SuccessRateBasedRouting,
|
routing_types::DynamicRoutingType::SuccessRateBasedRouting,
|
||||||
|
&mut dynamic_routing_ref,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
@ -608,6 +609,7 @@ pub async fn link_routing_config(
|
|||||||
business_profile.get_id(),
|
business_profile.get_id(),
|
||||||
routing_algorithm.algorithm_data.clone(),
|
routing_algorithm.algorithm_data.clone(),
|
||||||
routing_types::DynamicRoutingType::EliminationRouting,
|
routing_types::DynamicRoutingType::EliminationRouting,
|
||||||
|
&mut dynamic_routing_ref,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
@ -1929,6 +1931,9 @@ pub async fn contract_based_dynamic_routing_setup(
|
|||||||
elimination_routing_algorithm: None,
|
elimination_routing_algorithm: None,
|
||||||
dynamic_routing_volume_split: None,
|
dynamic_routing_volume_split: None,
|
||||||
contract_based_routing: Some(contract_algo),
|
contract_based_routing: Some(contract_algo),
|
||||||
|
is_merchant_created_in_decision_engine: dynamic_routing_algo_ref
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|algo| algo.is_merchant_created_in_decision_engine),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,9 +30,9 @@ use external_services::grpc_client::dynamic_routing::{
|
|||||||
use hyperswitch_domain_models::api::ApplicationResponse;
|
use hyperswitch_domain_models::api::ApplicationResponse;
|
||||||
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
|
||||||
use hyperswitch_interfaces::events::routing_api_logs as routing_events;
|
use hyperswitch_interfaces::events::routing_api_logs as routing_events;
|
||||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
#[cfg(feature = "v1")]
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
#[cfg(feature = "v1")]
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use storage_impl::redis::cache;
|
use storage_impl::redis::cache;
|
||||||
@ -52,15 +52,15 @@ use crate::{
|
|||||||
types::{domain, storage},
|
types::{domain, storage},
|
||||||
utils::StringExt,
|
utils::StringExt,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
use crate::{
|
||||||
|
core::payments::routing::utils::{self as routing_utils, DecisionEngineApiHandler},
|
||||||
|
services,
|
||||||
|
};
|
||||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{
|
core::{metrics as core_metrics, routing},
|
||||||
metrics as core_metrics,
|
|
||||||
payments::routing::utils::{self as routing_utils, DecisionEngineApiHandler},
|
|
||||||
routing,
|
|
||||||
},
|
|
||||||
routes::app::SessionStateInfo,
|
routes::app::SessionStateInfo,
|
||||||
services,
|
|
||||||
types::transformers::ForeignInto,
|
types::transformers::ForeignInto,
|
||||||
};
|
};
|
||||||
pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
|
pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
|
||||||
@ -1723,7 +1723,7 @@ pub async fn disable_dynamic_routing_algorithm(
|
|||||||
let db = state.store.as_ref();
|
let db = state.store.as_ref();
|
||||||
let key_manager_state = &state.into();
|
let key_manager_state = &state.into();
|
||||||
let profile_id = business_profile.get_id().clone();
|
let profile_id = business_profile.get_id().clone();
|
||||||
let (algorithm_id, dynamic_routing_algorithm, cache_entries_to_redact) =
|
let (algorithm_id, mut dynamic_routing_algorithm, cache_entries_to_redact) =
|
||||||
match dynamic_routing_type {
|
match dynamic_routing_type {
|
||||||
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
|
routing_types::DynamicRoutingType::SuccessRateBasedRouting => {
|
||||||
let Some(algorithm_ref) = dynamic_routing_algo_ref.success_based_algorithm else {
|
let Some(algorithm_ref) = dynamic_routing_algo_ref.success_based_algorithm else {
|
||||||
@ -1760,6 +1760,8 @@ pub async fn disable_dynamic_routing_algorithm(
|
|||||||
contract_based_routing: dynamic_routing_algo_ref.contract_based_routing,
|
contract_based_routing: dynamic_routing_algo_ref.contract_based_routing,
|
||||||
dynamic_routing_volume_split: dynamic_routing_algo_ref
|
dynamic_routing_volume_split: dynamic_routing_algo_ref
|
||||||
.dynamic_routing_volume_split,
|
.dynamic_routing_volume_split,
|
||||||
|
is_merchant_created_in_decision_engine: dynamic_routing_algo_ref
|
||||||
|
.is_merchant_created_in_decision_engine,
|
||||||
},
|
},
|
||||||
cache_entries_to_redact,
|
cache_entries_to_redact,
|
||||||
)
|
)
|
||||||
@ -1800,6 +1802,8 @@ pub async fn disable_dynamic_routing_algorithm(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
contract_based_routing: dynamic_routing_algo_ref.contract_based_routing,
|
contract_based_routing: dynamic_routing_algo_ref.contract_based_routing,
|
||||||
|
is_merchant_created_in_decision_engine: dynamic_routing_algo_ref
|
||||||
|
.is_merchant_created_in_decision_engine,
|
||||||
},
|
},
|
||||||
cache_entries_to_redact,
|
cache_entries_to_redact,
|
||||||
)
|
)
|
||||||
@ -1838,6 +1842,8 @@ pub async fn disable_dynamic_routing_algorithm(
|
|||||||
routing_types::DynamicAlgorithmWithTimestamp::new(None),
|
routing_types::DynamicAlgorithmWithTimestamp::new(None),
|
||||||
enabled_feature: routing_types::DynamicRoutingFeatures::None,
|
enabled_feature: routing_types::DynamicRoutingFeatures::None,
|
||||||
}),
|
}),
|
||||||
|
is_merchant_created_in_decision_engine: dynamic_routing_algo_ref
|
||||||
|
.is_merchant_created_in_decision_engine,
|
||||||
},
|
},
|
||||||
cache_entries_to_redact,
|
cache_entries_to_redact,
|
||||||
)
|
)
|
||||||
@ -1850,6 +1856,7 @@ pub async fn disable_dynamic_routing_algorithm(
|
|||||||
state,
|
state,
|
||||||
business_profile.get_id(),
|
business_profile.get_id(),
|
||||||
dynamic_routing_type,
|
dynamic_routing_type,
|
||||||
|
&mut dynamic_routing_algorithm,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
@ -2088,6 +2095,7 @@ pub async fn default_specific_dynamic_routing_setup(
|
|||||||
state,
|
state,
|
||||||
business_profile.get_id(),
|
business_profile.get_id(),
|
||||||
dynamic_routing_type,
|
dynamic_routing_type,
|
||||||
|
&mut dynamic_routing_algo_ref,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
@ -2203,6 +2211,7 @@ pub async fn enable_decision_engine_dynamic_routing_setup(
|
|||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
profile_id: &id_type::ProfileId,
|
profile_id: &id_type::ProfileId,
|
||||||
dynamic_routing_type: routing_types::DynamicRoutingType,
|
dynamic_routing_type: routing_types::DynamicRoutingType,
|
||||||
|
dynamic_routing_algo_ref: &mut routing_types::DynamicRoutingAlgorithmRef,
|
||||||
) -> RouterResult<()> {
|
) -> RouterResult<()> {
|
||||||
logger::debug!(
|
logger::debug!(
|
||||||
"performing call with open_router for profile {}",
|
"performing call with open_router for profile {}",
|
||||||
@ -2248,6 +2257,10 @@ pub async fn enable_decision_engine_dynamic_routing_setup(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create merchant in Decision Engine if it is not already created
|
||||||
|
create_merchant_in_decision_engine_if_not_exists(state, profile_id, dynamic_routing_algo_ref)
|
||||||
|
.await;
|
||||||
|
|
||||||
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
||||||
state,
|
state,
|
||||||
services::Method::Post,
|
services::Method::Post,
|
||||||
@ -2269,6 +2282,7 @@ pub async fn update_decision_engine_dynamic_routing_setup(
|
|||||||
profile_id: &id_type::ProfileId,
|
profile_id: &id_type::ProfileId,
|
||||||
request: serde_json::Value,
|
request: serde_json::Value,
|
||||||
dynamic_routing_type: routing_types::DynamicRoutingType,
|
dynamic_routing_type: routing_types::DynamicRoutingType,
|
||||||
|
dynamic_routing_algo_ref: &mut routing_types::DynamicRoutingAlgorithmRef,
|
||||||
) -> RouterResult<()> {
|
) -> RouterResult<()> {
|
||||||
logger::debug!(
|
logger::debug!(
|
||||||
"performing call with open_router for profile {}",
|
"performing call with open_router for profile {}",
|
||||||
@ -2320,6 +2334,10 @@ pub async fn update_decision_engine_dynamic_routing_setup(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create merchant in Decision Engine if it is not already created
|
||||||
|
create_merchant_in_decision_engine_if_not_exists(state, profile_id, dynamic_routing_algo_ref)
|
||||||
|
.await;
|
||||||
|
|
||||||
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
||||||
state,
|
state,
|
||||||
services::Method::Post,
|
services::Method::Post,
|
||||||
@ -2340,6 +2358,7 @@ pub async fn disable_decision_engine_dynamic_routing_setup(
|
|||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
profile_id: &id_type::ProfileId,
|
profile_id: &id_type::ProfileId,
|
||||||
dynamic_routing_type: routing_types::DynamicRoutingType,
|
dynamic_routing_type: routing_types::DynamicRoutingType,
|
||||||
|
dynamic_routing_algo_ref: &mut routing_types::DynamicRoutingAlgorithmRef,
|
||||||
) -> RouterResult<()> {
|
) -> RouterResult<()> {
|
||||||
logger::debug!(
|
logger::debug!(
|
||||||
"performing call with open_router for profile {}",
|
"performing call with open_router for profile {}",
|
||||||
@ -2364,6 +2383,10 @@ pub async fn disable_decision_engine_dynamic_routing_setup(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create merchant in Decision Engine if it is not already created
|
||||||
|
create_merchant_in_decision_engine_if_not_exists(state, profile_id, dynamic_routing_algo_ref)
|
||||||
|
.await;
|
||||||
|
|
||||||
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
routing_utils::ConfigApiClient::send_decision_engine_request::<_, String>(
|
||||||
state,
|
state,
|
||||||
services::Method::Post,
|
services::Method::Post,
|
||||||
@ -2378,7 +2401,32 @@ pub async fn disable_decision_engine_dynamic_routing_setup(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
|
#[cfg(feature = "v1")]
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn create_merchant_in_decision_engine_if_not_exists(
|
||||||
|
state: &SessionState,
|
||||||
|
profile_id: &id_type::ProfileId,
|
||||||
|
dynamic_routing_algo_ref: &mut routing_types::DynamicRoutingAlgorithmRef,
|
||||||
|
) {
|
||||||
|
if !dynamic_routing_algo_ref.is_merchant_created_in_decision_engine {
|
||||||
|
logger::debug!(
|
||||||
|
"Creating merchant_account in decision engine for profile {}",
|
||||||
|
profile_id.get_string_repr()
|
||||||
|
);
|
||||||
|
|
||||||
|
create_decision_engine_merchant(state, profile_id)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
logger::warn!("Merchant creation error in decision_engine: {err:?}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
// TODO: Update the status based on the status code or error message from the API call
|
||||||
|
dynamic_routing_algo_ref.update_merchant_creation_status_in_decision_engine(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn create_decision_engine_merchant(
|
pub async fn create_decision_engine_merchant(
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
|
|||||||
Reference in New Issue
Block a user