feat(core): Implement 3ds decision manger for V2 (#7022)

This commit is contained in:
Swangi Kumari
2025-02-05 19:07:46 +05:30
committed by GitHub
parent 67ea754e38
commit 1900959778
22 changed files with 364 additions and 155 deletions

1
.github/CODEOWNERS vendored
View File

@ -18,6 +18,7 @@ crates/router/src/services/ @juspay/hyperswitch-framework
crates/router/src/db/ @juspay/hyperswitch-framework crates/router/src/db/ @juspay/hyperswitch-framework
crates/router/src/routes/ @juspay/hyperswitch-framework crates/router/src/routes/ @juspay/hyperswitch-framework
migrations/ @juspay/hyperswitch-framework migrations/ @juspay/hyperswitch-framework
v2_migrations/ @juspay/hyperswitch-framework
api-reference/ @juspay/hyperswitch-framework api-reference/ @juspay/hyperswitch-framework
api-reference-v2/ @juspay/hyperswitch-framework api-reference-v2/ @juspay/hyperswitch-framework
Cargo.toml @juspay/hyperswitch-framework Cargo.toml @juspay/hyperswitch-framework

3
Cargo.lock generated
View File

@ -2039,8 +2039,10 @@ dependencies = [
"common_enums", "common_enums",
"common_utils", "common_utils",
"diesel", "diesel",
"euclid",
"serde", "serde",
"serde_json", "serde_json",
"strum 0.26.3",
"utoipa", "utoipa",
] ]
@ -3067,6 +3069,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"api_models", "api_models",
"common_enums", "common_enums",
"common_types",
"connector_configs", "connector_configs",
"currency_conversion", "currency_conversion",
"euclid", "euclid",

View File

@ -1,82 +1,10 @@
use common_utils::events; use common_utils::events;
use euclid::{ use euclid::frontend::ast::Program;
dssa::types::EuclidAnalysable,
enums,
frontend::{
ast::Program,
dir::{DirKeyKind, DirValue, EuclidDirFilter},
},
types::Metadata,
};
use serde::{Deserialize, Serialize};
#[derive(
Clone,
Debug,
Hash,
PartialEq,
Eq,
strum::Display,
strum::VariantNames,
strum::EnumIter,
strum::EnumString,
Serialize,
Deserialize,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum AuthenticationType {
ThreeDs,
NoThreeDs,
}
impl AuthenticationType {
pub fn to_dir_value(&self) -> DirValue {
match self {
Self::ThreeDs => DirValue::AuthenticationType(enums::AuthenticationType::ThreeDs),
Self::NoThreeDs => DirValue::AuthenticationType(enums::AuthenticationType::NoThreeDs),
}
}
}
impl EuclidAnalysable for AuthenticationType {
fn get_dir_value_for_analysis(&self, rule_name: String) -> Vec<(DirValue, Metadata)> {
let auth = self.to_string();
vec![(
self.to_dir_value(),
std::collections::HashMap::from_iter([(
"AUTHENTICATION_TYPE".to_string(),
serde_json::json!({
"rule_name":rule_name,
"Authentication_type": auth,
}),
)]),
)]
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ConditionalConfigs {
pub override_3ds: Option<AuthenticationType>,
}
impl EuclidDirFilter for ConditionalConfigs {
const ALLOWED: &'static [DirKeyKind] = &[
DirKeyKind::PaymentMethod,
DirKeyKind::CardType,
DirKeyKind::CardNetwork,
DirKeyKind::MetaData,
DirKeyKind::PaymentAmount,
DirKeyKind::PaymentCurrency,
DirKeyKind::CaptureMethod,
DirKeyKind::BillingCountry,
DirKeyKind::BusinessCountry,
];
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DecisionManagerRecord { pub struct DecisionManagerRecord {
pub name: String, pub name: String,
pub program: Program<ConditionalConfigs>, pub program: Program<common_types::payments::ConditionalConfigs>,
pub created_at: i64, pub created_at: i64,
pub modified_at: i64, pub modified_at: i64,
} }
@ -89,12 +17,14 @@ impl events::ApiEventMetric for DecisionManagerRecord {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ConditionalConfigReq { pub struct ConditionalConfigReq {
pub name: Option<String>, pub name: Option<String>,
pub algorithm: Option<Program<ConditionalConfigs>>, pub algorithm: Option<Program<common_types::payments::ConditionalConfigs>>,
} }
#[cfg(feature = "v1")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DecisionManagerRequest { pub struct DecisionManagerRequest {
pub name: Option<String>, pub name: Option<String>,
pub program: Option<Program<ConditionalConfigs>>, pub program: Option<Program<common_types::payments::ConditionalConfigs>>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -111,3 +41,17 @@ impl events::ApiEventMetric for DecisionManager {
} }
pub type DecisionManagerResponse = DecisionManagerRecord; pub type DecisionManagerResponse = DecisionManagerRecord;
#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DecisionManagerRequest {
pub name: String,
pub program: Program<common_types::payments::ConditionalConfigs>,
}
#[cfg(feature = "v2")]
impl events::ApiEventMetric for DecisionManagerRequest {
fn get_api_event_type(&self) -> Option<events::ApiEventsType> {
Some(events::ApiEventsType::Routing)
}
}

View File

@ -15,10 +15,12 @@ v2 = ["common_utils/v2"]
diesel = "2.2.3" diesel = "2.2.3"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115" serde_json = "1.0.115"
strum = { version = "0.26", features = ["derive"] }
utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] } utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] }
common_enums = { version = "0.1.0", path = "../common_enums" } common_enums = { version = "0.1.0", path = "../common_enums" }
common_utils = { version = "0.1.0", path = "../common_utils"} common_utils = { version = "0.1.0", path = "../common_utils"}
euclid = { version = "0.1.0", path = "../euclid" }
[lints] [lints]
workspace = true workspace = true

