mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +08:00
fix(payload): update request structure and switch to JSON payload format (#11463)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user