fix(payload): update request structure and switch to JSON payload format (#11463)

This commit is contained in:
awasthi21
2026-03-10 22:31:13 +05:30
committed by GitHub
parent b822cab401
commit 3671bf643b
3 changed files with 84 additions and 72 deletions

View File

@@ -207,7 +207,7 @@ impl ConnectorCommon for Payload {
}
fn common_get_content_type(&self) -> &'static str {
"application/x-www-form-urlencoded"
"application/json"
}
fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
@@ -252,11 +252,8 @@ impl ConnectorCommon for Payload {
Ok(ErrorResponse {
status_code: res.status_code,
code: response.error_type,
message: response.error_description,
reason: response
.details
.as_ref()
.map(|details_value| details_value.to_string()),
message: response.error_description.clone(),
reason: Some(response.error_description),
attempt_status: None,
connector_transaction_id: None,
connector_response_reference_id: None,
@@ -328,11 +325,11 @@ impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsRespons
},
) => {
let connector_req = requests::PayloadPaymentMethodRequest::try_from(req)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
_ => {
let connector_req = requests::PayloadPaymentRequestData::try_from(req)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
}
}
@@ -440,7 +437,7 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
let connector_router_data = payload::PayloadRouterData::from((amount, req));
let connector_req = requests::PayloadPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
@@ -607,7 +604,7 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
let connector_router_data = payload::PayloadRouterData::from((amount, req));
let connector_req = requests::PayloadCaptureRequest::try_from(&connector_router_data)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
@@ -691,7 +688,7 @@ impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Pa
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = payload::PayloadRouterData::from((Default::default(), req));
let connector_req = requests::PayloadCancelRequest::try_from(&connector_router_data)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
@@ -777,7 +774,7 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Payload
let connector_router_data = payload::PayloadRouterData::from((refund_amount, req));
let connector_req = requests::PayloadRefundRequest::try_from(&connector_router_data)?;
Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(

View File

@@ -23,42 +23,46 @@ pub enum TransactionTypes {
Reversal,
}
/// Billing address nested inside `payment_method` for AVS validation
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BillingAddress {
#[serde(rename = "payment_method[billing_address][city]")]
pub city: Option<String>,
#[serde(rename = "payment_method[billing_address][country_code]")]
pub country: Option<common_enums::CountryAlpha2>,
#[serde(rename = "payment_method[billing_address][postal_code]")]
pub country_code: Option<common_enums::CountryAlpha2>,
pub postal_code: Secret<String>,
#[serde(rename = "payment_method[billing_address][state_province]")]
pub state_province: Option<Secret<String>>,
#[serde(rename = "payment_method[billing_address][street_address]")]
pub street_address: Option<Secret<String>>,
}
/// Top-level payment request sent to /transactions
#[derive(Debug, Clone, Serialize)]
pub struct PayloadPaymentRequestData {
pub amount: StringMajorUnit,
#[serde(flatten)]
pub payment_method: PayloadPaymentMethods,
/// Serialises as `{"type": "card"|"bank_account", "card"|"bank_account": {...},
/// "billing_address": {...}, "keep_active": bool, ...}`
pub payment_method: PayloadPaymentMethod,
#[serde(rename = "type")]
pub transaction_types: TransactionTypes,
// For manual capture, set status to "authorized", otherwise omit
/// For manual capture, set to "authorized", otherwise omit
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<responses::PayloadPaymentStatus>,
// Billing address fields are for AVS validation
#[serde(flatten)]
pub billing_address: BillingAddress,
#[serde(skip_serializing_if = "Option::is_none")]
pub processing_id: Option<Secret<String>>,
/// Allows one-time payment by customer without saving their payment method
/// This is true by default
#[serde(rename = "payment_method[keep_active]")]
pub keep_active: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub customer_id: Option<String>,
}
/// Wrapper that nests `billing_address` and `keep_active` inside `payment_method`
#[derive(Debug, Clone, Serialize)]
pub struct PayloadPaymentMethod {
#[serde(flatten)]
pub method: PayloadPaymentMethods,
/// Billing address for AVS — lives inside payment_method in the API
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<BillingAddress>,
/// Whether to keep the payment method active (set false for one-time payments)
pub keep_active: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct CustomerRequest {
pub keep_active: bool,
@@ -72,8 +76,7 @@ pub struct PayloadMandateRequestData {
pub amount: StringMajorUnit,
#[serde(rename = "type")]
pub transaction_types: TransactionTypes,
// Based on the connectors' response, we can do recurring payment either based on a default payment method id saved in the customer profile or a specific payment method id
// Connector by default, saves every payment method
// Connector by default saves every payment method; reference by specific PM id for recurring
pub payment_method_id: Secret<String>,
// For manual capture, set status to "authorized", otherwise omit
#[serde(skip_serializing_if = "Option::is_none")]
@@ -81,30 +84,32 @@ pub struct PayloadMandateRequestData {
}
#[derive(Default, Clone, Debug, Serialize, Eq, PartialEq)]
pub struct PayloadCard {
#[serde(rename = "payment_method[card][card_number]")]
pub number: cards::CardNumber,
#[serde(rename = "payment_method[card][expiry]")]
pub struct PayloadCardData {
pub card_number: cards::CardNumber,
pub expiry: Secret<String>,
#[serde(rename = "payment_method[card][card_code]")]
pub cvc: Secret<String>,
pub card_code: Secret<String>,
}
#[derive(Default, Clone, Debug, Serialize, Eq, PartialEq)]
pub struct PayloadCard {
pub card: PayloadCardData,
}
#[derive(Clone, Debug, Serialize)]
pub struct PayloadBankAccountInner {
pub account_class: Option<PayloadAccClass>,
pub account_currency: String,
pub account_number: Secret<String>,
pub account_type: PayloadAccAccountType,
pub routing_number: Secret<String>,
}
#[derive(Clone, Debug, Serialize)]
pub struct PayloadBank {
#[serde(rename = "payment_method[bank_account][account_class]")]
pub account_class: Option<PayloadAccClass>,
#[serde(rename = "payment_method[bank_account][account_currency]")]
pub account_currency: String,
#[serde(rename = "payment_method[bank_account][account_number]")]
pub account_number: Secret<String>,
#[serde(rename = "payment_method[bank_account][account_type]")]
pub account_type: PayloadAccAccountType,
#[serde(rename = "payment_method[bank_account][routing_number]")]
pub routing_number: Secret<String>,
#[serde(rename = "payment_method[account_holder]")]
pub bank_account: PayloadBankAccountInner,
pub account_holder: Secret<String>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PayloadAccClass {
@@ -119,13 +124,15 @@ pub enum PayloadAccAccountType {
Savings,
}
/// Tagged enum — serialises as `{"type": "card", "card": {...}}` or
/// `{"type": "bank_account", "bank_account": {...}}`
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "payment_method[type]")]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PayloadPaymentMethods {
Card(PayloadCard),
BankAccount(PayloadBank),
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub struct PayloadCancelRequest {
pub status: responses::PayloadPaymentStatus,
@@ -143,15 +150,18 @@ pub struct PayloadRefundRequest {
#[serde(rename = "type")]
pub transaction_type: TransactionTypes,
pub amount: StringMajorUnit,
#[serde(rename = "ledger[0][assoc_transaction_id]")]
pub ledger_assoc_transaction_id: String,
pub ledger: Vec<PayloadRefundLedgerEntry>,
}
#[derive(Debug, Serialize)]
pub struct PayloadRefundLedgerEntry {
pub assoc_transaction_id: String,
}
// Request struct for ACH SetupMandate using /payment_methods API
#[derive(Debug, Clone, Serialize)]
pub struct PayloadPaymentMethodRequest {
pub account_id: Secret<String>, // Customer ID from createCustomer
#[serde(flatten)]
pub bank_account: PayloadBankAccountData,
pub account_holder: Secret<String>,
#[serde(rename = "type")]
@@ -160,11 +170,8 @@ pub struct PayloadPaymentMethodRequest {
#[derive(Debug, Clone, Serialize)]
pub struct PayloadBankAccountData {
#[serde(rename = "bank_account[account_number]")]
pub account_number: Secret<String>,
#[serde(rename = "bank_account[routing_number]")]
pub routing_number: Secret<String>,
#[serde(rename = "bank_account[account_type]")]
pub account_type: PayloadAccAccountType,
}

View File

@@ -71,11 +71,13 @@ fn build_payload_payment_request_data(
})?
}
let card = requests::PayloadCard {
number: req_card.clone().card_number,
expiry: req_card
.clone()
.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?,
cvc: req_card.card_cvc.clone(),
card: requests::PayloadCardData {
card_number: req_card.clone().card_number,
expiry: req_card
.clone()
.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?,
card_code: req_card.card_cvc.clone(),
},
};
Ok(requests::PayloadPaymentMethods::Card(card))
}
@@ -105,11 +107,13 @@ fn build_payload_payment_request_data(
}
})?;
let bank = requests::PayloadBank {
account_class,
account_currency: currency.to_string(),
account_number: account_number.clone(),
account_type,
routing_number: routing_number.clone(),
bank_account: requests::PayloadBankAccountInner {
account_class,
account_currency: currency.to_string(),
account_number: account_number.clone(),
account_type,
routing_number: routing_number.clone(),
},
account_holder,
};
Ok(requests::PayloadPaymentMethods::BankAccount(bank))
@@ -132,25 +136,27 @@ fn build_payload_payment_request_data(
None
};
let billing_address = requests::BillingAddress {
let billing_address = Some(requests::BillingAddress {
city,
country,
country_code: country,
postal_code,
state_province,
street_address,
};
});
let payload_auth = PayloadAuth::try_from((connector_auth_type, currency))?;
// Metadata processing_account_id takes precedence over connector auth config
Ok(requests::PayloadPaymentRequestData {
amount,
payment_method: payment_method?,
payment_method: requests::PayloadPaymentMethod {
method: payment_method?,
billing_address,
keep_active: is_mandate,
},
transaction_types: requests::TransactionTypes::Payment,
status,
billing_address,
processing_id: get_processing_account_id_from_metadata(metadata)
.or(payload_auth.processing_account_id),
keep_active: is_mandate,
customer_id,
})
}
@@ -612,7 +618,9 @@ impl<F> TryFrom<&PayloadRouterData<&RefundsRouterData<F>>> for requests::Payload
Ok(Self {
transaction_type: requests::TransactionTypes::Refund,
amount: item.amount.to_owned(),
ledger_assoc_transaction_id: connector_transaction_id,
ledger: vec![requests::PayloadRefundLedgerEntry {
assoc_transaction_id: connector_transaction_id,
}],
})
}
}