View File

@ -3,8 +3,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use common_enums::enums; use common_enums::enums;
use common_utils::{errors, impl_to_sql_from_sql_json, types::MinorUnit}; use common_utils::{errors, events, impl_to_sql_from_sql_json, types::MinorUnit};
use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow};
use euclid::frontend::{
ast::Program,
dir::{DirKeyKind, EuclidDirFilter},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
@ -21,26 +25,6 @@ pub enum SplitPaymentsRequest {
} }
impl_to_sql_from_sql_json!(SplitPaymentsRequest); impl_to_sql_from_sql_json!(SplitPaymentsRequest);
#[derive(
Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
#[serde(deny_unknown_fields)]
/// Fee information for Split Payments to be charged on the payment being collected for Stripe
pub struct StripeSplitPaymentRequest {
/// Stripe's charge type
#[schema(value_type = PaymentChargeType, example = "direct")]
pub charge_type: enums::PaymentChargeType,
/// Platform fees to be collected on the payment
#[schema(value_type = i64, example = 6540)]
pub application_fees: MinorUnit,
/// Identifier for the reseller's account to send the funds to
pub transfer_account_id: String,
}
impl_to_sql_from_sql_json!(StripeSplitPaymentRequest);
#[derive( #[derive(
Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)] )]
@ -65,3 +49,70 @@ impl AuthenticationConnectorAccountMap {
.cloned() .cloned()
} }
} }
#[derive(
Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
#[serde(deny_unknown_fields)]
/// Fee information for Split Payments to be charged on the payment being collected for Stripe
pub struct StripeSplitPaymentRequest {
/// Stripe's charge type
#[schema(value_type = PaymentChargeType, example = "direct")]
pub charge_type: enums::PaymentChargeType,
/// Platform fees to be collected on the payment
#[schema(value_type = i64, example = 6540)]
pub application_fees: MinorUnit,
/// Identifier for the reseller's account to send the funds to
pub transfer_account_id: String,
}
impl_to_sql_from_sql_json!(StripeSplitPaymentRequest);
#[derive(
Serialize, Default, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
/// ConditionalConfigs
pub struct ConditionalConfigs {
/// Override 3DS
pub override_3ds: Option<common_enums::AuthenticationType>,
}
impl EuclidDirFilter for ConditionalConfigs {
const ALLOWED: &'static [DirKeyKind] = &[
DirKeyKind::PaymentMethod,
DirKeyKind::CardType,
DirKeyKind::CardNetwork,
DirKeyKind::MetaData,
DirKeyKind::PaymentAmount,
DirKeyKind::PaymentCurrency,
DirKeyKind::CaptureMethod,
DirKeyKind::BillingCountry,
DirKeyKind::BusinessCountry,
];
}
impl_to_sql_from_sql_json!(ConditionalConfigs);
#[derive(Serialize, Deserialize, Debug, Clone, FromSqlRow, AsExpression, ToSchema)]
#[diesel(sql_type = Jsonb)]
/// DecisionManagerRecord
pub struct DecisionManagerRecord {
/// Name of the Decision Manager
pub name: String,
/// Program to be executed
pub program: Program<ConditionalConfigs>,
/// Created at timestamp
pub created_at: i64,
}
impl events::ApiEventMetric for DecisionManagerRecord {
fn get_api_event_type(&self) -> Option<events::ApiEventsType> {
Some(events::ApiEventsType::Routing)
}
}
impl_to_sql_from_sql_json!(DecisionManagerRecord);
/// DecisionManagerResponse
pub type DecisionManagerResponse = DecisionManagerRecord;

