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:
rishavkar
2023-07-26 18:15:26 +05:30
committed by GitHub
parent 4805a94ab9
commit c284f41cc6
38 changed files with 666 additions and 110 deletions

View File

@ -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,
}
}

View File

@ -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")?;

View File

@ -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)]

View File

@ -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,
))

View File

@ -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,
))

View File

@ -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(),

View File

@ -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),
))

View File

@ -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),
))

View File

@ -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(),

View File

@ -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),
))

View File

@ -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),
))

View File

@ -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,
))

View File

@ -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),
))

View File

@ -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,

View File

@ -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

View 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)?
}
}

View File

@ -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,

View File

@ -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(),

View File

@ -1,5 +1,5 @@
mod client;
pub(crate) mod request;
pub mod request;
use std::{
collections::HashMap,

View File

@ -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}")))

View File

@ -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>,
},
}

View File

@ -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,