mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(core): Implement 3ds decision manger for V2 (#7022)
This commit is contained in:
@ -3798,6 +3798,7 @@ impl ProfileCreateBridge for api::ProfileCreate {
|
||||
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
||||
is_click_to_pay_enabled: self.is_click_to_pay_enabled,
|
||||
authentication_product_ids: self.authentication_product_ids,
|
||||
three_ds_decision_manager_config: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -4147,6 +4148,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate {
|
||||
is_network_tokenization_enabled: self.is_network_tokenization_enabled,
|
||||
is_click_to_pay_enabled: self.is_click_to_pay_enabled,
|
||||
authentication_product_ids: self.authentication_product_ids,
|
||||
three_ds_decision_manager_config: None,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
#[cfg(feature = "v2")]
|
||||
use api_models::conditional_configs::DecisionManagerRequest;
|
||||
use api_models::conditional_configs::{
|
||||
DecisionManager, DecisionManagerRecord, DecisionManagerResponse,
|
||||
};
|
||||
use common_utils::ext_traits::StringExt;
|
||||
#[cfg(feature = "v2")]
|
||||
use common_utils::types::keymanager::KeyManagerState;
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::{
|
||||
@ -10,15 +14,57 @@ use crate::{
|
||||
services::api as service_api,
|
||||
types::domain,
|
||||
};
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
pub async fn upsert_conditional_config(
|
||||
_state: SessionState,
|
||||
_key_store: domain::MerchantKeyStore,
|
||||
_merchant_account: domain::MerchantAccount,
|
||||
_request: DecisionManager,
|
||||
) -> RouterResponse<DecisionManagerRecord> {
|
||||
todo!()
|
||||
state: SessionState,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
request: DecisionManagerRequest,
|
||||
profile: domain::Profile,
|
||||
) -> RouterResponse<common_types::payments::DecisionManagerRecord> {
|
||||
use common_utils::ext_traits::OptionExt;
|
||||
|
||||
let key_manager_state: &KeyManagerState = &(&state).into();
|
||||
let db = &*state.store;
|
||||
let name = request.name;
|
||||
let program = request.program;
|
||||
let timestamp = common_utils::date_time::now_unix_timestamp();
|
||||
|
||||
euclid::frontend::ast::lowering::lower_program(program.clone())
|
||||
.change_context(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "Invalid Request Data".to_string(),
|
||||
})
|
||||
.attach_printable("The Request has an Invalid Comparison")?;
|
||||
|
||||
let decision_manager_record = common_types::payments::DecisionManagerRecord {
|
||||
name,
|
||||
program,
|
||||
created_at: timestamp,
|
||||
};
|
||||
|
||||
let business_profile_update = domain::ProfileUpdate::DecisionManagerRecordUpdate {
|
||||
three_ds_decision_manager_config: decision_manager_record,
|
||||
};
|
||||
let updated_profile = db
|
||||
.update_profile_by_profile_id(
|
||||
key_manager_state,
|
||||
&key_store,
|
||||
profile,
|
||||
business_profile_update,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to update decision manager record in business profile")?;
|
||||
|
||||
Ok(service_api::ApplicationResponse::Json(
|
||||
updated_profile
|
||||
.three_ds_decision_manager_config
|
||||
.clone()
|
||||
.get_required_value("three_ds_decision_manager_config")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable(
|
||||
"Failed to get updated decision manager record in business profile",
|
||||
)?,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
@ -204,6 +250,7 @@ pub async fn delete_conditional_config(
|
||||
Ok(service_api::ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub async fn retrieve_conditional_config(
|
||||
state: SessionState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
@ -229,3 +276,27 @@ pub async fn retrieve_conditional_config(
|
||||
};
|
||||
Ok(service_api::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
pub async fn retrieve_conditional_config(
|
||||
state: SessionState,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
profile: domain::Profile,
|
||||
) -> RouterResponse<common_types::payments::DecisionManagerResponse> {
|
||||
let db = state.store.as_ref();
|
||||
let key_manager_state: &KeyManagerState = &(&state).into();
|
||||
let profile_id = profile.get_id();
|
||||
|
||||
let record = profile
|
||||
.three_ds_decision_manager_config
|
||||
.clone()
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("The Conditional Config Record was not found")?;
|
||||
|
||||
let response = common_types::payments::DecisionManagerRecord {
|
||||
name: record.name,
|
||||
program: record.program,
|
||||
created_at: record.created_at,
|
||||
};
|
||||
Ok(service_api::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ use crate::{
|
||||
api::{self, ConnectorCallType, ConnectorCommon},
|
||||
domain,
|
||||
storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt},
|
||||
transformers::{ForeignInto, ForeignTryInto},
|
||||
transformers::ForeignTryInto,
|
||||
},
|
||||
utils::{
|
||||
self, add_apple_pay_flow_metrics, add_connector_http_status_code_metrics, Encode,
|
||||
@ -1145,7 +1145,7 @@ where
|
||||
Ok(payment_dsl_data
|
||||
.payment_attempt
|
||||
.authentication_type
|
||||
.or(output.override_3ds.map(ForeignInto::foreign_into))
|
||||
.or(output.override_3ds)
|
||||
.or(Some(storage_enums::AuthenticationType::NoThreeDs)))
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
mod transformers;
|
||||
|
||||
use api_models::{
|
||||
conditional_configs::{ConditionalConfigs, DecisionManagerRecord},
|
||||
routing,
|
||||
};
|
||||
use api_models::{conditional_configs::DecisionManagerRecord, routing};
|
||||
use common_utils::ext_traits::StringExt;
|
||||
use error_stack::ResultExt;
|
||||
use euclid::backend::{self, inputs as dsl_inputs, EuclidBackend};
|
||||
@ -23,11 +18,11 @@ pub async fn perform_decision_management(
|
||||
algorithm_ref: routing::RoutingAlgorithmRef,
|
||||
merchant_id: &common_utils::id_type::MerchantId,
|
||||
payment_data: &core_routing::PaymentsDslInput<'_>,
|
||||
) -> ConditionalConfigResult<ConditionalConfigs> {
|
||||
) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
|
||||
let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id {
|
||||
id
|
||||
} else {
|
||||
return Ok(ConditionalConfigs::default());
|
||||
return Ok(common_types::payments::ConditionalConfigs::default());
|
||||
};
|
||||
let db = &*state.store;
|
||||
|
||||
@ -64,8 +59,8 @@ pub async fn perform_decision_management(
|
||||
|
||||
pub fn execute_dsl_and_get_conditional_config(
|
||||
backend_input: dsl_inputs::BackendInput,
|
||||
interpreter: &backend::VirInterpreterBackend<ConditionalConfigs>,
|
||||
) -> ConditionalConfigResult<ConditionalConfigs> {
|
||||
interpreter: &backend::VirInterpreterBackend<common_types::payments::ConditionalConfigs>,
|
||||
) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
|
||||
let routing_output = interpreter
|
||||
.execute(backend_input)
|
||||
.map(|out| out.connector_selection)
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
use api_models::{self, conditional_configs};
|
||||
use diesel_models::enums as storage_enums;
|
||||
use euclid::enums as dsl_enums;
|
||||
|
||||
use crate::types::transformers::ForeignFrom;
|
||||
impl ForeignFrom<dsl_enums::AuthenticationType> for conditional_configs::AuthenticationType {
|
||||
fn foreign_from(from: dsl_enums::AuthenticationType) -> Self {
|
||||
match from {
|
||||
dsl_enums::AuthenticationType::ThreeDs => Self::ThreeDs,
|
||||
dsl_enums::AuthenticationType::NoThreeDs => Self::NoThreeDs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<conditional_configs::AuthenticationType> for storage_enums::AuthenticationType {
|
||||
fn foreign_from(from: conditional_configs::AuthenticationType) -> Self {
|
||||
match from {
|
||||
conditional_configs::AuthenticationType::ThreeDs => Self::ThreeDs,
|
||||
conditional_configs::AuthenticationType::NoThreeDs => Self::NoThreeDs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,17 +197,6 @@ pub async fn update_merchant_active_algorithm_ref(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
pub async fn update_merchant_active_algorithm_ref(
|
||||
_state: &SessionState,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
_config_key: cache::CacheKind<'_>,
|
||||
_algorithm_id: routing_types::RoutingAlgorithmRef,
|
||||
) -> RouterResult<()> {
|
||||
// TODO: handle updating the active routing algorithm for v2 in merchant account
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub async fn update_profile_active_algorithm_ref(
|
||||
db: &dyn StorageInterface,
|
||||
|
||||
@ -1832,7 +1832,12 @@ impl Profile {
|
||||
&TransactionType::Payment,
|
||||
)
|
||||
},
|
||||
))),
|
||||
)))
|
||||
.service(
|
||||
web::resource("/decision")
|
||||
.route(web::put().to(routing::upsert_decision_manager_config))
|
||||
.route(web::get().to(routing::retrieve_decision_manager_config)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,7 +688,7 @@ pub async fn retrieve_surcharge_decision_manager_config(
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
#[cfg(all(feature = "olap", feature = "v1"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn upsert_decision_manager_config(
|
||||
state: web::Data<AppState>,
|
||||
@ -726,6 +726,44 @@ pub async fn upsert_decision_manager_config(
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "v2"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn upsert_decision_manager_config(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<api_models::conditional_configs::DecisionManagerRequest>,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::DecisionManagerUpsertConfig;
|
||||
Box::pin(oss_api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, auth: auth::AuthenticationData, update_decision, _| {
|
||||
conditional_config::upsert_conditional_config(
|
||||
state,
|
||||
auth.key_store,
|
||||
update_decision,
|
||||
auth.profile,
|
||||
)
|
||||
},
|
||||
#[cfg(not(feature = "release"))]
|
||||
auth::auth_type(
|
||||
&auth::HeaderAuth(auth::ApiKeyAuth),
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileThreeDsDecisionManagerWrite,
|
||||
},
|
||||
req.headers(),
|
||||
),
|
||||
#[cfg(feature = "release")]
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileThreeDsDecisionManagerWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn delete_decision_manager_config(
|
||||
@ -762,6 +800,40 @@ pub async fn delete_decision_manager_config(
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "v2"))]
|
||||
#[cfg(feature = "olap")]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn retrieve_decision_manager_config(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::DecisionManagerRetrieveConfig;
|
||||
Box::pin(oss_api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
(),
|
||||
|state, auth: auth::AuthenticationData, _, _| {
|
||||
conditional_config::retrieve_conditional_config(state, auth.key_store, auth.profile)
|
||||
},
|
||||
#[cfg(not(feature = "release"))]
|
||||
auth::auth_type(
|
||||
&auth::HeaderAuth(auth::ApiKeyAuth),
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileThreeDsDecisionManagerWrite,
|
||||
},
|
||||
req.headers(),
|
||||
),
|
||||
#[cfg(feature = "release")]
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileThreeDsDecisionManagerWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "v1"))]
|
||||
#[cfg(feature = "olap")]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn retrieve_decision_manager_config(
|
||||
|
||||
@ -45,7 +45,7 @@ generate_permissions! {
|
||||
},
|
||||
ThreeDsDecisionManager: {
|
||||
scopes: [Read, Write],
|
||||
entities: [Merchant]
|
||||
entities: [Merchant, Profile]
|
||||
},
|
||||
SurchargeDecisionManager: {
|
||||
scopes: [Read, Write],
|
||||
|
||||
Reference in New Issue
Block a user