View File

@ -310,6 +310,7 @@ pub struct Profile {
pub is_click_to_pay_enabled: bool, pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
impl Profile { impl Profile {
@ -371,6 +372,7 @@ pub struct ProfileNew {
pub is_click_to_pay_enabled: bool, pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -416,6 +418,7 @@ pub struct ProfileUpdateInternal {
pub is_click_to_pay_enabled: Option<bool>, pub is_click_to_pay_enabled: Option<bool>,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -459,6 +462,7 @@ impl ProfileUpdateInternal {
max_auto_retries_enabled, max_auto_retries_enabled,
is_click_to_pay_enabled, is_click_to_pay_enabled,
authentication_product_ids, authentication_product_ids,
three_ds_decision_manager_config,
} = self; } = self;
Profile { Profile {
id: source.id, id: source.id,
@ -527,6 +531,8 @@ impl ProfileUpdateInternal {
.unwrap_or(source.is_click_to_pay_enabled), .unwrap_or(source.is_click_to_pay_enabled),
authentication_product_ids: authentication_product_ids authentication_product_ids: authentication_product_ids
.or(source.authentication_product_ids), .or(source.authentication_product_ids),
three_ds_decision_manager_config: three_ds_decision_manager_config
.or(source.three_ds_decision_manager_config),
} }
} }
} }

View File

@ -224,6 +224,7 @@ diesel::table! {
max_auto_retries_enabled -> Nullable<Int2>, max_auto_retries_enabled -> Nullable<Int2>,
is_click_to_pay_enabled -> Bool, is_click_to_pay_enabled -> Bool,
authentication_product_ids -> Nullable<Jsonb>, authentication_product_ids -> Nullable<Jsonb>,
three_ds_decision_manager_config -> Nullable<Jsonb>,
} }
} }

View File

@ -1,4 +1,4 @@
use std::fmt; use std::{collections::HashMap, fmt};
use serde::Serialize; use serde::Serialize;
@ -159,3 +159,25 @@ pub enum ValueType {
EnumVariants(Vec<EuclidValue>), EnumVariants(Vec<EuclidValue>),
Number, Number,
} }
impl EuclidAnalysable for common_enums::AuthenticationType {
fn get_dir_value_for_analysis(&self, rule_name: String) -> Vec<(dir::DirValue, Metadata)> {
let auth = self.to_string();
let dir_value = match self {
Self::ThreeDs => dir::DirValue::AuthenticationType(Self::ThreeDs),
Self::NoThreeDs => dir::DirValue::AuthenticationType(Self::NoThreeDs),
};
vec![(
dir_value,
HashMap::from_iter([(
"AUTHENTICATION_TYPE".to_string(),
serde_json::json!({
"rule_name": rule_name,
"Authentication_type": auth,
}),
)]),
)]
}
}

