diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dc4010d0fd..6ad86dd4cc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,6 +18,7 @@ crates/router/src/services/ @juspay/hyperswitch-framework crates/router/src/db/ @juspay/hyperswitch-framework crates/router/src/routes/ @juspay/hyperswitch-framework migrations/ @juspay/hyperswitch-framework +v2_migrations/ @juspay/hyperswitch-framework api-reference/ @juspay/hyperswitch-framework api-reference-v2/ @juspay/hyperswitch-framework Cargo.toml @juspay/hyperswitch-framework diff --git a/Cargo.lock b/Cargo.lock index 4737fff7aa..9e39e50ff5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2039,8 +2039,10 @@ dependencies = [ "common_enums", "common_utils", "diesel", + "euclid", "serde", "serde_json", + "strum 0.26.3", "utoipa", ] @@ -3067,6 +3069,7 @@ version = "0.1.0" dependencies = [ "api_models", "common_enums", + "common_types", "connector_configs", "currency_conversion", "euclid", diff --git a/crates/api_models/src/conditional_configs.rs b/crates/api_models/src/conditional_configs.rs index 3aed34e47a..ac7170bbdf 100644 --- a/crates/api_models/src/conditional_configs.rs +++ b/crates/api_models/src/conditional_configs.rs @@ -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, -} -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, + pub program: Program, pub created_at: i64, pub modified_at: i64, } @@ -89,12 +17,14 @@ impl events::ApiEventMetric for DecisionManagerRecord { #[serde(deny_unknown_fields)] pub struct ConditionalConfigReq { pub name: Option, - pub algorithm: Option>, + pub algorithm: Option>, } + +#[cfg(feature = "v1")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DecisionManagerRequest { pub name: Option, - pub program: Option>, + pub program: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -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, +} + +#[cfg(feature = "v2")] +impl events::ApiEventMetric for DecisionManagerRequest { + fn get_api_event_type(&self) -> Option { + Some(events::ApiEventsType::Routing) + } +} diff --git a/crates/common_types/Cargo.toml b/crates/common_types/Cargo.toml index 33dd799f0f..3f49f546a7 100644 --- a/crates/common_types/Cargo.toml +++ b/crates/common_types/Cargo.toml @@ -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 diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index 0807560562..ea42fbf7a4 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -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; @@ -21,26 +25,6 @@ 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)] -/// 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, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -65,3 +49,70 @@ impl AuthenticationConnectorAccountMap { .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, +} +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, + /// Created at timestamp + pub created_at: i64, +} + +impl events::ApiEventMetric for DecisionManagerRecord { + fn get_api_event_type(&self) -> Option { + Some(events::ApiEventsType::Routing) + } +} +impl_to_sql_from_sql_json!(DecisionManagerRecord); + +/// DecisionManagerResponse +pub type DecisionManagerResponse = DecisionManagerRecord; diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 06aa21fe9d..2cbd7deb42 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -310,6 +310,7 @@ pub struct Profile { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } impl Profile { @@ -371,6 +372,7 @@ pub struct ProfileNew { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -416,6 +418,7 @@ pub struct ProfileUpdateInternal { pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -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, @@ -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), } } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 9b1c3aa7d4..5bd3195f43 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -224,6 +224,7 @@ diesel::table! { max_auto_retries_enabled -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, + three_ds_decision_manager_config -> Nullable, } } diff --git a/crates/euclid/src/dssa/types.rs b/crates/euclid/src/dssa/types.rs index f8340c3150..54e1820f0e 100644 --- a/crates/euclid/src/dssa/types.rs +++ b/crates/euclid/src/dssa/types.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{collections::HashMap, fmt}; use serde::Serialize; @@ -159,3 +159,25 @@ pub enum ValueType { EnumVariants(Vec), 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, + }), + )]), + )] + } +} diff --git a/crates/euclid_wasm/Cargo.toml b/crates/euclid_wasm/Cargo.toml index 4f967df34a..9c22ed6231 100644 --- a/crates/euclid_wasm/Cargo.toml +++ b/crates/euclid_wasm/Cargo.toml @@ -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 = [] } diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index b299ced68b..f977fc4554 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -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; @@ -221,7 +221,7 @@ pub fn get_key_type(key: &str) -> Result { #[wasm_bindgen(js_name = getThreeDsKeys)] pub fn get_three_ds_keys() -> JsResult { - let keys = ::ALLOWED; + let keys = ::ALLOWED; Ok(serde_wasm_bindgen::to_value(keys)?) } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 47c505e8ca..3e49a596d8 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -734,6 +734,7 @@ pub struct Profile { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -777,6 +778,7 @@ pub struct ProfileSetter { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -826,6 +828,7 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_click_to_pay_enabled: value.is_click_to_pay_enabled, 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, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -904,6 +908,9 @@ pub enum ProfileUpdate { CollectCvvDuringPaymentUpdate { should_collect_cvv_during_payment: bool, }, + DecisionManagerRecordUpdate { + three_ds_decision_manager_config: common_types::payments::DecisionManagerRecord, + }, } #[cfg(feature = "v2")] @@ -939,6 +946,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_click_to_pay_enabled, authentication_product_ids, + three_ds_decision_manager_config, } = *update; Self { profile_name, @@ -979,6 +987,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids, + three_ds_decision_manager_config, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -1022,6 +1031,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -1063,6 +1073,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -1104,6 +1115,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::DefaultRoutingFallbackUpdate { default_fallback_routing, @@ -1145,6 +1157,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -1186,6 +1199,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::CollectCvvDuringPaymentUpdate { should_collect_cvv_during_payment, @@ -1227,6 +1241,49 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: 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, is_click_to_pay_enabled: self.is_click_to_pay_enabled, 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_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, + three_ds_decision_manager_config: item.three_ds_decision_manager_config, }) } .await @@ -1415,6 +1474,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + three_ds_decision_manager_config: self.three_ds_decision_manager_config, }) } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 356b4a31cc..3ec9d76397 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -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, }, ))) } diff --git a/crates/router/src/core/conditional_config.rs b/crates/router/src/core/conditional_config.rs index 9ef409268f..66ad750bbb 100644 --- a/crates/router/src/core/conditional_config.rs +++ b/crates/router/src/core/conditional_config.rs @@ -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 { - todo!() + state: SessionState, + key_store: domain::MerchantKeyStore, + request: DecisionManagerRequest, + profile: domain::Profile, +) -> RouterResponse { + 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 { + 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)) +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 47d2024834..33540cf0b4 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -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))) } diff --git a/crates/router/src/core/payments/conditional_configs.rs b/crates/router/src/core/payments/conditional_configs.rs index cc6cc9cd74..d511c0fd6a 100644 --- a/crates/router/src/core/payments/conditional_configs.rs +++ b/crates/router/src/core/payments/conditional_configs.rs @@ -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 { +) -> ConditionalConfigResult { 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, -) -> ConditionalConfigResult { + interpreter: &backend::VirInterpreterBackend, +) -> ConditionalConfigResult { let routing_output = interpreter .execute(backend_input) .map(|out| out.connector_selection) diff --git a/crates/router/src/core/payments/conditional_configs/transformers.rs b/crates/router/src/core/payments/conditional_configs/transformers.rs deleted file mode 100644 index 023bd65dcf..0000000000 --- a/crates/router/src/core/payments/conditional_configs/transformers.rs +++ /dev/null @@ -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 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 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, - } - } -} diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index ca14e3e7e4..84de32483a 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -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, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 17ab056e85..7f7ea76710 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -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)), + ), ) } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 76852bf12e..e7227e1f75 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -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, @@ -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, + req: HttpRequest, + json_payload: web::Json, +) -> 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, + 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( diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 04d3d43673..5396fff289 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -45,7 +45,7 @@ generate_permissions! { }, ThreeDsDecisionManager: { scopes: [Read, Write], - entities: [Merchant] + entities: [Merchant, Profile] }, SurchargeDecisionManager: { scopes: [Read, Write], diff --git a/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql new file mode 100644 index 0000000000..a5295472f1 --- /dev/null +++ b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql @@ -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; \ No newline at end of file diff --git a/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql new file mode 100644 index 0000000000..e329a39f1a --- /dev/null +++ b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE business_profile +ADD COLUMN IF NOT EXISTS three_ds_decision_manager_config jsonb; \ No newline at end of file