From c284f41cc685b4a5093be12ec4b5e4b503de82b5 Mon Sep 17 00:00:00 2001 From: rishavkar <73836104+rishavkar@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:15:26 +0530 Subject: [PATCH] feat(core): Changed frm_config format type in merchant_connector_account and added frm_message in payments response (#1543) Co-authored-by: Jagan Elavarasan Co-authored-by: Sampras Lopes Co-authored-by: Sampras Lopes --- crates/api_models/src/admin.rs | 84 ++++++----- crates/api_models/src/enums.rs | 1 + crates/api_models/src/payments.rs | 15 ++ crates/common_utils/src/consts.rs | 5 + crates/diesel_models/src/enums.rs | 44 ++++++ crates/diesel_models/src/fraud_check.rs | 105 +++++++++++++ crates/diesel_models/src/lib.rs | 2 + .../src/merchant_connector_account.rs | 9 +- crates/diesel_models/src/query.rs | 1 + crates/diesel_models/src/query/fraud_check.rs | 57 +++++++ crates/diesel_models/src/schema.rs | 33 +++- crates/router/src/core/admin.rs | 40 ++--- .../router/src/core/payment_methods/vault.rs | 2 +- crates/router/src/core/payments.rs | 2 + .../payments/operations/payment_cancel.rs | 1 + .../payments/operations/payment_capture.rs | 1 + .../operations/payment_complete_authorize.rs | 1 + .../payments/operations/payment_confirm.rs | 1 + .../payments/operations/payment_create.rs | 1 + .../operations/payment_method_validate.rs | 1 + .../payments/operations/payment_session.rs | 1 + .../core/payments/operations/payment_start.rs | 1 + .../payments/operations/payment_status.rs | 20 +++ .../payments/operations/payment_update.rs | 1 + .../router/src/core/payments/transformers.rs | 9 +- crates/router/src/db.rs | 2 + crates/router/src/db/fraud_check.rs | 112 ++++++++++++++ crates/router/src/openapi.rs | 3 + .../src/scheduler/workflows/api_key_expiry.rs | 1 + crates/router/src/services/api.rs | 2 +- crates/router/src/types/api.rs | 5 + .../domain/merchant_connector_account.rs | 4 +- crates/router/src/types/transformers.rs | 20 ++- .../down.sql | 3 + .../up.sql | 7 + .../down.sql | 3 + .../up.sql | 35 +++++ openapi/openapi_spec.json | 141 ++++++++++++++---- 38 files changed, 666 insertions(+), 110 deletions(-) create mode 100644 crates/diesel_models/src/fraud_check.rs create mode 100644 crates/diesel_models/src/query/fraud_check.rs create mode 100644 crates/router/src/db/fraud_check.rs create mode 100644 migrations/2023-06-22-161131_change-type-of-frm-configs.sql/down.sql create mode 100644 migrations/2023-06-22-161131_change-type-of-frm-configs.sql/up.sql create mode 100644 migrations/2023-07-17-111427_add-fraud-check-table.sql/down.sql create mode 100644 migrations/2023-07-17-111427_add-fraud-check-table.sql/up.sql diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index ec68111dd0..17fe2336d5 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -8,7 +8,10 @@ use url; use utoipa::ToSchema; use super::payments::AddressDetails; -use crate::{enums as api_enums, payment_methods}; +use crate::{ + enums::{self as api_enums}, + payment_methods, +}; #[derive(Clone, Debug, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] @@ -592,16 +595,8 @@ pub struct MerchantConnectorCreate { #[schema(value_type = Option,max_length = 255,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, /// contains the frm configs for the merchant connector - #[schema(example = json!([ - { - "frm_enabled_pms" : ["card"], - "frm_enabled_pm_types" : ["credit"], - "frm_enabled_gateways" : ["stripe"], - "frm_action": "cancel_txn", - "frm_preferred_flow_type" : "pre" - } - ]))] - pub frm_configs: Option, + #[schema(example = json!(common_utils::consts::FRM_CONFIGS_EG))] + pub frm_configs: Option>, /// Business Country of the connector #[cfg(feature = "multiple_mca")] @@ -714,16 +709,8 @@ pub struct MerchantConnectorResponse { pub business_sub_label: Option, /// contains the frm configs for the merchant connector - #[schema(example = json!([ - { - "frm_enabled_pms" : ["card"], - "frm_enabled_pm_types" : ["credit"], - "frm_enabled_gateways" : ["stripe"], - "frm_action": "cancel_txn", - "frm_preferred_flow_type" : "pre" - } - ]))] - pub frm_configs: Option, + #[schema(example = json!(common_utils::consts::FRM_CONFIGS_EG))] + pub frm_configs: Option>, /// Webhook details of this merchant connector #[schema(example = json!({ @@ -791,16 +778,8 @@ pub struct MerchantConnectorUpdate { pub metadata: Option, /// contains the frm configs for the merchant connector - #[schema(example = json!([ - { - "frm_enabled_pms" : ["card"], - "frm_enabled_pm_types" : ["credit"], - "frm_enabled_gateways" : ["stripe"], - "frm_action": "cancel_txn", - "frm_preferred_flow_type" : "pre" - } - ]))] - pub frm_configs: Option, + #[schema(example = json!(common_utils::consts::FRM_CONFIGS_EG))] + pub frm_configs: Option>, /// Webhook details of this merchant connector #[schema(example = json!({ @@ -815,15 +794,40 @@ pub struct MerchantConnectorUpdate { #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] pub struct FrmConfigs { - pub frm_enabled_pms: Option>, - pub frm_enabled_pm_types: Option>, - pub frm_enabled_gateways: Option>, - /// What should be the action if FRM declines the txn (autorefund/cancel txn/manual review) - #[schema(value_type = FrmAction)] - pub frm_action: api_enums::FrmAction, - /// Whether to make a call to the FRM before or after the payment + ///this is the connector that can be used for the payment + #[schema(value_type = ConnectorType, example = "payment_processor")] + pub gateway: Option, + ///payment methods that can be used in the payment + pub payment_methods: Vec, +} + +///Details of FrmPaymentMethod are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct FrmPaymentMethod { + ///payment methods(card, wallet, etc) that can be used in the payment + #[schema(value_type = PaymentMethod,example = "card")] + pub payment_method: Option, + ///payment method types(credit, debit) that can be used in the payment + pub payment_method_types: Vec, +} + +///Details of FrmPaymentMethodType are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct FrmPaymentMethodType { + ///payment method types(credit, debit) that can be used in the payment + #[schema(value_type = PaymentMethodType)] + pub payment_method_type: Option, + ///card networks(like visa mastercard) types that can be used in the payment + #[schema(value_type = CardNetwork)] + pub card_networks: Option>, + ///frm flow type to be used...can be pre/post #[schema(value_type = FrmPreferredFlowTypes)] - pub frm_preferred_flow_type: api_enums::FrmPreferredFlowTypes, + pub flow: api_enums::FrmPreferredFlowTypes, + ///action that the frm would take, in case fraud is detected + #[schema(value_type = FrmAction)] + pub action: api_enums::FrmAction, } /// Details of all the payment methods enabled for the connector for the given merchant account #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] @@ -831,7 +835,7 @@ pub struct FrmConfigs { pub struct PaymentMethodsEnabled { /// Type of payment method. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, + pub payment_method: common_enums::PaymentMethod, /// Subtype of payment method #[schema(value_type = Option>,example = json!(["credit"]))] diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index dc81db2ca6..129472c9d0 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -116,6 +116,7 @@ pub enum Connector { Worldline, Worldpay, Zen, + Signifyd, } impl Connector { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 4c30d190c7..23b95cd8cc 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1636,6 +1636,9 @@ pub struct PaymentsResponse { #[schema(value_type = Option, example = "993672945374576J")] pub connector_transaction_id: Option, + /// Frm message contains information about the frm response + pub frm_message: Option, + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] pub metadata: Option, @@ -2356,6 +2359,18 @@ pub struct FeatureMetadata { pub redirect_response: Option, } +///frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct FrmMessage { + pub frm_name: String, + pub frm_transaction_id: Option, + pub frm_transaction_type: Option, + pub frm_status: Option, + pub frm_score: Option, + pub frm_reason: Option, + pub frm_error: Option, +} + mod payment_id_type { use std::fmt; diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index c3179a2f24..947b880e11 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -13,3 +13,8 @@ pub(crate) const ALPHABETS: [char; 62] = [ /// TTL for token pub const TOKEN_TTL: i64 = 900; + +///an example of the frm_configs json +pub static FRM_CONFIGS_EG: &str = r#" +[{"gateway":"stripe","payment_methods":[{"payment_method":"card","payment_method_types":[{"payment_method_type":"credit","card_networks":["Visa"],"flow":"pre","action":"cancel_txn"},{"payment_method_type":"debit","card_networks":["Visa"],"flow":"pre"}]}]}] +"#; diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 5ba1912b6d..fb6dd585c1 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -6,6 +6,7 @@ pub mod diesel_exports { DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventClass as EventClass, DbEventObjectType as EventObjectType, DbEventType as EventType, + DbFraudCheckStatus as FraudCheckStatus, DbFraudCheckType as FraudCheckType, DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus, DbMandateType as MandateType, DbMerchantStorageScheme as MerchantStorageScheme, @@ -312,3 +313,46 @@ pub enum BankNames { TheSiamCommercialBank, KasikornBank, } + +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, +)] +#[router_derive::diesel_enum(storage_type = "pg_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum FraudCheckType { + PreFrm, + PostFrm, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + frunk::LabelledGeneric, +)] +#[router_derive::diesel_enum(storage_type = "pg_enum")] +#[strum(serialize_all = "snake_case")] +pub enum FraudCheckStatus { + Fraud, + ManualReview, + #[default] + Pending, + Legit, + TransactionFailure, +} diff --git a/crates/diesel_models/src/fraud_check.rs b/crates/diesel_models/src/fraud_check.rs new file mode 100644 index 0000000000..00ecfd3cc6 --- /dev/null +++ b/crates/diesel_models/src/fraud_check.rs @@ -0,0 +1,105 @@ +use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; +use masking::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{ + enums::{FraudCheckStatus, FraudCheckType}, + schema::fraud_check, +}; +#[derive(Clone, Debug, Identifiable, Queryable, Serialize, Deserialize)] +#[diesel(table_name = fraud_check, primary_key(payment_id, merchant_id))] +pub struct FraudCheck { + pub frm_id: String, + pub payment_id: String, + pub merchant_id: String, + pub attempt_id: String, + pub created_at: PrimitiveDateTime, + pub frm_name: String, + pub frm_transaction_id: Option, + pub frm_transaction_type: FraudCheckType, + pub frm_status: FraudCheckStatus, + pub frm_score: Option, + pub frm_reason: Option, + pub frm_error: Option, + pub payment_details: Option, + pub metadata: Option, + pub modified_at: PrimitiveDateTime, +} + +#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)] +#[diesel(table_name = fraud_check)] +pub struct FraudCheckNew { + pub frm_id: String, + pub payment_id: String, + pub merchant_id: String, + pub attempt_id: String, + pub created_at: PrimitiveDateTime, + pub frm_name: String, + pub frm_transaction_id: Option, + pub frm_transaction_type: FraudCheckType, + pub frm_status: FraudCheckStatus, + pub frm_score: Option, + pub frm_reason: Option, + pub frm_error: Option, + pub payment_details: Option, + pub metadata: Option, + pub modified_at: PrimitiveDateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum FraudCheckUpdate { + //Refer PaymentAttemptUpdate for other variants implementations + ResponseUpdate { + frm_status: FraudCheckStatus, + frm_transaction_id: Option, + frm_reason: Option, + frm_score: Option, + metadata: Option, + modified_at: PrimitiveDateTime, + }, + ErrorUpdate { + status: FraudCheckStatus, + error_message: Option>, + }, +} + +#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] +#[diesel(table_name = fraud_check)] +pub struct FraudCheckUpdateInternal { + frm_status: Option, + frm_transaction_id: Option, + frm_reason: Option, + frm_score: Option, + frm_error: Option>, + metadata: Option, +} + +impl From for FraudCheckUpdateInternal { + fn from(fraud_check_update: FraudCheckUpdate) -> Self { + match fraud_check_update { + FraudCheckUpdate::ResponseUpdate { + frm_status, + frm_transaction_id, + frm_reason, + frm_score, + metadata, + modified_at: _, + } => Self { + frm_status: Some(frm_status), + frm_transaction_id, + frm_reason, + frm_score, + metadata, + ..Default::default() + }, + FraudCheckUpdate::ErrorUpdate { + status, + error_message, + } => Self { + frm_status: Some(status), + frm_error: error_message, + ..Default::default() + }, + } + } +} diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index b62d751e61..6f277d9094 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -11,6 +11,8 @@ pub mod ephemeral_key; pub mod errors; pub mod events; pub mod file; +#[allow(unused)] +pub mod fraud_check; #[cfg(feature = "kv_store")] pub mod kv; pub mod locker_mock_up; diff --git a/crates/diesel_models/src/merchant_connector_account.rs b/crates/diesel_models/src/merchant_connector_account.rs index 135e1eda62..9982fb7593 100644 --- a/crates/diesel_models/src/merchant_connector_account.rs +++ b/crates/diesel_models/src/merchant_connector_account.rs @@ -32,7 +32,8 @@ pub struct MerchantConnectorAccount { pub business_country: storage_enums::CountryAlpha2, pub business_label: String, pub business_sub_label: Option, - pub frm_configs: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub frm_configs: Option>>, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, pub connector_webhook_details: Option, @@ -54,7 +55,8 @@ pub struct MerchantConnectorAccountNew { pub business_country: storage_enums::CountryAlpha2, pub business_label: String, pub business_sub_label: Option, - pub frm_configs: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub frm_configs: Option>>, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, pub connector_webhook_details: Option, @@ -72,7 +74,8 @@ pub struct MerchantConnectorAccountUpdateInternal { pub merchant_connector_id: Option, pub payment_methods_enabled: Option>, pub metadata: Option, - pub frm_configs: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub frm_configs: Option>>, pub modified_at: Option, pub connector_webhook_details: Option, } diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index 3e9ad77fc5..42825a983e 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -7,6 +7,7 @@ pub mod customers; pub mod dispute; pub mod events; pub mod file; +pub mod fraud_check; pub mod generics; pub mod locker_mock_up; pub mod mandate; diff --git a/crates/diesel_models/src/query/fraud_check.rs b/crates/diesel_models/src/query/fraud_check.rs new file mode 100644 index 0000000000..a0e939e5d0 --- /dev/null +++ b/crates/diesel_models/src/query/fraud_check.rs @@ -0,0 +1,57 @@ +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use router_env::tracing::{self, instrument}; + +use crate::{ + errors, fraud_check::*, query::generics, schema::fraud_check::dsl, PgPooledConn, StorageResult, +}; + +impl FraudCheckNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl FraudCheck { + #[instrument(skip(conn))] + pub async fn update_with_attempt_id( + self, + conn: &PgPooledConn, + fraud_check: FraudCheckUpdate, + ) -> StorageResult { + match generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + dsl::attempt_id + .eq(self.attempt_id.to_owned()) + .and(dsl::merchant_id.eq(self.merchant_id.to_owned())), + FraudCheckUpdateInternal::from(fraud_check), + ) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NoFieldsToUpdate => Ok(self), + _ => Err(error), + }, + result => result, + } + } + + pub async fn get_with_payment_id( + conn: &PgPooledConn, + payment_id: String, + merchant_id: String, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::payment_id + .eq(payment_id) + .and(dsl::merchant_id.eq(merchant_id)), + ) + .await + } +} diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index b1cbb88e07..9a1eeeac45 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -220,6 +220,36 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + fraud_check (frm_id, attempt_id, payment_id, merchant_id) { + #[max_length = 64] + frm_id -> Varchar, + #[max_length = 64] + payment_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + attempt_id -> Varchar, + created_at -> Timestamp, + #[max_length = 255] + frm_name -> Varchar, + #[max_length = 255] + frm_transaction_id -> Nullable, + frm_transaction_type -> FraudCheckType, + frm_status -> FraudCheckStatus, + frm_score -> Nullable, + frm_reason -> Nullable, + #[max_length = 255] + frm_error -> Nullable, + payment_details -> Nullable, + metadata -> Nullable, + modified_at -> Timestamp, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -361,7 +391,7 @@ diesel::table! { business_label -> Varchar, #[max_length = 64] business_sub_label -> Nullable, - frm_configs -> Nullable, + frm_configs -> Nullable>>, created_at -> Timestamp, modified_at -> Timestamp, connector_webhook_details -> Nullable, @@ -695,6 +725,7 @@ diesel::allow_tables_to_appear_in_same_query!( dispute, events, file_metadata, + fraud_check, locker_mock_up, mandate, merchant_account, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 6db72e93f4..c8efdf5397 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -494,15 +494,7 @@ pub async fn create_payment_connector( expected_format: "auth_type and api_key".to_string(), })?; - let frm_configs = match req.frm_configs { - Some(frm_value) => { - let configs_for_frm_value: serde_json::Value = - utils::Encode::::encode_to_value(&frm_value) - .change_context(errors::ApiErrorResponse::ConfigNotFound)?; - Some(Secret::new(configs_for_frm_value)) - } - None => None, - }; + let frm_configs = get_frm_config_as_secret(req.frm_configs); let merchant_connector_account = domain::MerchantConnectorAccount { merchant_id: merchant_id.to_string(), @@ -672,15 +664,7 @@ pub async fn update_payment_connector( .collect::>() }); - let frm_configs = match req.frm_configs.as_ref() { - Some(frm_value) => { - let configs_for_frm_value: serde_json::Value = - utils::Encode::::encode_to_value(&frm_value) - .change_context(errors::ApiErrorResponse::ConfigNotFound)?; - Some(Secret::new(configs_for_frm_value)) - } - None => None, - }; + let frm_configs = get_frm_config_as_secret(req.frm_configs); let payment_connector = storage::MerchantConnectorAccountUpdate::Update { merchant_id: None, @@ -855,3 +839,23 @@ pub async fn check_merchant_account_kv_status( }, )) } + +pub fn get_frm_config_as_secret( + frm_configs: Option>, +) -> Option>> { + match frm_configs.as_ref() { + Some(frm_value) => { + let configs_for_frm_value: Vec> = frm_value + .iter() + .map(|config| { + utils::Encode::::encode_to_value(&config) + .change_context(errors::ApiErrorResponse::ConfigNotFound) + .map(masking::Secret::new) + }) + .collect::, _>>() + .ok()?; + Some(configs_for_frm_value) + } + None => None, + } +} diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index c93d83e0bb..6dca567567 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1051,7 +1051,7 @@ pub async fn delete_tokenized_data( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error serializing api::DeleteTokenizeByTokenRequest")?; - let (public_key, _private_key) = get_locker_jwe_keys(&state.kms_secrets) + let (public_key, _private_key) = get_locker_jwe_keys(&state.kms_secrets.clone()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error getting Encryption key")?; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 1a2aafd272..47af7e8d04 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -8,6 +8,7 @@ pub mod transformers; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; +use api_models::payments::FrmMessage; use common_utils::pii; use diesel_models::ephemeral_key; use error_stack::{IntoReport, ResultExt}; @@ -1093,6 +1094,7 @@ where pub recurring_mandate_payment_data: Option, pub ephemeral_key: Option, pub redirect_response: Option, + pub frm_message: Option, } #[derive(Debug, Default, Clone)] diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index fde900673b..6bb467d8ed 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -162,6 +162,7 @@ impl GetTracker, api::PaymentsCancelRequest> recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index ba31d8c9b8..11ad4627ee 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -167,6 +167,7 @@ impl GetTracker, api::PaymentsCaptu recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 94feb273c1..2aced2b4e2 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -234,6 +234,7 @@ impl GetTracker, api::PaymentsRequest> for Co recurring_mandate_payment_data, ephemeral_key: None, redirect_response, + frm_message: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 102aa35967..d22be94c1e 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -290,6 +290,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_mandate_payment_data, ephemeral_key: None, redirect_response: None, + frm_message: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index e456f58a0e..5b638b5e7b 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -269,6 +269,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_mandate_payment_data, ephemeral_key, redirect_response: None, + frm_message: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 712e46acdb..af037972ba 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -188,6 +188,7 @@ impl GetTracker, api::VerifyRequest> for Paym recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message: None, }, Some(payments::CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 7812a3ed2f..ccfb3bfda1 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -180,6 +180,7 @@ impl GetTracker, api::PaymentsSessionRequest> recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index af2df5f1b1..afdea2a362 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -152,6 +152,7 @@ impl GetTracker, api::PaymentsStartRequest> f recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 6a6ee01f70..8fd4f1e7e5 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -267,6 +267,25 @@ async fn get_tracker_for_sync< format!("Error while retrieving dispute list for, merchant_id: {merchant_id}, payment_id: {payment_id_str}") })?; + let frm_response = db + .find_fraud_check_by_payment_id(payment_id_str.to_string(), merchant_id.to_string()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Error while retrieving frm_response, merchant_id: {merchant_id}, payment_id: {payment_id_str}") + }); + let frm_message = frm_response + .ok() + .map(|response| api_models::payments::FrmMessage { + frm_name: response.frm_name, + frm_transaction_id: response.frm_transaction_id, + frm_transaction_type: Some(response.frm_transaction_type.to_string()), + frm_status: Some(response.frm_status.to_string()), + frm_score: response.frm_score, + frm_reason: response.frm_reason, + frm_error: response.frm_error, + }); + let contains_encoded_data = connector_response.encoded_data.is_some(); let creds_identifier = request @@ -324,6 +343,7 @@ async fn get_tracker_for_sync< recurring_mandate_payment_data: None, ephemeral_key: None, redirect_response: None, + frm_message, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 296487c9ac..495e639b8c 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -338,6 +338,7 @@ impl GetTracker, api::PaymentsRequest> for Pa recurring_mandate_payment_data, ephemeral_key: None, redirect_response: None, + frm_message: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index b4447c8524..72f60d9709 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1,6 +1,5 @@ use std::{fmt::Debug, marker::PhantomData}; -use api_models::payments::OrderDetailsWithAmount; use common_utils::fp_utils; use diesel_models::{ephemeral_key, payment_attempt::PaymentListFilters}; use error_stack::ResultExt; @@ -189,6 +188,7 @@ where &operation, payment_data.ephemeral_key, payment_data.sessions_token, + payment_data.frm_message, payment_data.setup_mandate, connector_request_reference_id_config, ) @@ -292,6 +292,7 @@ pub fn payments_to_payments_response( operation: &Op, ephemeral_key_option: Option, session_tokens: Vec, + frm_message: Option, mandate_data: Option, connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, ) -> RouterResponse @@ -494,6 +495,7 @@ where payment_intent.allowed_payment_method_types, ) .set_ephemeral_key(ephemeral_key_option.map(ForeignFrom::foreign_from)) + .set_frm_message(frm_message) .set_manual_retry_allowed(helpers::is_manual_retry_allowed( &payment_intent.status, &payment_attempt.status, @@ -550,6 +552,7 @@ where &merchant_id, ), order_details: payment_intent.order_details, + frm_message, connector_transaction_id: payment_attempt.connector_transaction_id, feature_metadata: payment_intent.feature_metadata, connector_metadata: payment_intent.connector_metadata, @@ -681,8 +684,8 @@ pub fn bank_transfer_next_steps_check( pub fn change_order_details_to_new_type( order_amount: i64, order_details: api_models::payments::OrderDetails, -) -> Option> { - Some(vec![OrderDetailsWithAmount { +) -> Option> { + Some(vec![api_models::payments::OrderDetailsWithAmount { product_name: order_details.product_name, quantity: order_details.quantity, amount: order_amount, diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index caf6264db6..6880bbe8c5 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -9,6 +9,7 @@ pub mod dispute; pub mod ephemeral_key; pub mod events; pub mod file; +pub mod fraud_check; pub mod locker_mock_up; pub mod mandate; pub mod merchant_account; @@ -54,6 +55,7 @@ pub trait StorageInterface: + ephemeral_key::EphemeralKeyInterface + events::EventInterface + file::FileMetadataInterface + + fraud_check::FraudCheckInterface + locker_mock_up::LockerMockUpInterface + mandate::MandateInterface + merchant_account::MerchantAccountInterface diff --git a/crates/router/src/db/fraud_check.rs b/crates/router/src/db/fraud_check.rs new file mode 100644 index 0000000000..756318ba0d --- /dev/null +++ b/crates/router/src/db/fraud_check.rs @@ -0,0 +1,112 @@ +use diesel_models::fraud_check::{self as storage, FraudCheck, FraudCheckUpdate}; +use error_stack::IntoReport; + +use super::MockDb; +use crate::{ + connection, + core::errors::{self, CustomResult}, + services::Store, +}; + +#[async_trait::async_trait] +pub trait FraudCheckInterface { + async fn insert_fraud_check_response( + &self, + new: storage::FraudCheckNew, + ) -> CustomResult; + + async fn update_fraud_check_response_with_attempt_id( + &self, + this: FraudCheck, + fraud_check: FraudCheckUpdate, + ) -> CustomResult; + + async fn find_fraud_check_by_payment_id( + &self, + payment_id: String, + merchant_id: String, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl FraudCheckInterface for Store { + async fn insert_fraud_check_response( + &self, + new: storage::FraudCheckNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + new.insert(&conn).await.map_err(Into::into).into_report() + } + async fn update_fraud_check_response_with_attempt_id( + &self, + this: FraudCheck, + fraud_check: FraudCheckUpdate, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + this.update_with_attempt_id(&conn, fraud_check) + .await + .map_err(Into::into) + .into_report() + } + async fn find_fraud_check_by_payment_id( + &self, + payment_id: String, + merchant_id: String, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + FraudCheck::get_with_payment_id(&conn, payment_id, merchant_id) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl FraudCheckInterface for MockDb { + async fn insert_fraud_check_response( + &self, + _new: storage::FraudCheckNew, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + async fn update_fraud_check_response_with_attempt_id( + &self, + _this: FraudCheck, + _fraud_check: FraudCheckUpdate, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + async fn find_fraud_check_by_payment_id( + &self, + _payment_id: String, + _merchant_id: String, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} + +#[cfg(feature = "kafka_events")] +#[async_trait::async_trait] +impl FraudCheckInterface for super::KafkaStore { + async fn insert_fraud_check_response( + &self, + _new: storage::FraudCheckNew, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + async fn update_fraud_check_response_with_attempt_id( + &self, + _this: FraudCheck, + _fraud_check: FraudCheckUpdate, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn find_fraud_check_by_payment_id( + &self, + _payment_id: String, + _merchant_id: String, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 2afac28f98..99e4be9df3 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -167,6 +167,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::MerchantConnectorUpdate, api_models::admin::PrimaryBusinessDetails, api_models::admin::FrmConfigs, + api_models::admin::FrmPaymentMethod, + api_models::admin::FrmPaymentMethodType, api_models::admin::PaymentMethodsEnabled, api_models::admin::MerchantConnectorDetailsWrap, api_models::admin::MerchantConnectorDetails, @@ -302,6 +304,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PayoutEntityType, api_models::enums::PayoutStatus, api_models::enums::PayoutType, + api_models::payments::FrmMessage, crate::types::api::admin::MerchantAccountResponse, crate::types::api::admin::MerchantConnectorId, crate::types::api::admin::MerchantDetails, diff --git a/crates/router/src/scheduler/workflows/api_key_expiry.rs b/crates/router/src/scheduler/workflows/api_key_expiry.rs index 6593eb90a2..62d8a54c40 100644 --- a/crates/router/src/scheduler/workflows/api_key_expiry.rs +++ b/crates/router/src/scheduler/workflows/api_key_expiry.rs @@ -62,6 +62,7 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { state .email_client + .clone() .send_email( email_id.ok_or_else(|| errors::ProcessTrackerError::MissingRequiredField)?, "API Key Expiry Notice".to_string(), diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 353d508c28..2a24acdbd1 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1,5 +1,5 @@ mod client; -pub(crate) mod request; +pub mod request; use std::{ collections::HashMap, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 5f726cfe38..e8f4d497e6 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -329,6 +329,11 @@ impl ConnectorData { enums::Connector::Trustpay => Ok(Box::new(&connector::Trustpay)), enums::Connector::Tsys => Ok(Box::new(&connector::Tsys)), enums::Connector::Zen => Ok(Box::new(&connector::Zen)), + enums::Connector::Signifyd => { + Err(report!(errors::ConnectorError::InvalidConnectorName) + .attach_printable(format!("invalid connector name: {connector_name}"))) + .change_context(errors::ApiErrorResponse::InternalServerError) + } }, Err(_) => Err(report!(errors::ConnectorError::InvalidConnectorName) .attach_printable(format!("invalid connector name: {connector_name}"))) diff --git a/crates/router/src/types/domain/merchant_connector_account.rs b/crates/router/src/types/domain/merchant_connector_account.rs index 0188ee177b..371ea5f6ae 100644 --- a/crates/router/src/types/domain/merchant_connector_account.rs +++ b/crates/router/src/types/domain/merchant_connector_account.rs @@ -24,7 +24,7 @@ pub struct MerchantConnectorAccount { pub payment_methods_enabled: Option>, pub connector_type: enums::ConnectorType, pub metadata: Option, - pub frm_configs: Option>, //Option + pub frm_configs: Option>>, pub connector_label: String, pub business_country: enums::CountryAlpha2, pub business_label: String, @@ -46,7 +46,7 @@ pub enum MerchantConnectorAccountUpdate { merchant_connector_id: Option, payment_methods_enabled: Option>, metadata: Option, - frm_configs: Option>, + frm_configs: Option>>, connector_webhook_details: Option, }, } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index b1aa46472b..f7a7a01d58 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -471,14 +471,18 @@ impl TryFrom for api_models::admin::MerchantCo }; let frm_configs = match item.frm_configs { Some(frm_value) => { - let configs_for_frm : api_models::admin::FrmConfigs = frm_value - .peek() - .clone() - .parse_value("FrmConfigs") - .change_context(errors::ApiErrorResponse::InvalidDataFormat { - field_name: "frm_configs".to_string(), - expected_format: "\"frm_configs\" : { \"frm_enabled_pms\" : [\"card\"], \"frm_enabled_pm_types\" : [\"credit\"], \"frm_enabled_gateways\" : [\"stripe\"], \"frm_action\": \"cancel_txn\", \"frm_preferred_flow_type\" : \"pre\" }".to_string(), - })?; + let configs_for_frm : Vec = frm_value + .iter() + .map(|config| { config + .peek() + .clone() + .parse_value("FrmConfigs") + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "frm_configs".to_string(), + expected_format: "[{ \"gateway\": \"stripe\", \"payment_methods\": [{ \"payment_method\": \"card\",\"payment_method_types\": [{\"payment_method_type\": \"credit\",\"card_networks\": [\"Visa\"],\"flow\": \"pre\",\"action\": \"cancel_txn\"}]}]}]".to_string(), + }) + }) + .collect::, _>>()?; Some(configs_for_frm) } None => None, diff --git a/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/down.sql b/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/down.sql new file mode 100644 index 0000000000..8672061462 --- /dev/null +++ b/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE merchant_connector_account +ALTER COLUMN frm_configs TYPE jsonb +USING frm_configs[1]::jsonb; \ No newline at end of file diff --git a/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/up.sql b/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/up.sql new file mode 100644 index 0000000000..15d9168864 --- /dev/null +++ b/migrations/2023-06-22-161131_change-type-of-frm-configs.sql/up.sql @@ -0,0 +1,7 @@ +UPDATE merchant_connector_account set frm_configs = null ; + +ALTER TABLE merchant_connector_account +ALTER COLUMN frm_configs TYPE jsonb[] +USING ARRAY[frm_configs]::jsonb[]; + +UPDATE merchant_connector_account set frm_configs = null ; diff --git a/migrations/2023-07-17-111427_add-fraud-check-table.sql/down.sql b/migrations/2023-07-17-111427_add-fraud-check-table.sql/down.sql new file mode 100644 index 0000000000..6955d3785d --- /dev/null +++ b/migrations/2023-07-17-111427_add-fraud-check-table.sql/down.sql @@ -0,0 +1,3 @@ +DROP TABLE fraud_check; +DROP TYPE "FraudCheckType"; +DROP TYPE "FraudCheckStatus"; \ No newline at end of file diff --git a/migrations/2023-07-17-111427_add-fraud-check-table.sql/up.sql b/migrations/2023-07-17-111427_add-fraud-check-table.sql/up.sql new file mode 100644 index 0000000000..7ddbd54a31 --- /dev/null +++ b/migrations/2023-07-17-111427_add-fraud-check-table.sql/up.sql @@ -0,0 +1,35 @@ +-- Your SQL goes here-- Your SQL goes here +CREATE TYPE "FraudCheckType" AS ENUM ( + 'pre_frm', + 'post_frm' +); + +CREATE TYPE "FraudCheckStatus" AS ENUM ( + 'fraud', + 'manual_review', + 'pending', + 'legit', + 'transaction_failure' +); + +CREATE TABLE fraud_check ( + frm_id VARCHAR(64) NOT NULL UNIQUE, + payment_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + attempt_id VARCHAR(64) NOT NULL UNIQUE, + created_at TIMESTAMP NOT NULL DEFAULT now(), + frm_name VARCHAR(255) NOT NULL, + frm_transaction_id VARCHAR(255) UNIQUE, + frm_transaction_type "FraudCheckType" NOT NULL, + frm_status "FraudCheckStatus" NOT NULL, + frm_score INTEGER, + frm_reason JSONB, + frm_error VARCHAR(255), + payment_details JSONB, + metadata JSONB, + modified_at TIMESTAMP NOT NULL DEFAULT now(), + + PRIMARY KEY (frm_id, attempt_id, payment_id, merchant_id) +); + +CREATE UNIQUE INDEX frm_id_index ON fraud_check (frm_id, attempt_id, payment_id, merchant_id); diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 82f32b51a2..750a4d3be5 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -3459,7 +3459,8 @@ "wise", "worldline", "worldpay", - "zen" + "zen", + "signifyd" ] }, "ConnectorMetadata": { @@ -4655,36 +4656,99 @@ "type": "object", "description": "Details of FrmConfigs are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table", "required": [ - "frm_action", - "frm_preferred_flow_type" + "gateway", + "payment_methods" ], "properties": { - "frm_enabled_pms": { + "gateway": { + "$ref": "#/components/schemas/ConnectorType" + }, + "payment_methods": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/FrmPaymentMethod" }, + "description": "payment methods that can be used in the payment" + } + } + }, + "FrmMessage": { + "type": "object", + "description": "frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None", + "required": [ + "frm_name" + ], + "properties": { + "frm_name": { + "type": "string" + }, + "frm_transaction_id": { + "type": "string", "nullable": true }, - "frm_enabled_pm_types": { + "frm_transaction_type": { + "type": "string", + "nullable": true + }, + "frm_status": { + "type": "string", + "nullable": true + }, + "frm_score": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "frm_reason": { + "nullable": true + }, + "frm_error": { + "type": "string", + "nullable": true + } + } + }, + "FrmPaymentMethod": { + "type": "object", + "description": "Details of FrmPaymentMethod are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table", + "required": [ + "payment_method", + "payment_method_types" + ], + "properties": { + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_types": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/FrmPaymentMethodType" }, - "nullable": true + "description": "payment method types(credit, debit) that can be used in the payment" + } + } + }, + "FrmPaymentMethodType": { + "type": "object", + "description": "Details of FrmPaymentMethodType are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table", + "required": [ + "payment_method_type", + "card_networks", + "flow", + "action" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "frm_enabled_gateways": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true + "card_networks": { + "$ref": "#/components/schemas/CardNetwork" }, - "frm_action": { - "$ref": "#/components/schemas/FrmAction" - }, - "frm_preferred_flow_type": { + "flow": { "$ref": "#/components/schemas/FrmPreferredFlowTypes" + }, + "action": { + "$ref": "#/components/schemas/FrmAction" } } }, @@ -5750,11 +5814,12 @@ "nullable": true }, "frm_configs": { - "allOf": [ - { - "$ref": "#/components/schemas/FrmConfigs" - } - ], + "type": "array", + "items": { + "$ref": "#/components/schemas/FrmConfigs" + }, + "description": "contains the frm configs for the merchant connector", + "example": "\n[{\"gateway\":\"stripe\",\"payment_methods\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\",\"action\":\"cancel_txn\"},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\"}]}]}]\n", "nullable": true }, "business_country": { @@ -5973,11 +6038,12 @@ "nullable": true }, "frm_configs": { - "allOf": [ - { - "$ref": "#/components/schemas/FrmConfigs" - } - ], + "type": "array", + "items": { + "$ref": "#/components/schemas/FrmConfigs" + }, + "description": "contains the frm configs for the merchant connector", + "example": "\n[{\"gateway\":\"stripe\",\"payment_methods\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\",\"action\":\"cancel_txn\"},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\"}]}]}]\n", "nullable": true }, "connector_webhook_details": { @@ -6069,11 +6135,12 @@ "nullable": true }, "frm_configs": { - "allOf": [ - { - "$ref": "#/components/schemas/FrmConfigs" - } - ], + "type": "array", + "items": { + "$ref": "#/components/schemas/FrmConfigs" + }, + "description": "contains the frm configs for the merchant connector", + "example": "\n[{\"gateway\":\"stripe\",\"payment_methods\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\",\"action\":\"cancel_txn\"},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\"],\"flow\":\"pre\"}]}]}]\n", "nullable": true }, "connector_webhook_details": { @@ -8429,6 +8496,14 @@ "example": "993672945374576J", "nullable": true }, + "frm_message": { + "allOf": [ + { + "$ref": "#/components/schemas/FrmMessage" + } + ], + "nullable": true + }, "metadata": { "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.",