View File

@ -22,6 +22,7 @@ v2 = []
[dependencies] [dependencies]
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
common_enums = { version = "0.1.0", path = "../common_enums" } common_enums = { version = "0.1.0", path = "../common_enums" }
common_types = { version = "0.1.0", path = "../common_types" }
connector_configs = { version = "0.1.0", path = "../connector_configs" } connector_configs = { version = "0.1.0", path = "../connector_configs" }
currency_conversion = { version = "0.1.0", path = "../currency_conversion" } currency_conversion = { version = "0.1.0", path = "../currency_conversion" }
euclid = { version = "0.1.0", path = "../euclid", features = [] } euclid = { version = "0.1.0", path = "../euclid", features = [] }

View File

@ -7,7 +7,7 @@ use std::{
}; };
use api_models::{ use api_models::{
conditional_configs::ConditionalConfigs, enums as api_model_enums, routing::ConnectorSelection, enums as api_model_enums, routing::ConnectorSelection,
surcharge_decision_configs::SurchargeDecisionConfigs, surcharge_decision_configs::SurchargeDecisionConfigs,
}; };
use common_enums::RoutableConnectors; use common_enums::RoutableConnectors;
@ -221,7 +221,7 @@ pub fn get_key_type(key: &str) -> Result<String, String> {
#[wasm_bindgen(js_name = getThreeDsKeys)] #[wasm_bindgen(js_name = getThreeDsKeys)]
pub fn get_three_ds_keys() -> JsResult { pub fn get_three_ds_keys() -> JsResult {
let keys = <ConditionalConfigs as EuclidDirFilter>::ALLOWED; let keys = <common_types::payments::ConditionalConfigs as EuclidDirFilter>::ALLOWED;
Ok(serde_wasm_bindgen::to_value(keys)?) Ok(serde_wasm_bindgen::to_value(keys)?)
} }

View File

@ -734,6 +734,7 @@ pub struct Profile {
pub is_click_to_pay_enabled: bool, pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -777,6 +778,7 @@ pub struct ProfileSetter {
pub is_click_to_pay_enabled: bool, pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -826,6 +828,7 @@ impl From<ProfileSetter> for Profile {
is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_network_tokenization_enabled: value.is_network_tokenization_enabled,
is_click_to_pay_enabled: value.is_click_to_pay_enabled, is_click_to_pay_enabled: value.is_click_to_pay_enabled,
authentication_product_ids: value.authentication_product_ids, authentication_product_ids: value.authentication_product_ids,
three_ds_decision_manager_config: value.three_ds_decision_manager_config,
} }
} }
} }
@ -879,6 +882,7 @@ pub struct ProfileGeneralUpdate {
pub is_click_to_pay_enabled: Option<bool>, pub is_click_to_pay_enabled: Option<bool>,
pub authentication_product_ids: pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>, Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -904,6 +908,9 @@ pub enum ProfileUpdate {
CollectCvvDuringPaymentUpdate { CollectCvvDuringPaymentUpdate {
should_collect_cvv_during_payment: bool, should_collect_cvv_during_payment: bool,
}, },
DecisionManagerRecordUpdate {
three_ds_decision_manager_config: common_types::payments::DecisionManagerRecord,
},
} }
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
@ -939,6 +946,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
is_network_tokenization_enabled, is_network_tokenization_enabled,
is_click_to_pay_enabled, is_click_to_pay_enabled,
authentication_product_ids, authentication_product_ids,
three_ds_decision_manager_config,
} = *update; } = *update;
Self { Self {
profile_name, profile_name,
@ -979,6 +987,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids, authentication_product_ids,
three_ds_decision_manager_config,
} }
} }
ProfileUpdate::RoutingAlgorithmUpdate { ProfileUpdate::RoutingAlgorithmUpdate {
@ -1022,6 +1031,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
}, },
ProfileUpdate::ExtendedCardInfoUpdate { ProfileUpdate::ExtendedCardInfoUpdate {
is_extended_card_info_enabled, is_extended_card_info_enabled,
@ -1063,6 +1073,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
}, },
ProfileUpdate::ConnectorAgnosticMitUpdate { ProfileUpdate::ConnectorAgnosticMitUpdate {
is_connector_agnostic_mit_enabled, is_connector_agnostic_mit_enabled,
@ -1104,6 +1115,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
}, },
ProfileUpdate::DefaultRoutingFallbackUpdate { ProfileUpdate::DefaultRoutingFallbackUpdate {
default_fallback_routing, default_fallback_routing,
@ -1145,6 +1157,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
}, },
ProfileUpdate::NetworkTokenizationUpdate { ProfileUpdate::NetworkTokenizationUpdate {
is_network_tokenization_enabled, is_network_tokenization_enabled,
@ -1186,6 +1199,7 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
}, },
ProfileUpdate::CollectCvvDuringPaymentUpdate { ProfileUpdate::CollectCvvDuringPaymentUpdate {
should_collect_cvv_during_payment, should_collect_cvv_during_payment,
@ -1227,6 +1241,49 @@ impl From<ProfileUpdate> for ProfileUpdateInternal {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: None, is_click_to_pay_enabled: None,
authentication_product_ids: None, authentication_product_ids: None,
three_ds_decision_manager_config: None,
},
ProfileUpdate::DecisionManagerRecordUpdate {
three_ds_decision_manager_config,
} => Self {
profile_name: None,
modified_at: now,
return_url: None,
enable_payment_response_hash: None,
payment_response_hash_key: None,
redirect_to_merchant_with_http_post: None,
webhook_details: None,
metadata: None,
is_recon_enabled: None,
applepay_verified_domains: None,
payment_link_config: None,
session_expiry: None,
authentication_connector_details: None,
payout_link_config: None,
is_extended_card_info_enabled: None,
extended_card_info_config: None,
is_connector_agnostic_mit_enabled: None,
use_billing_as_payment_method_billing: None,
collect_shipping_details_from_wallet_connector: None,
collect_billing_details_from_wallet_connector: None,
outgoing_webhook_custom_http_headers: None,
always_collect_billing_details_from_wallet_connector: None,
always_collect_shipping_details_from_wallet_connector: None,
routing_algorithm_id: None,
payout_routing_algorithm_id: None,
order_fulfillment_time: None,
order_fulfillment_time_origin: None,
frm_routing_algorithm_id: None,
default_fallback_routing: None,
should_collect_cvv_during_payment: None,
tax_connector_id: None,
is_tax_connector_enabled: None,
is_network_tokenization_enabled: None,
is_auto_retries_enabled: None,
max_auto_retries_enabled: None,
is_click_to_pay_enabled: None,
authentication_product_ids: None,
three_ds_decision_manager_config: Some(three_ds_decision_manager_config),
}, },
} }
} }
@ -1288,6 +1345,7 @@ impl super::behaviour::Conversion for Profile {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: self.is_click_to_pay_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled,
authentication_product_ids: self.authentication_product_ids, authentication_product_ids: self.authentication_product_ids,
three_ds_decision_manager_config: self.three_ds_decision_manager_config,
}) })
} }
@ -1358,6 +1416,7 @@ impl super::behaviour::Conversion for Profile {
is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_network_tokenization_enabled: item.is_network_tokenization_enabled,
is_click_to_pay_enabled: item.is_click_to_pay_enabled, is_click_to_pay_enabled: item.is_click_to_pay_enabled,
authentication_product_ids: item.authentication_product_ids, authentication_product_ids: item.authentication_product_ids,
three_ds_decision_manager_config: item.three_ds_decision_manager_config,
}) })
} }
.await .await
@ -1415,6 +1474,7 @@ impl super::behaviour::Conversion for Profile {
max_auto_retries_enabled: None, max_auto_retries_enabled: None,
is_click_to_pay_enabled: self.is_click_to_pay_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled,
authentication_product_ids: self.authentication_product_ids, authentication_product_ids: self.authentication_product_ids,
three_ds_decision_manager_config: self.three_ds_decision_manager_config,
}) })
} }
} }

