feat(core): Add masking support for form-data types request (#9496)

Co-authored-by: Sayak Bhattacharya <sayak.b@Sayak-Bhattacharya-G092THXJ34.local>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sayak Bhattacharya
2025-10-07 22:22:43 +05:30
committed by GitHub
parent d98adb2e03
commit 1af7f42762
10 changed files with 84 additions and 57 deletions

View File

@ -1,4 +1,5 @@
use masking::{Maskable, Secret};
use reqwest::multipart::Form;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@ -66,7 +67,7 @@ impl std::fmt::Debug for RequestContent {
pub enum RequestContent {
Json(Box<dyn masking::ErasedMaskSerialize + Send>),
FormUrlEncoded(Box<dyn masking::ErasedMaskSerialize + Send>),
FormData(reqwest::multipart::Form),
FormData((Form, Box<dyn masking::ErasedMaskSerialize + Send>)),
Xml(Box<dyn masking::ErasedMaskSerialize + Send>),
RawBytes(Vec<u8>),
}
@ -77,7 +78,7 @@ impl RequestContent {
Self::Json(i) => serde_json::to_string(&i).unwrap_or_default().into(),
Self::FormUrlEncoded(i) => serde_urlencoded::to_string(i).unwrap_or_default().into(),
Self::Xml(i) => quick_xml::se::to_string(&i).unwrap_or_default().into(),
Self::FormData(_) => String::new().into(),
Self::FormData((_, i)) => serde_json::to_string(i).unwrap_or_default().into(),
Self::RawBytes(_) => String::new().into(),
}
}

View File

@ -44,7 +44,7 @@ pub async fn send_request(
let client = client.post(url);
match request.body {
Some(RequestContent::Json(payload)) => client.json(&payload),
Some(RequestContent::FormData(form)) => client.multipart(form),
Some(RequestContent::FormData((form, _))) => client.multipart(form),
Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload),
Some(RequestContent::Xml(payload)) => {
let body = quick_xml::se::to_string(&payload)
@ -59,7 +59,7 @@ pub async fn send_request(
let client = client.put(url);
match request.body {
Some(RequestContent::Json(payload)) => client.json(&payload),
Some(RequestContent::FormData(form)) => client.multipart(form),
Some(RequestContent::FormData((form, _))) => client.multipart(form),
Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload),
Some(RequestContent::Xml(payload)) => {
let body = quick_xml::se::to_string(&payload)
@ -74,7 +74,7 @@ pub async fn send_request(
let client = client.patch(url);
match request.body {
Some(RequestContent::Json(payload)) => client.json(&payload),
Some(RequestContent::FormData(form)) => client.multipart(form),
Some(RequestContent::FormData((form, _))) => client.multipart(form),
Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload),
Some(RequestContent::Xml(payload)) => {
let body = quick_xml::se::to_string(&payload)

View File

@ -1069,8 +1069,7 @@ impl ConnectorIntegration<Upload, UploadFileRequestData, UploadFileResponse> for
req: &UploadFileRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = transformers::construct_file_upload_request(req.clone())?;
Ok(RequestContent::FormData(connector_req))
transformers::construct_file_upload_request(req.clone())
}
fn build_request(

View File

@ -6,7 +6,7 @@ use common_utils::{
errors::{CustomResult, ParsingError},
ext_traits::ByteSliceExt,
id_type::CustomerId,
request::Method,
request::{Method, RequestContent},
types::MinorUnit,
};
use error_stack::ResultExt;
@ -1812,10 +1812,27 @@ pub struct CheckoutWebhookObjectResource {
pub data: serde_json::Value,
}
#[derive(Debug, Serialize)]
pub struct CheckoutFileRequest {
pub purpose: &'static str,
#[serde(skip)]
pub file: Vec<u8>,
#[serde(skip)]
pub file_key: String,
#[serde(skip)]
pub file_type: String,
}
pub fn construct_file_upload_request(
file_upload_router_data: UploadFileRouterData,
) -> CustomResult<reqwest::multipart::Form, errors::ConnectorError> {
) -> CustomResult<RequestContent, errors::ConnectorError> {
let request = file_upload_router_data.request;
let checkout_file_request = CheckoutFileRequest {
purpose: "dispute_evidence",
file: request.file.clone(),
file_key: request.file_key.clone(),
file_type: request.file_type.to_string(),
};
let mut multipart = reqwest::multipart::Form::new();
multipart = multipart.text("purpose", "dispute_evidence");
let file_data = reqwest::multipart::Part::bytes(request.file)
@ -1833,7 +1850,10 @@ pub fn construct_file_upload_request(
.change_context(errors::ConnectorError::RequestEncodingFailed)
.attach_printable("Failure in constructing file data")?;
multipart = multipart.part("file", file_data);
Ok(multipart)
Ok(RequestContent::FormData((
multipart,
Box::new(checkout_file_request),
)))
}
#[derive(Debug, Deserialize, Serialize)]

View File

@ -246,7 +246,9 @@ impl ConnectorCommon for Fiuu {
})
}
}
pub fn build_form_from_struct<T: Serialize>(data: T) -> Result<Form, common_errors::ParsingError> {
pub fn build_form_from_struct<T: Serialize + Send + 'static>(
data: T,
) -> Result<RequestContent, common_errors::ParsingError> {
let mut form = Form::new();
let serialized = serde_json::to_value(&data).map_err(|e| {
router_env::logger::error!("Error serializing data to JSON value: {:?}", e);
@ -268,7 +270,7 @@ pub fn build_form_from_struct<T: Serialize>(data: T) -> Result<Form, common_erro
};
form = form.text(key.clone(), value.clone());
}
Ok(form)
Ok(RequestContent::FormData((form, Box::new(data))))
}
impl ConnectorValidation for Fiuu {
@ -345,19 +347,18 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
.as_ref()
.map(|mandate_id| mandate_id.is_network_transaction_id_flow());
let connector_req = match (optional_is_mit_flow, optional_is_nti_flow) {
match (optional_is_mit_flow, optional_is_nti_flow) {
(Some(true), Some(false)) => {
let recurring_request = fiuu::FiuuMandateRequest::try_from(&connector_router_data)?;
build_form_from_struct(recurring_request)
.change_context(errors::ConnectorError::ParsingFailed)?
.change_context(errors::ConnectorError::ParsingFailed)
}
_ => {
let payment_request = fiuu::FiuuPaymentRequest::try_from(&connector_router_data)?;
build_form_from_struct(payment_request)
.change_context(errors::ConnectorError::ParsingFailed)?
.change_context(errors::ConnectorError::ParsingFailed)
}
}
};
Ok(RequestContent::FormData(connector_req))
}
fn build_request(
@ -429,9 +430,7 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Fiu
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let sync_request = fiuu::FiuuPaymentSyncRequest::try_from(req)?;
let connector_req = build_form_from_struct(sync_request)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(sync_request).change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -528,11 +527,10 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
)?;
let connector_router_data = fiuu::FiuuRouterData::from((amount, req));
let connector_req = build_form_from_struct(fiuu::PaymentCaptureRequest::try_from(
build_form_from_struct(fiuu::PaymentCaptureRequest::try_from(
&connector_router_data,
)?)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
.change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -595,9 +593,8 @@ impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Fi
req: &PaymentsCancelRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = build_form_from_struct(fiuu::FiuuPaymentCancelRequest::try_from(req)?)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(fiuu::FiuuPaymentCancelRequest::try_from(req)?)
.change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -667,10 +664,8 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Fiuu {
)?;
let connector_router_data = fiuu::FiuuRouterData::from((refund_amount, req));
let connector_req =
build_form_from_struct(fiuu::FiuuRefundRequest::try_from(&connector_router_data)?)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
.change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -732,9 +727,8 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Fiuu {
req: &RefundSyncRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = build_form_from_struct(fiuu::FiuuRefundSyncRequest::try_from(req)?)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(fiuu::FiuuRefundSyncRequest::try_from(req)?)
.change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(

View File

@ -51,7 +51,9 @@ use transformers as hipay;
use crate::{constants::headers, types::ResponseRouterData, utils};
pub fn build_form_from_struct<T: Serialize>(data: T) -> Result<Form, common_errors::ParsingError> {
pub fn build_form_from_struct<T: Serialize + Send + 'static>(
data: T,
) -> Result<RequestContent, common_errors::ParsingError> {
let mut form = Form::new();
let serialized = serde_json::to_value(&data).map_err(|e| {
router_env::logger::error!("Error serializing data to JSON value: {:?}", e);
@ -74,7 +76,7 @@ pub fn build_form_from_struct<T: Serialize>(data: T) -> Result<Form, common_erro
};
form = form.text(key.clone(), value.clone());
}
Ok(form)
Ok(RequestContent::FormData((form, Box::new(data))))
}
#[derive(Clone)]
pub struct Hipay {
@ -131,9 +133,7 @@ impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, Pay
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = transformers::HiPayTokenRequest::try_from(req)?;
router_env::logger::info!(raw_connector_request=?connector_req);
let connector_req = build_form_from_struct(connector_req)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(connector_req).change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -296,9 +296,7 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
let connector_router_data = hipay::HipayRouterData::from((amount, req));
let connector_req = hipay::HipayPaymentsRequest::try_from(&connector_router_data)?;
router_env::logger::info!(raw_connector_request=?connector_req);
let connector_req = build_form_from_struct(connector_req)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(connector_req).change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -454,9 +452,7 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
let connector_router_data = hipay::HipayRouterData::from((capture_amount, req));
let connector_req = hipay::HipayMaintenanceRequest::try_from(&connector_router_data)?;
router_env::logger::info!(raw_connector_request=?connector_req);
let connector_req = build_form_from_struct(connector_req)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(connector_req).change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(
@ -549,9 +545,7 @@ impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Hi
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = hipay::HipayMaintenanceRequest::try_from(req)?;
router_env::logger::info!(raw_connector_request=?connector_req);
let connector_req = build_form_from_struct(connector_req)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(connector_req).change_context(errors::ConnectorError::ParsingFailed)
}
fn handle_response(
@ -609,9 +603,7 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Hipay {
let connector_router_data = hipay::HipayRouterData::from((refund_amount, req));
let connector_req = hipay::HipayMaintenanceRequest::try_from(&connector_router_data)?;
router_env::logger::info!(raw_connector_request=?connector_req);
let connector_req = build_form_from_struct(connector_req)
.change_context(errors::ConnectorError::ParsingFailed)?;
Ok(RequestContent::FormData(connector_req))
build_form_from_struct(connector_req).change_context(errors::ConnectorError::ParsingFailed)
}
fn build_request(

View File

@ -1880,8 +1880,7 @@ impl ConnectorIntegration<Upload, UploadFileRequestData, UploadFileResponse> for
req: &UploadFileRouterData,
_connectors: &Connectors,
) -> CustomResult<RequestContent, ConnectorError> {
let connector_req = transformers::construct_file_upload_request(req.clone())?;
Ok(RequestContent::FormData(connector_req))
transformers::construct_file_upload_request(req.clone())
}
fn build_request(

View File

@ -4500,10 +4500,27 @@ pub fn get_bank_transfer_request_data(
}
}
#[derive(Debug, Serialize)]
pub struct StripeFileRequest {
pub purpose: &'static str,
#[serde(skip)]
pub file: Vec<u8>,
#[serde(skip)]
pub file_key: String,
#[serde(skip)]
pub file_type: String,
}
pub fn construct_file_upload_request(
file_upload_router_data: UploadFileRouterData,
) -> CustomResult<reqwest::multipart::Form, ConnectorError> {
) -> CustomResult<RequestContent, ConnectorError> {
let request = file_upload_router_data.request;
let stripe_file_request = StripeFileRequest {
purpose: "dispute_evidence",
file: request.file.clone(),
file_key: request.file_key.clone(),
file_type: request.file_type.to_string(),
};
let mut multipart = reqwest::multipart::Form::new();
multipart = multipart.text("purpose", "dispute_evidence");
let file_data = reqwest::multipart::Part::bytes(request.file)
@ -4511,7 +4528,10 @@ pub fn construct_file_upload_request(
.mime_str(request.file_type.as_ref())
.map_err(|_| ConnectorError::RequestEncodingFailed)?;
multipart = multipart.part("file", file_data);
Ok(multipart)
Ok(RequestContent::FormData((
multipart,
Box::new(stripe_file_request),
)))
}
#[derive(Debug, Deserialize, Serialize)]

View File

@ -948,7 +948,7 @@ pub async fn create_payment_method_core(
match &req.payment_method_data {
api::PaymentMethodCreateData::Card(_) => {
create_payment_method_card_core(
Box::pin(create_payment_method_card_core(
state,
req,
merchant_context,
@ -957,7 +957,7 @@ pub async fn create_payment_method_core(
&customer_id,
payment_method_id,
payment_method_billing_address,
)
))
.await
}
api::PaymentMethodCreateData::ProxyCard(_) => {

View File

@ -334,7 +334,9 @@ where
| RequestContent::Xml(i) => i
.masked_serialize()
.unwrap_or(json!({ "error": "failed to mask serialize"})),
RequestContent::FormData(_) => json!({"request_type": "FORM_DATA"}),
RequestContent::FormData((_, i)) => i
.masked_serialize()
.unwrap_or(json!({ "error": "failed to mask serialize"})),
RequestContent::RawBytes(_) => json!({"request_type": "RAW_BYTES"}),
},
None => serde_json::Value::Null,