mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
fix(deserialization): deserialize reward payment method data (#4011)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -10,7 +10,8 @@ use masking::Secret;
|
||||
use router_derive::Setter;
|
||||
use serde::{
|
||||
de::{self, Unexpected, Visitor},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
ser::Serializer,
|
||||
Deserialize, Deserializer, Serialize,
|
||||
};
|
||||
use time::PrimitiveDateTime;
|
||||
use url::Url;
|
||||
@ -309,6 +310,7 @@ pub struct PaymentsRequest {
|
||||
|
||||
/// The payment method information provided for making a payment
|
||||
#[schema(example = "bank_transfer")]
|
||||
#[serde(with = "payment_method_data_serde", default)]
|
||||
pub payment_method_data: Option<PaymentMethodDataRequest>,
|
||||
|
||||
/// The payment method that is to be used
|
||||
@ -1056,6 +1058,93 @@ pub enum BankDebitData {
|
||||
},
|
||||
}
|
||||
|
||||
/// Custom serializer and deserializer for PaymentMethodData
|
||||
mod payment_method_data_serde {
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Deserialize `reward` payment_method as string for backwards compatibility
|
||||
/// The api contract would be
|
||||
/// ```json
|
||||
/// {
|
||||
/// "payment_method": "reward",
|
||||
/// "payment_method_type": "evoucher",
|
||||
/// "payment_method_data": "reward",
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For other payment methods, use the provided deserializer
|
||||
/// ```json
|
||||
/// "payment_method_data": {
|
||||
/// "card": {
|
||||
/// "card_number": "4242424242424242",
|
||||
/// "card_exp_month": "10",
|
||||
/// "card_exp_year": "25",
|
||||
/// "card_holder_name": "joseph Doe",
|
||||
/// "card_cvc": "123"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn deserialize<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<PaymentMethodDataRequest>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum __Inner {
|
||||
RewardString(String),
|
||||
OptionalPaymentMethod(Box<PaymentMethodDataRequest>),
|
||||
}
|
||||
|
||||
let deserialize_to_inner = __Inner::deserialize(deserializer)?;
|
||||
match deserialize_to_inner {
|
||||
__Inner::OptionalPaymentMethod(value) => Ok(Some(*value)),
|
||||
__Inner::RewardString(inner_string) => {
|
||||
let payment_method_data = match inner_string.as_str() {
|
||||
"reward" => PaymentMethodData::Reward,
|
||||
_ => Err(serde::de::Error::custom("Invalid Variant"))?,
|
||||
};
|
||||
|
||||
Ok(Some(PaymentMethodDataRequest {
|
||||
payment_method_data,
|
||||
billing: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S>(
|
||||
payment_method_data_request: &Option<PaymentMethodDataRequest>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(payment_method_data_request) = payment_method_data_request {
|
||||
match payment_method_data_request.payment_method_data {
|
||||
PaymentMethodData::Reward => serializer.serialize_str("reward"),
|
||||
PaymentMethodData::CardRedirect(_)
|
||||
| PaymentMethodData::BankDebit(_)
|
||||
| PaymentMethodData::BankRedirect(_)
|
||||
| PaymentMethodData::BankTransfer(_)
|
||||
| PaymentMethodData::CardToken(_)
|
||||
| PaymentMethodData::Crypto(_)
|
||||
| PaymentMethodData::GiftCard(_)
|
||||
| PaymentMethodData::PayLater(_)
|
||||
| PaymentMethodData::Upi(_)
|
||||
| PaymentMethodData::Voucher(_)
|
||||
| PaymentMethodData::Card(_)
|
||||
| PaymentMethodData::MandatePayment
|
||||
| PaymentMethodData::Wallet(_) => payment_method_data_request.serialize(serializer),
|
||||
}
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, Eq, PartialEq)]
|
||||
pub struct PaymentMethodDataRequest {
|
||||
#[serde(flatten)]
|
||||
@ -1121,7 +1210,7 @@ impl PaymentMethodData {
|
||||
| Self::BankTransfer(_)
|
||||
| Self::Crypto(_)
|
||||
| Self::MandatePayment
|
||||
| Self::Reward
|
||||
| Self::Reward {}
|
||||
| Self::Upi(_)
|
||||
| Self::Voucher(_)
|
||||
| Self::GiftCard(_)
|
||||
@ -1939,6 +2028,39 @@ pub enum VoucherData {
|
||||
PayEasy(Box<JCSVoucherData>),
|
||||
}
|
||||
|
||||
/// Use custom serializer to provide backwards compatible response for `reward` payment_method_data
|
||||
pub fn serialize_payment_method_data_response<S>(
|
||||
payment_method_data_response: &Option<PaymentMethodDataResponseWithBilling>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(payment_method_data_response) = payment_method_data_response {
|
||||
match payment_method_data_response.payment_method_data {
|
||||
PaymentMethodDataResponse::Reward {} => serializer.serialize_str("reward"),
|
||||
PaymentMethodDataResponse::BankDebit {}
|
||||
| PaymentMethodDataResponse::BankRedirect {}
|
||||
| PaymentMethodDataResponse::Card(_)
|
||||
| PaymentMethodDataResponse::CardRedirect {}
|
||||
| PaymentMethodDataResponse::CardToken {}
|
||||
| PaymentMethodDataResponse::Crypto {}
|
||||
| PaymentMethodDataResponse::MandatePayment {}
|
||||
| PaymentMethodDataResponse::GiftCard {}
|
||||
| PaymentMethodDataResponse::PayLater {}
|
||||
| PaymentMethodDataResponse::Paypal {}
|
||||
| PaymentMethodDataResponse::Upi {}
|
||||
| PaymentMethodDataResponse::Wallet(_)
|
||||
| PaymentMethodDataResponse::BankTransfer {}
|
||||
| PaymentMethodDataResponse::Voucher {} => {
|
||||
payment_method_data_response.serialize(serializer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PaymentMethodDataResponse {
|
||||
@ -1960,7 +2082,7 @@ pub enum PaymentMethodDataResponse {
|
||||
CardToken {},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, ToSchema, serde::Serialize)]
|
||||
pub struct PaymentMethodDataResponseWithBilling {
|
||||
// The struct is flattened in order to provide backwards compatibility
|
||||
#[serde(flatten)]
|
||||
@ -2426,6 +2548,7 @@ pub struct PaymentsResponse {
|
||||
/// The payment method information provided for making a payment
|
||||
#[schema(value_type = Option<PaymentMethod>, example = "bank_transfer")]
|
||||
#[auth_based]
|
||||
#[serde(serialize_with = "serialize_payment_method_data_response")]
|
||||
pub payment_method_data: Option<PaymentMethodDataResponseWithBilling>,
|
||||
|
||||
/// Provide a reference to a stored payment method
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
"Scenario28-Confirm a payment with requires_customer_action status",
|
||||
"Scenario29-Create payment with payment method billing",
|
||||
"Scenario30-Update payment with payment method billing",
|
||||
"Scenario31-Pass payment method billing in Confirm"
|
||||
"Scenario31-Pass payment method billing in Confirm",
|
||||
"Scenario32-Ensure API Contract for Payment Method Data"
|
||||
]
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"eventOrder": [
|
||||
"event.test.js",
|
||||
"event.prerequest.js"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
// Validate status 2xx
|
||||
pm.test("[POST]::/payments - Status code is 2xx", function () {
|
||||
pm.response.to.be.success;
|
||||
});
|
||||
|
||||
// Validate if response has JSON Body
|
||||
pm.test("[POST]::/payments - Response has JSON Body", function () {
|
||||
pm.response.to.have.jsonBody();
|
||||
});
|
||||
@ -0,0 +1,41 @@
|
||||
{
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
},
|
||||
"raw_json_formatted": {
|
||||
"amount": 6540,
|
||||
"currency": "USD",
|
||||
"payment_method": "card",
|
||||
"payment_method_data": {
|
||||
"card": {
|
||||
"card_number": "4242424242424242",
|
||||
"card_exp_month": "10",
|
||||
"card_exp_year": "25",
|
||||
"card_holder_name": "joseph Doe",
|
||||
"card_cvc": "123"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/payments",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["payments"]
|
||||
},
|
||||
"description": "Create a Payment to ensure api contract is intact"
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"eventOrder": [
|
||||
"event.test.js",
|
||||
"event.prerequest.js"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
// Validate status 2xx
|
||||
pm.test("[POST]::/payments - Status code is 2xx", function () {
|
||||
pm.response.to.be.success;
|
||||
});
|
||||
|
||||
// Validate if response header has matching content-type
|
||||
pm.test("[POST]::/payments - Content-Type is application/json", function () {
|
||||
pm.expect(pm.response.headers.get("Content-Type")).to.include(
|
||||
"application/json",
|
||||
);
|
||||
});
|
||||
|
||||
// Validate if response has JSON Body
|
||||
pm.test("[POST]::/payments - Response has JSON Body", function () {
|
||||
pm.response.to.have.jsonBody();
|
||||
});
|
||||
|
||||
// Set response object as internal variable
|
||||
let jsonData = {};
|
||||
try {
|
||||
jsonData = pm.response.json();
|
||||
} catch (e) {}
|
||||
|
||||
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
|
||||
if (jsonData?.payment_id) {
|
||||
pm.collectionVariables.set("payment_id", jsonData.payment_id);
|
||||
console.log(
|
||||
"- use {{payment_id}} as collection variable for value",
|
||||
jsonData.payment_id,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
|
||||
);
|
||||
}
|
||||
|
||||
// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
|
||||
if (jsonData?.mandate_id) {
|
||||
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
|
||||
console.log(
|
||||
"- use {{mandate_id}} as collection variable for value",
|
||||
jsonData.mandate_id,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
|
||||
);
|
||||
}
|
||||
|
||||
// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
|
||||
if (jsonData?.client_secret) {
|
||||
pm.collectionVariables.set("client_secret", jsonData.client_secret);
|
||||
console.log(
|
||||
"- use {{client_secret}} as collection variable for value",
|
||||
jsonData.client_secret,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
|
||||
);
|
||||
}
|
||||
|
||||
// Response body should have value "requires_payment_method" for "status"
|
||||
if (jsonData?.status) {
|
||||
pm.test(
|
||||
"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'",
|
||||
function () {
|
||||
pm.expect(jsonData.status).to.eql("requires_payment_method");
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
{
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
},
|
||||
"raw_json_formatted": {
|
||||
"amount": 6540,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/payments",
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["payments"]
|
||||
},
|
||||
"description": "Create a Payment to ensure api contract is intact"
|
||||
}
|
||||
@ -21795,6 +21795,182 @@
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Scenario32-Ensure API Contract for Payment Method Data",
|
||||
"item": [
|
||||
{
|
||||
"name": "Payments - Card Payment Method",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// Validate status 2xx",
|
||||
"pm.test(\"[POST]::/payments - Status code is 2xx\", function () {",
|
||||
" pm.response.to.be.success;",
|
||||
"});",
|
||||
"",
|
||||
"// Validate if response has JSON Body",
|
||||
"pm.test(\"[POST]::/payments - Response has JSON Body\", function () {",
|
||||
" pm.response.to.have.jsonBody();",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
},
|
||||
"raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/payments",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"payments"
|
||||
]
|
||||
},
|
||||
"description": "Create a Payment to ensure api contract is intact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Payments - Reward Payment Method",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// Validate status 2xx",
|
||||
"pm.test(\"[POST]::/payments - Status code is 2xx\", function () {",
|
||||
" pm.response.to.be.success;",
|
||||
"});",
|
||||
"",
|
||||
"// Validate if response header has matching content-type",
|
||||
"pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {",
|
||||
" pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(",
|
||||
" \"application/json\",",
|
||||
" );",
|
||||
"});",
|
||||
"",
|
||||
"// Validate if response has JSON Body",
|
||||
"pm.test(\"[POST]::/payments - Response has JSON Body\", function () {",
|
||||
" pm.response.to.have.jsonBody();",
|
||||
"});",
|
||||
"",
|
||||
"// Set response object as internal variable",
|
||||
"let jsonData = {};",
|
||||
"try {",
|
||||
" jsonData = pm.response.json();",
|
||||
"} catch (e) {}",
|
||||
"",
|
||||
"// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id",
|
||||
"if (jsonData?.payment_id) {",
|
||||
" pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);",
|
||||
" console.log(",
|
||||
" \"- use {{payment_id}} as collection variable for value\",",
|
||||
" jsonData.payment_id,",
|
||||
" );",
|
||||
"} else {",
|
||||
" console.log(",
|
||||
" \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",",
|
||||
" );",
|
||||
"}",
|
||||
"",
|
||||
"// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id",
|
||||
"if (jsonData?.mandate_id) {",
|
||||
" pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);",
|
||||
" console.log(",
|
||||
" \"- use {{mandate_id}} as collection variable for value\",",
|
||||
" jsonData.mandate_id,",
|
||||
" );",
|
||||
"} else {",
|
||||
" console.log(",
|
||||
" \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",",
|
||||
" );",
|
||||
"}",
|
||||
"",
|
||||
"// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret",
|
||||
"if (jsonData?.client_secret) {",
|
||||
" pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);",
|
||||
" console.log(",
|
||||
" \"- use {{client_secret}} as collection variable for value\",",
|
||||
" jsonData.client_secret,",
|
||||
" );",
|
||||
"} else {",
|
||||
" console.log(",
|
||||
" \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",",
|
||||
" );",
|
||||
"}",
|
||||
"",
|
||||
"// Response body should have value \"requires_payment_method\" for \"status\"",
|
||||
"if (jsonData?.status) {",
|
||||
" pm.test(",
|
||||
" \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",",
|
||||
" function () {",
|
||||
" pm.expect(jsonData.status).to.eql(\"requires_payment_method\");",
|
||||
" },",
|
||||
" );",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
},
|
||||
"raw": "{\"amount\":6540,\"currency\":\"USD\"}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/payments",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"payments"
|
||||
]
|
||||
},
|
||||
"description": "Create a Payment to ensure api contract is intact"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user