View File

@ -3798,6 +3798,7 @@ impl ProfileCreateBridge for api::ProfileCreate {
is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_network_tokenization_enabled: self.is_network_tokenization_enabled,
is_click_to_pay_enabled: self.is_click_to_pay_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled,
authentication_product_ids: self.authentication_product_ids, 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_network_tokenization_enabled: self.is_network_tokenization_enabled,
is_click_to_pay_enabled: self.is_click_to_pay_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled,
authentication_product_ids: self.authentication_product_ids, authentication_product_ids: self.authentication_product_ids,
three_ds_decision_manager_config: None,
}, },
))) )))
} }

View File

@ -1,7 +1,11 @@
#[cfg(feature = "v2")]
use api_models::conditional_configs::DecisionManagerRequest;
use api_models::conditional_configs::{ use api_models::conditional_configs::{
DecisionManager, DecisionManagerRecord, DecisionManagerResponse, DecisionManager, DecisionManagerRecord, DecisionManagerResponse,
}; };
use common_utils::ext_traits::StringExt; use common_utils::ext_traits::StringExt;
#[cfg(feature = "v2")]
use common_utils::types::keymanager::KeyManagerState;
use error_stack::ResultExt; use error_stack::ResultExt;
use crate::{ use crate::{
@ -10,15 +14,57 @@ use crate::{
services::api as service_api, services::api as service_api,
types::domain, types::domain,
}; };
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
pub async fn upsert_conditional_config( pub async fn upsert_conditional_config(
_state: SessionState, state: SessionState,
_key_store: domain::MerchantKeyStore, key_store: domain::MerchantKeyStore,
_merchant_account: domain::MerchantAccount, request: DecisionManagerRequest,
_request: DecisionManager, profile: domain::Profile,
) -> RouterResponse<DecisionManagerRecord> { ) -> RouterResponse<common_types::payments::DecisionManagerRecord> {
todo!() 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")] #[cfg(feature = "v1")]
@ -204,6 +250,7 @@ pub async fn delete_conditional_config(
Ok(service_api::ApplicationResponse::StatusOk) Ok(service_api::ApplicationResponse::StatusOk)
} }
#[cfg(feature = "v1")]
pub async fn retrieve_conditional_config( pub async fn retrieve_conditional_config(
state: SessionState, state: SessionState,
merchant_account: domain::MerchantAccount, merchant_account: domain::MerchantAccount,
@ -229,3 +276,27 @@ pub async fn retrieve_conditional_config(
}; };
Ok(service_api::ApplicationResponse::Json(response)) 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))
}

