1use serde::{Deserialize, Serialize};
4
5use super::payment_request::{PaymentItem, PaymentRequest, PaymentResponse};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct IntentMandate {
14 #[serde(default = "default_true")]
18 pub user_cart_confirmation_required: bool,
19
20 pub natural_language_description: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub merchants: Option<Vec<String>>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub skus: Option<Vec<String>>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub requires_refundability: Option<bool>,
35
36 pub intent_expiry: String,
38}
39
40fn default_true() -> bool {
41 true
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct CartContents {
47 pub id: String,
49
50 pub user_cart_confirmation_required: bool,
52
53 pub payment_request: PaymentRequest,
55
56 pub cart_expiry: String,
58
59 pub merchant_name: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct CartMandate {
69 pub contents: CartContents,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
75 pub merchant_authorization: Option<String>,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct PaymentMandateContents {
81 pub payment_mandate_id: String,
83
84 pub payment_details_id: String,
86
87 pub payment_details_total: PaymentItem,
89
90 pub payment_response: PaymentResponse,
92
93 pub merchant_agent: String,
95
96 pub timestamp: String,
98}
99
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct PaymentMandate {
106 pub payment_mandate_contents: PaymentMandateContents,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
112 pub user_authorization: Option<String>,
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::types::payment_request::{
119 PaymentCurrencyAmount, PaymentDetailsInit, PaymentMethodData,
120 };
121
122 fn sample_payment_request() -> PaymentRequest {
123 PaymentRequest {
124 method_data: vec![PaymentMethodData {
125 supported_methods: "CARD".into(),
126 data: None,
127 }],
128 details: PaymentDetailsInit {
129 id: "order_123".into(),
130 display_items: vec![PaymentItem {
131 label: "Shoes".into(),
132 amount: PaymentCurrencyAmount {
133 currency: "USD".into(),
134 value: 120.0,
135 },
136 pending: None,
137 refund_period: 30,
138 }],
139 shipping_options: None,
140 modifiers: None,
141 total: PaymentItem {
142 label: "Total".into(),
143 amount: PaymentCurrencyAmount {
144 currency: "USD".into(),
145 value: 120.0,
146 },
147 pending: None,
148 refund_period: 30,
149 },
150 },
151 options: None,
152 shipping_address: None,
153 }
154 }
155
156 #[test]
157 fn intent_mandate_default_confirmation() {
158 let json = r#"{
159 "natural_language_description": "Buy red shoes",
160 "intent_expiry": "2026-12-31T23:59:59Z"
161 }"#;
162 let im: IntentMandate = serde_json::from_str(json).unwrap();
163 assert!(im.user_cart_confirmation_required);
164 }
165
166 #[test]
167 fn intent_mandate_roundtrip() {
168 let im = IntentMandate {
169 user_cart_confirmation_required: false,
170 natural_language_description: "Cool red shoes".into(),
171 merchants: Some(vec!["nike".into()]),
172 skus: None,
173 requires_refundability: Some(true),
174 intent_expiry: "2026-09-16T15:00:00Z".into(),
175 };
176 let json = serde_json::to_string(&im).unwrap();
177 let back: IntentMandate = serde_json::from_str(&json).unwrap();
178 assert_eq!(im, back);
179 }
180
181 #[test]
182 fn cart_mandate_roundtrip() {
183 let cm = CartMandate {
184 contents: CartContents {
185 id: "cart_123".into(),
186 user_cart_confirmation_required: false,
187 payment_request: sample_payment_request(),
188 cart_expiry: "2026-12-31T23:59:59Z".into(),
189 merchant_name: "Cool Shoe Store".into(),
190 },
191 merchant_authorization: Some("eyJhbGci...".into()),
192 };
193 let json = serde_json::to_string(&cm).unwrap();
194 let back: CartMandate = serde_json::from_str(&json).unwrap();
195 assert_eq!(cm, back);
196 }
197
198 #[test]
199 fn payment_mandate_roundtrip() {
200 let pm = PaymentMandate {
201 payment_mandate_contents: PaymentMandateContents {
202 payment_mandate_id: "pm_123".into(),
203 payment_details_id: "order_123".into(),
204 payment_details_total: PaymentItem {
205 label: "Total".into(),
206 amount: PaymentCurrencyAmount {
207 currency: "USD".into(),
208 value: 120.0,
209 },
210 pending: None,
211 refund_period: 30,
212 },
213 payment_response: crate::types::payment_request::PaymentResponse {
214 request_id: "order_123".into(),
215 method_name: "CARD".into(),
216 details: None,
217 shipping_address: None,
218 shipping_option: None,
219 payer_name: None,
220 payer_email: None,
221 payer_phone: None,
222 },
223 merchant_agent: "MerchantAgent".into(),
224 timestamp: "2025-08-26T19:36:36Z".into(),
225 },
226 user_authorization: Some("eyJhbGci...".into()),
227 };
228 let json = serde_json::to_string(&pm).unwrap();
229 let back: PaymentMandate = serde_json::from_str(&json).unwrap();
230 assert_eq!(pm, back);
231 }
232
233 #[test]
234 fn compat_with_python_sdk_intent_message() {
235 let json = r#"{
237 "user_cart_confirmation_required": false,
238 "natural_language_description": "I'd like some cool red shoes in my size",
239 "merchants": null,
240 "skus": null,
241 "required_refundability": true,
242 "intent_expiry": "2025-09-16T15:00:00Z"
243 }"#;
244 let _im: IntentMandate = serde_json::from_str(json).unwrap();
249 }
250
251 #[test]
252 fn compat_with_python_sdk_cart_artifact() {
253 let json = r#"{
254 "contents": {
255 "id": "cart_shoes_123",
256 "user_cart_confirmation_required": false,
257 "user_signature_required": false,
258 "payment_request": {
259 "method_data": [{"supported_methods": "CARD", "data": {"payment_processor_url": "http://example.com/pay"}}],
260 "details": {
261 "id": "order_shoes_123",
262 "display_items": [{"label": "Cool Shoes Max", "amount": {"currency": "USD", "value": 120.0}}],
263 "total": {"label": "Total", "amount": {"currency": "USD", "value": 120.0}}
264 },
265 "options": {
266 "request_payer_name": false,
267 "request_payer_email": false,
268 "request_payer_phone": false,
269 "request_shipping": true
270 }
271 },
272 "cart_expiry": "2026-12-31T23:59:59Z",
273 "merchant_name": "Cool Shoe Store"
274 },
275 "merchant_authorization": "sig_merchant_shoes_abc1"
276 }"#;
277 let cm: CartMandate = serde_json::from_str(json).unwrap();
278 assert_eq!(cm.contents.id, "cart_shoes_123");
279 }
280}