Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 20 additions & 76 deletions crates/api_models/src/conditional_configs.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,10 @@
use common_utils::events;
use euclid::{
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,
];
}
use euclid::frontend::ast::Program;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DecisionManagerRecord {
pub name: String,
pub program: Program<ConditionalConfigs>,
pub program: Program<common_types::payments::ConditionalConfigs>,
pub created_at: i64,
pub modified_at: i64,
}
Expand All @@ -89,12 +17,14 @@ impl events::ApiEventMetric for DecisionManagerRecord {
#[serde(deny_unknown_fields)]
pub struct ConditionalConfigReq {
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)]
pub struct DecisionManagerRequest {
pub name: Option<String>,
pub program: Option<Program<ConditionalConfigs>>,
pub program: Option<Program<common_types::payments::ConditionalConfigs>>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand All @@ -111,3 +41,17 @@ impl events::ApiEventMetric for DecisionManager {
}

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)
}
}
2 changes: 2 additions & 0 deletions crates/common_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ v2 = ["common_utils/v2"]
diesel = "2.2.3"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115"
strum = { version = "0.26", features = ["derive"] }
utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] }

common_enums = { version = "0.1.0", path = "../common_enums" }
common_utils = { version = "0.1.0", path = "../common_utils"}
euclid = { version = "0.1.0", path = "../euclid" }

[lints]
workspace = true
89 changes: 70 additions & 19 deletions crates/common_types/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
use std::collections::HashMap;

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 euclid::frontend::{
ast::Program,
dir::{DirKeyKind, EuclidDirFilter},
};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

Expand All @@ -21,6 +25,31 @@ pub enum 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)]
/// Hashmap to store mca_id's with product names
pub struct AuthenticationConnectorAccountMap(
HashMap<enums::AuthenticationProduct, common_utils::id_type::MerchantConnectorAccountId>,
);
impl_to_sql_from_sql_json!(AuthenticationConnectorAccountMap);

impl AuthenticationConnectorAccountMap {
/// fn to get click to pay connector_account_id
pub fn get_click_to_pay_connector_account_id(
&self,
) -> Result<common_utils::id_type::MerchantConnectorAccountId, errors::ValidationError> {
self.0
.get(&enums::AuthenticationProduct::ClickToPay)
.ok_or(errors::ValidationError::MissingRequiredField {
field_name: "authentication_product_id.click_to_pay".to_string(),
})
.cloned()
}
}

#[derive(
Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
Expand All @@ -42,26 +71,48 @@ pub struct StripeSplitPaymentRequest {
impl_to_sql_from_sql_json!(StripeSplitPaymentRequest);

#[derive(
Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
Serialize, Default, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
#[serde(deny_unknown_fields)]
/// Hashmap to store mca_id's with product names
pub struct AuthenticationConnectorAccountMap(
HashMap<enums::AuthenticationProduct, common_utils::id_type::MerchantConnectorAccountId>,
);
impl_to_sql_from_sql_json!(AuthenticationConnectorAccountMap);
/// 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 AuthenticationConnectorAccountMap {
/// fn to get click to pay connector_account_id
pub fn get_click_to_pay_connector_account_id(
&self,
) -> Result<common_utils::id_type::MerchantConnectorAccountId, errors::ValidationError> {
self.0
.get(&enums::AuthenticationProduct::ClickToPay)
.ok_or(errors::ValidationError::MissingRequiredField {
field_name: "authentication_product_id.click_to_pay".to_string(),
})
.cloned()
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;
6 changes: 6 additions & 0 deletions crates/diesel_models/src/business_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ pub struct Profile {
pub is_click_to_pay_enabled: bool,
pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

impl Profile {
Expand Down Expand Up @@ -371,6 +372,7 @@ pub struct ProfileNew {
pub is_click_to_pay_enabled: bool,
pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

#[cfg(feature = "v2")]
Expand Down Expand Up @@ -416,6 +418,7 @@ pub struct ProfileUpdateInternal {
pub is_click_to_pay_enabled: Option<bool>,
pub authentication_product_ids:
Option<common_types::payments::AuthenticationConnectorAccountMap>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

#[cfg(feature = "v2")]
Expand Down Expand Up @@ -459,6 +462,7 @@ impl ProfileUpdateInternal {
max_auto_retries_enabled,
is_click_to_pay_enabled,
authentication_product_ids,
three_ds_decision_manager_config,
} = self;
Profile {
id: source.id,
Expand Down Expand Up @@ -527,6 +531,8 @@ impl ProfileUpdateInternal {
.unwrap_or(source.is_click_to_pay_enabled),
authentication_product_ids: 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),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ diesel::table! {
max_auto_retries_enabled -> Nullable<Int2>,
is_click_to_pay_enabled -> Bool,
authentication_product_ids -> Nullable<Jsonb>,
three_ds_decision_manager_config -> Nullable<Jsonb>,
}
}

Expand Down
24 changes: 23 additions & 1 deletion crates/euclid/src/dssa/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt;
use std::{collections::HashMap, fmt};

use serde::Serialize;

Expand Down Expand Up @@ -159,3 +159,25 @@ pub enum ValueType {
EnumVariants(Vec<EuclidValue>),
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,
}),
)]),
)]
}
}
1 change: 1 addition & 0 deletions crates/euclid_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ v2 = []
[dependencies]
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
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" }
currency_conversion = { version = "0.1.0", path = "../currency_conversion" }
euclid = { version = "0.1.0", path = "../euclid", features = [] }
Expand Down
4 changes: 2 additions & 2 deletions crates/euclid_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
};

use api_models::{
conditional_configs::ConditionalConfigs, enums as api_model_enums, routing::ConnectorSelection,
enums as api_model_enums, routing::ConnectorSelection,
surcharge_decision_configs::SurchargeDecisionConfigs,
};
use common_enums::RoutableConnectors;
Expand Down Expand Up @@ -221,7 +221,7 @@ pub fn get_key_type(key: &str) -> Result<String, String> {

#[wasm_bindgen(js_name = getThreeDsKeys)]
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)?)
}

Expand Down
Loading
Loading