diff --git a/crates/hyperswitch_connectors/src/connectors/payload.rs b/crates/hyperswitch_connectors/src/connectors/payload.rs index 7de5162822..726a9f8743 100644 --- a/crates/hyperswitch_connectors/src/connectors/payload.rs +++ b/crates/hyperswitch_connectors/src/connectors/payload.rs @@ -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 { 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 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 for Pa ) -> CustomResult { 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 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( diff --git a/crates/hyperswitch_connectors/src/connectors/payload/requests.rs b/crates/hyperswitch_connectors/src/connectors/payload/requests.rs index 75d3372ea9..5ad630526a 100644 --- a/crates/hyperswitch_connectors/src/connectors/payload/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/payload/requests.rs @@ -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, - #[serde(rename = "payment_method[billing_address][country_code]")] - pub country: Option, - #[serde(rename = "payment_method[billing_address][postal_code]")] + pub country_code: Option, pub postal_code: Secret, - #[serde(rename = "payment_method[billing_address][state_province]")] pub state_province: Option>, - #[serde(rename = "payment_method[billing_address][street_address]")] pub street_address: Option>, } +/// 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, - // Billing address fields are for AVS validation - #[serde(flatten)] - pub billing_address: BillingAddress, + #[serde(skip_serializing_if = "Option::is_none")] pub processing_id: Option>, - /// 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, } +/// 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, + /// 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, // 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, - #[serde(rename = "payment_method[card][card_code]")] - pub cvc: Secret, + pub card_code: Secret, +} + +#[derive(Default, Clone, Debug, Serialize, Eq, PartialEq)] +pub struct PayloadCard { + pub card: PayloadCardData, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PayloadBankAccountInner { + pub account_class: Option, + pub account_currency: String, + pub account_number: Secret, + pub account_type: PayloadAccAccountType, + pub routing_number: Secret, } #[derive(Clone, Debug, Serialize)] pub struct PayloadBank { - #[serde(rename = "payment_method[bank_account][account_class]")] - pub account_class: Option, - #[serde(rename = "payment_method[bank_account][account_currency]")] - pub account_currency: String, - #[serde(rename = "payment_method[bank_account][account_number]")] - pub account_number: Secret, - #[serde(rename = "payment_method[bank_account][account_type]")] - pub account_type: PayloadAccAccountType, - #[serde(rename = "payment_method[bank_account][routing_number]")] - pub routing_number: Secret, - #[serde(rename = "payment_method[account_holder]")] + pub bank_account: PayloadBankAccountInner, pub account_holder: Secret, } + #[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, +} + +#[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, // Customer ID from createCustomer - #[serde(flatten)] pub bank_account: PayloadBankAccountData, pub account_holder: Secret, #[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, - #[serde(rename = "bank_account[routing_number]")] pub routing_number: Secret, - #[serde(rename = "bank_account[account_type]")] pub account_type: PayloadAccAccountType, } diff --git a/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs index f964cac904..f4759836e2 100644 --- a/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs @@ -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 TryFrom<&PayloadRouterData<&RefundsRouterData>> 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, + }], }) } }