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
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
112 changes: 111 additions & 1 deletion crates/common_types/src/payments.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
//! Payment related types

use common_enums::enums;
use common_utils::{impl_to_sql_from_sql_json, types::MinorUnit};
use common_utils::{events, impl_to_sql_from_sql_json, types::MinorUnit};
use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow};
use euclid::{
dssa::types::EuclidAnalysable,
frontend::{
ast::Program,
dir::{DirKeyKind, DirValue, EuclidDirFilter},
},
types::Metadata,
};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

Expand Down Expand Up @@ -38,3 +46,105 @@ pub struct StripeSplitPaymentRequest {
pub transfer_account_id: String,
}
impl_to_sql_from_sql_json!(StripeSplitPaymentRequest);

#[derive(
Clone,
Debug,
Hash,
PartialEq,
Eq,
strum::Display,
strum::VariantNames,
strum::EnumIter,
strum::EnumString,
Serialize,
Deserialize,
FromSqlRow,
AsExpression,
)]
#[diesel(sql_type = Jsonb)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
/// AuthenticationType
pub enum AuthenticationType {
Narayanbhat166 marked this conversation as resolved.
Show resolved Hide resolved
/// If the card is enrolled for 3DS authentication, the 3DS based authentication will be activated. The liability of chargeback shift to the issuer
ThreeDs,
/// 3DS based authentication will not be activated. The liability of chargeback stays with the merchant.
NoThreeDs,
}

impl_to_sql_from_sql_json!(AuthenticationType);

impl AuthenticationType {
/// Convert to DirValue
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(
Serialize, Default, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
/// ConditionalConfigs
pub struct ConditionalConfigs {
/// Override 3DS
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,
];
}

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;
8 changes: 8 additions & 0 deletions crates/common_utils/src/id_type/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ impl From<ProfileId> for router_env::opentelemetry::Value {
Self::from(val.0 .0 .0)
}
}

impl ProfileId {
#[cfg(feature = "v2")]
/// get_payment_config_routing_id
pub fn get_payment_config_routing_id(&self) -> String {
Narayanbhat166 marked this conversation as resolved.
Show resolved Hide resolved
format!("payment_config_id_{}", self.get_string_repr())
}
}
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 @@ -306,6 +306,7 @@ pub struct Profile {
pub max_auto_retries_enabled: Option<i16>,
pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: Option<serde_json::Value>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

impl Profile {
Expand Down Expand Up @@ -366,6 +367,7 @@ pub struct ProfileNew {
pub max_auto_retries_enabled: Option<i16>,
pub is_click_to_pay_enabled: bool,
pub authentication_product_ids: Option<serde_json::Value>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

#[cfg(feature = "v2")]
Expand Down Expand Up @@ -410,6 +412,7 @@ pub struct ProfileUpdateInternal {
pub max_auto_retries_enabled: Option<i16>,
pub is_click_to_pay_enabled: Option<bool>,
pub authentication_product_ids: Option<serde_json::Value>,
pub three_ds_decision_manager_config: Option<common_types::payments::DecisionManagerRecord>,
}

#[cfg(feature = "v2")]
Expand Down Expand Up @@ -453,6 +456,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 @@ -521,6 +525,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
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