View File

@ -109,7 +109,7 @@ use crate::{
api::{self, ConnectorCallType, ConnectorCommon}, api::{self, ConnectorCallType, ConnectorCommon},
domain, domain,
storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt}, storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt},
transformers::{ForeignInto, ForeignTryInto}, transformers::ForeignTryInto,
}, },
utils::{ utils::{
self, add_apple_pay_flow_metrics, add_connector_http_status_code_metrics, Encode, self, add_apple_pay_flow_metrics, add_connector_http_status_code_metrics, Encode,
@ -1145,7 +1145,7 @@ where
Ok(payment_dsl_data Ok(payment_dsl_data
.payment_attempt .payment_attempt
.authentication_type .authentication_type
.or(output.override_3ds.map(ForeignInto::foreign_into)) .or(output.override_3ds)
.or(Some(storage_enums::AuthenticationType::NoThreeDs))) .or(Some(storage_enums::AuthenticationType::NoThreeDs)))
} }

View File

@ -1,9 +1,4 @@
mod transformers; use api_models::{conditional_configs::DecisionManagerRecord, routing};
use api_models::{
conditional_configs::{ConditionalConfigs, DecisionManagerRecord},
routing,
};
use common_utils::ext_traits::StringExt; use common_utils::ext_traits::StringExt;
use error_stack::ResultExt; use error_stack::ResultExt;
use euclid::backend::{self, inputs as dsl_inputs, EuclidBackend}; use euclid::backend::{self, inputs as dsl_inputs, EuclidBackend};
@ -23,11 +18,11 @@ pub async fn perform_decision_management(
algorithm_ref: routing::RoutingAlgorithmRef, algorithm_ref: routing::RoutingAlgorithmRef,
merchant_id: &common_utils::id_type::MerchantId, merchant_id: &common_utils::id_type::MerchantId,
payment_data: &core_routing::PaymentsDslInput<'_>, payment_data: &core_routing::PaymentsDslInput<'_>,
) -> ConditionalConfigResult<ConditionalConfigs> { ) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id { let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id {
id id
} else { } else {
return Ok(ConditionalConfigs::default()); return Ok(common_types::payments::ConditionalConfigs::default());
}; };
let db = &*state.store; let db = &*state.store;
@ -64,8 +59,8 @@ pub async fn perform_decision_management(
pub fn execute_dsl_and_get_conditional_config( pub fn execute_dsl_and_get_conditional_config(
backend_input: dsl_inputs::BackendInput, backend_input: dsl_inputs::BackendInput,
interpreter: &backend::VirInterpreterBackend<ConditionalConfigs>, interpreter: &backend::VirInterpreterBackend<common_types::payments::ConditionalConfigs>,
) -> ConditionalConfigResult<ConditionalConfigs> { ) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
let routing_output = interpreter let routing_output = interpreter
.execute(backend_input) .execute(backend_input)
.map(|out| out.connector_selection) .map(|out| out.connector_selection)

View File

@ -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,
}
}
}

