use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::contact::ContactAddress;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentCurrencyAmount {
pub currency: String,
pub value: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentItem {
pub label: String,
pub amount: PaymentCurrencyAmount,
#[serde(skip_serializing_if = "Option::is_none")]
pub pending: Option<bool>,
#[serde(
default = "default_refund_period",
deserialize_with = "deserialize_refund_period"
)]
pub refund_period: i32,
}
fn default_refund_period() -> i32 {
30
}
fn deserialize_refund_period<'de, D>(deserializer: D) -> Result<i32, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum IntOrFloat {
Int(i32),
Float(f64),
}
match IntOrFloat::deserialize(deserializer)? {
IntOrFloat::Int(i) => Ok(i),
IntOrFloat::Float(f) => Ok(f as i32),
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentShippingOption {
pub id: String,
pub label: String,
pub amount: PaymentCurrencyAmount,
#[serde(skip_serializing_if = "Option::is_none")]
pub selected: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub request_payer_name: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_payer_email: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_payer_phone: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_shipping: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_type: Option<String>,
}
impl Default for PaymentOptions {
fn default() -> Self {
Self {
request_payer_name: Some(false),
request_payer_email: Some(false),
request_payer_phone: Some(false),
request_shipping: Some(true),
shipping_type: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentMethodData {
pub supported_methods: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentDetailsModifier {
pub supported_methods: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<PaymentItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_display_items: Option<Vec<PaymentItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentDetailsInit {
pub id: String,
pub display_items: Vec<PaymentItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_options: Option<Vec<PaymentShippingOption>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modifiers: Option<Vec<PaymentDetailsModifier>>,
pub total: PaymentItem,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentRequest {
pub method_data: Vec<PaymentMethodData>,
pub details: PaymentDetailsInit,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<PaymentOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_address: Option<ContactAddress>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PaymentResponse {
pub request_id: String,
pub method_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_address: Option<ContactAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_option: Option<PaymentShippingOption>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payer_phone: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payment_currency_amount_roundtrip() {
let amt = PaymentCurrencyAmount {
currency: "USD".into(),
value: 120.0,
};
let json = serde_json::to_string(&amt).unwrap();
let back: PaymentCurrencyAmount = serde_json::from_str(&json).unwrap();
assert_eq!(amt, back);
}
#[test]
fn payment_item_default_refund_period() {
let json = r#"{"label":"Shoes","amount":{"currency":"USD","value":99.99}}"#;
let item: PaymentItem = serde_json::from_str(json).unwrap();
assert_eq!(item.refund_period, 30);
}
#[test]
fn payment_request_full_roundtrip() {
let req = PaymentRequest {
method_data: vec![PaymentMethodData {
supported_methods: "CARD".into(),
data: Some(HashMap::from([(
"payment_processor_url".into(),
serde_json::Value::String("http://example.com/pay".into()),
)])),
}],
details: PaymentDetailsInit {
id: "order_123".into(),
display_items: vec![PaymentItem {
label: "Cool Shoes".into(),
amount: PaymentCurrencyAmount {
currency: "USD".into(),
value: 120.0,
},
pending: None,
refund_period: 30,
}],
shipping_options: None,
modifiers: None,
total: PaymentItem {
label: "Total".into(),
amount: PaymentCurrencyAmount {
currency: "USD".into(),
value: 120.0,
},
pending: None,
refund_period: 30,
},
},
options: Some(PaymentOptions {
request_payer_name: Some(false),
request_payer_email: Some(false),
request_payer_phone: Some(false),
request_shipping: Some(true),
shipping_type: None,
}),
shipping_address: None,
};
let json = serde_json::to_string(&req).unwrap();
let back: PaymentRequest = serde_json::from_str(&json).unwrap();
assert_eq!(req, back);
}
#[test]
fn payment_response_minimal() {
let resp = PaymentResponse {
request_id: "order_123".into(),
method_name: "CARD".into(),
details: None,
shipping_address: None,
shipping_option: None,
payer_name: None,
payer_email: None,
payer_phone: None,
};
let json = serde_json::to_string(&resp).unwrap();
let back: PaymentResponse = serde_json::from_str(&json).unwrap();
assert_eq!(resp, back);
}
#[test]
fn payment_method_data_single_string() {
let json = r#"{"supported_methods":"CARD"}"#;
let pmd: PaymentMethodData = serde_json::from_str(json).unwrap();
assert_eq!(pmd.supported_methods, "CARD");
}
}