mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(core): Changed frm_config format type in merchant_connector_account and added frm_message in payments response (#1543)
Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com> Co-authored-by: Sampras Lopes <lsampras@pm.me> Co-authored-by: Sampras Lopes <lsampras@protonmail.com>
This commit is contained in:
@ -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::<api_models::admin::FrmConfigs>::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::<Vec<serde_json::Value>>()
|
||||
});
|
||||
|
||||
let frm_configs = match req.frm_configs.as_ref() {
|
||||
Some(frm_value) => {
|
||||
let configs_for_frm_value: serde_json::Value =
|
||||
utils::Encode::<api_models::admin::FrmConfigs>::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<Vec<api_models::admin::FrmConfigs>>,
|
||||
) -> Option<Vec<Secret<serde_json::Value>>> {
|
||||
match frm_configs.as_ref() {
|
||||
Some(frm_value) => {
|
||||
let configs_for_frm_value: Vec<Secret<serde_json::Value>> = frm_value
|
||||
.iter()
|
||||
.map(|config| {
|
||||
utils::Encode::<api_models::admin::FrmConfigs>::encode_to_value(&config)
|
||||
.change_context(errors::ApiErrorResponse::ConfigNotFound)
|
||||
.map(masking::Secret::new)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.ok()?;
|
||||
Some(configs_for_frm_value)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")?;
|
||||
|
||||
@ -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<RecurringMandatePaymentData>,
|
||||
pub ephemeral_key: Option<ephemeral_key::EphemeralKey>,
|
||||
pub redirect_response: Option<api_models::payments::RedirectResponse>,
|
||||
pub frm_message: Option<FrmMessage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
||||
@ -162,6 +162,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
|
||||
recurring_mandate_payment_data: None,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
None,
|
||||
))
|
||||
|
||||
@ -167,6 +167,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
|
||||
recurring_mandate_payment_data: None,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
None,
|
||||
))
|
||||
|
||||
@ -234,6 +234,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
|
||||
recurring_mandate_payment_data,
|
||||
ephemeral_key: None,
|
||||
redirect_response,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(CustomerDetails {
|
||||
customer_id: request.customer_id.clone(),
|
||||
|
||||
@ -290,6 +290,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
recurring_mandate_payment_data,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(customer_details),
|
||||
))
|
||||
|
||||
@ -269,6 +269,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
recurring_mandate_payment_data,
|
||||
ephemeral_key,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(customer_details),
|
||||
))
|
||||
|
||||
@ -188,6 +188,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),
|
||||
|
||||
@ -180,6 +180,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
|
||||
recurring_mandate_payment_data: None,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(customer_details),
|
||||
))
|
||||
|
||||
@ -152,6 +152,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
|
||||
recurring_mandate_payment_data: None,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(customer_details),
|
||||
))
|
||||
|
||||
@ -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,
|
||||
))
|
||||
|
||||
@ -338,6 +338,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
recurring_mandate_payment_data,
|
||||
ephemeral_key: None,
|
||||
redirect_response: None,
|
||||
frm_message: None,
|
||||
},
|
||||
Some(customer_details),
|
||||
))
|
||||
|
||||
@ -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<R, Op>(
|
||||
operation: &Op,
|
||||
ephemeral_key_option: Option<ephemeral_key::EphemeralKey>,
|
||||
session_tokens: Vec<api::SessionToken>,
|
||||
frm_message: Option<payments::FrmMessage>,
|
||||
mandate_data: Option<api_models::payments::MandateData>,
|
||||
connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
|
||||
) -> RouterResponse<api::PaymentsResponse>
|
||||
@ -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<Vec<OrderDetailsWithAmount>> {
|
||||
Some(vec![OrderDetailsWithAmount {
|
||||
) -> Option<Vec<api_models::payments::OrderDetailsWithAmount>> {
|
||||
Some(vec![api_models::payments::OrderDetailsWithAmount {
|
||||
product_name: order_details.product_name,
|
||||
quantity: order_details.quantity,
|
||||
amount: order_amount,
|
||||
|
||||
@ -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
|
||||
|
||||
112
crates/router/src/db/fraud_check.rs
Normal file
112
crates/router/src/db/fraud_check.rs
Normal file
@ -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<FraudCheck, errors::StorageError>;
|
||||
|
||||
async fn update_fraud_check_response_with_attempt_id(
|
||||
&self,
|
||||
this: FraudCheck,
|
||||
fraud_check: FraudCheckUpdate,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError>;
|
||||
|
||||
async fn find_fraud_check_by_payment_id(
|
||||
&self,
|
||||
payment_id: String,
|
||||
merchant_id: String,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl FraudCheckInterface for Store {
|
||||
async fn insert_fraud_check_response(
|
||||
&self,
|
||||
new: storage::FraudCheckNew,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError> {
|
||||
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<FraudCheck, errors::StorageError> {
|
||||
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<FraudCheck, errors::StorageError> {
|
||||
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<FraudCheck, errors::StorageError> {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
async fn update_fraud_check_response_with_attempt_id(
|
||||
&self,
|
||||
_this: FraudCheck,
|
||||
_fraud_check: FraudCheckUpdate,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError> {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
async fn find_fraud_check_by_payment_id(
|
||||
&self,
|
||||
_payment_id: String,
|
||||
_merchant_id: String,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError> {
|
||||
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<FraudCheck, errors::StorageError> {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
async fn update_fraud_check_response_with_attempt_id(
|
||||
&self,
|
||||
_this: FraudCheck,
|
||||
_fraud_check: FraudCheckUpdate,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError> {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn find_fraud_check_by_payment_id(
|
||||
&self,
|
||||
_payment_id: String,
|
||||
_merchant_id: String,
|
||||
) -> CustomResult<FraudCheck, errors::StorageError> {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod client;
|
||||
pub(crate) mod request;
|
||||
pub mod request;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
||||
@ -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}")))
|
||||
|
||||
@ -24,7 +24,7 @@ pub struct MerchantConnectorAccount {
|
||||
pub payment_methods_enabled: Option<Vec<serde_json::Value>>,
|
||||
pub connector_type: enums::ConnectorType,
|
||||
pub metadata: Option<pii::SecretSerdeValue>,
|
||||
pub frm_configs: Option<Secret<serde_json::Value>>, //Option<FrmConfigs>
|
||||
pub frm_configs: Option<Vec<Secret<serde_json::Value>>>,
|
||||
pub connector_label: String,
|
||||
pub business_country: enums::CountryAlpha2,
|
||||
pub business_label: String,
|
||||
@ -46,7 +46,7 @@ pub enum MerchantConnectorAccountUpdate {
|
||||
merchant_connector_id: Option<String>,
|
||||
payment_methods_enabled: Option<Vec<serde_json::Value>>,
|
||||
metadata: Option<pii::SecretSerdeValue>,
|
||||
frm_configs: Option<Secret<serde_json::Value>>,
|
||||
frm_configs: Option<Vec<Secret<serde_json::Value>>>,
|
||||
connector_webhook_details: Option<pii::SecretSerdeValue>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -471,14 +471,18 @@ impl TryFrom<domain::MerchantConnectorAccount> 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<api_models::admin::FrmConfigs> = 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::<Result<Vec<_>, _>>()?;
|
||||
Some(configs_for_frm)
|
||||
}
|
||||
None => None,
|
||||
|
||||
Reference in New Issue
Block a user