View File

@ -197,17 +197,6 @@ pub async fn update_merchant_active_algorithm_ref(
Ok(()) 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")] #[cfg(feature = "v1")]
pub async fn update_profile_active_algorithm_ref( pub async fn update_profile_active_algorithm_ref(
db: &dyn StorageInterface, db: &dyn StorageInterface,

View File

@ -1832,7 +1832,12 @@ impl Profile {
&TransactionType::Payment, &TransactionType::Payment,
) )
}, },
))), )))
.service(
web::resource("/decision")
.route(web::put().to(routing::upsert_decision_manager_config))
.route(web::get().to(routing::retrieve_decision_manager_config)),
),
) )
} }
} }

View File

@ -688,7 +688,7 @@ pub async fn retrieve_surcharge_decision_manager_config(
.await .await
} }
#[cfg(feature = "olap")] #[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn upsert_decision_manager_config( pub async fn upsert_decision_manager_config(
state: web::Data<AppState>, state: web::Data<AppState>,
@ -726,6 +726,44 @@ pub async fn upsert_decision_manager_config(
.await .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")] #[cfg(feature = "olap")]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn delete_decision_manager_config( pub async fn delete_decision_manager_config(
@ -762,6 +800,40 @@ pub async fn delete_decision_manager_config(
.await .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")] #[cfg(feature = "olap")]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn retrieve_decision_manager_config( pub async fn retrieve_decision_manager_config(

View File

@ -45,7 +45,7 @@ generate_permissions! {
}, },
ThreeDsDecisionManager: { ThreeDsDecisionManager: {
scopes: [Read, Write], scopes: [Read, Write],
entities: [Merchant] entities: [Merchant, Profile]
}, },
SurchargeDecisionManager: { SurchargeDecisionManager: {
scopes: [Read, Write], scopes: [Read, Write],

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE business_profile
DROP COLUMN IF EXISTS three_ds_decision_manager_config;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE business_profile
ADD COLUMN IF NOT EXISTS three_ds_decision_manager_config jsonb;