1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use super::contact::ContactAddress;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct PaymentCurrencyAmount {
18 pub currency: String,
20 pub value: f64,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct PaymentItem {
29 pub label: String,
31 pub amount: PaymentCurrencyAmount,
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub pending: Option<bool>,
36 #[serde(
38 default = "default_refund_period",
39 deserialize_with = "deserialize_refund_period"
40 )]
41 pub refund_period: i32,
42}
43
44fn default_refund_period() -> i32 {
45 30
46}
47
48fn deserialize_refund_period<'de, D>(deserializer: D) -> Result<i32, D::Error>
49where
50 D: serde::Deserializer<'de>,
51{
52 #[derive(Deserialize)]
53 #[serde(untagged)]
54 enum IntOrFloat {
55 Int(i32),
56 Float(f64),
57 }
58
59 match IntOrFloat::deserialize(deserializer)? {
60 IntOrFloat::Int(i) => Ok(i),
61 IntOrFloat::Float(f) => Ok(f as i32),
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct PaymentShippingOption {
70 pub id: String,
72 pub label: String,
74 pub amount: PaymentCurrencyAmount,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub selected: Option<bool>,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct PaymentOptions {
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub request_payer_name: Option<bool>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub request_payer_email: Option<bool>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub request_payer_phone: Option<bool>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub request_shipping: Option<bool>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub shipping_type: Option<String>,
97}
98
99impl Default for PaymentOptions {
100 fn default() -> Self {
101 Self {
102 request_payer_name: Some(false),
103 request_payer_email: Some(false),
104 request_payer_phone: Some(false),
105 request_shipping: Some(true),
106 shipping_type: None,
107 }
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct PaymentMethodData {
116 pub supported_methods: String,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub data: Option<HashMap<String, serde_json::Value>>,
121}
122
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub struct PaymentDetailsModifier {
128 pub supported_methods: String,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub total: Option<PaymentItem>,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub additional_display_items: Option<Vec<PaymentItem>>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub data: Option<HashMap<String, serde_json::Value>>,
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct PaymentDetailsInit {
146 pub id: String,
148 pub display_items: Vec<PaymentItem>,
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub shipping_options: Option<Vec<PaymentShippingOption>>,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub modifiers: Option<Vec<PaymentDetailsModifier>>,
156 pub total: PaymentItem,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub struct PaymentRequest {
165 pub method_data: Vec<PaymentMethodData>,
167 pub details: PaymentDetailsInit,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub options: Option<PaymentOptions>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub shipping_address: Option<ContactAddress>,
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct PaymentResponse {
182 pub request_id: String,
184 pub method_name: String,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub details: Option<HashMap<String, serde_json::Value>>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub shipping_address: Option<ContactAddress>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub shipping_option: Option<PaymentShippingOption>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub payer_name: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub payer_email: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub payer_phone: Option<String>,
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn payment_currency_amount_roundtrip() {
207 let amt = PaymentCurrencyAmount {
208 currency: "USD".into(),
209 value: 120.0,
210 };
211 let json = serde_json::to_string(&amt).unwrap();
212 let back: PaymentCurrencyAmount = serde_json::from_str(&json).unwrap();
213 assert_eq!(amt, back);
214 }
215
216 #[test]
217 fn payment_item_default_refund_period() {
218 let json = r#"{"label":"Shoes","amount":{"currency":"USD","value":99.99}}"#;
219 let item: PaymentItem = serde_json::from_str(json).unwrap();
220 assert_eq!(item.refund_period, 30);
221 }
222
223 #[test]
224 fn payment_request_full_roundtrip() {
225 let req = PaymentRequest {
226 method_data: vec![PaymentMethodData {
227 supported_methods: "CARD".into(),
228 data: Some(HashMap::from([(
229 "payment_processor_url".into(),
230 serde_json::Value::String("http://example.com/pay".into()),
231 )])),
232 }],
233 details: PaymentDetailsInit {
234 id: "order_123".into(),
235 display_items: vec![PaymentItem {
236 label: "Cool Shoes".into(),
237 amount: PaymentCurrencyAmount {
238 currency: "USD".into(),
239 value: 120.0,
240 },
241 pending: None,
242 refund_period: 30,
243 }],
244 shipping_options: None,
245 modifiers: None,
246 total: PaymentItem {
247 label: "Total".into(),
248 amount: PaymentCurrencyAmount {
249 currency: "USD".into(),
250 value: 120.0,
251 },
252 pending: None,
253 refund_period: 30,
254 },
255 },
256 options: Some(PaymentOptions {
257 request_payer_name: Some(false),
258 request_payer_email: Some(false),
259 request_payer_phone: Some(false),
260 request_shipping: Some(true),
261 shipping_type: None,
262 }),
263 shipping_address: None,
264 };
265 let json = serde_json::to_string(&req).unwrap();
266 let back: PaymentRequest = serde_json::from_str(&json).unwrap();
267 assert_eq!(req, back);
268 }
269
270 #[test]
271 fn payment_response_minimal() {
272 let resp = PaymentResponse {
273 request_id: "order_123".into(),
274 method_name: "CARD".into(),
275 details: None,
276 shipping_address: None,
277 shipping_option: None,
278 payer_name: None,
279 payer_email: None,
280 payer_phone: None,
281 };
282 let json = serde_json::to_string(&resp).unwrap();
283 let back: PaymentResponse = serde_json::from_str(&json).unwrap();
284 assert_eq!(resp, back);
285 }
286
287 #[test]
288 fn payment_method_data_single_string() {
289 let json = r#"{"supported_methods":"CARD"}"#;
291 let pmd: PaymentMethodData = serde_json::from_str(json).unwrap();
292 assert_eq!(pmd.supported_methods, "CARD");
293 }
294}