1use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14#[serde(rename_all = "snake_case")]
15pub enum MandateType {
16 Intent,
17 Cart,
18 Payment,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[serde(rename_all = "snake_case")]
24pub enum MandateStatus {
25 Pending,
26 Active,
27 Completed,
28 Cancelled,
29 Expired,
30}
31
32pub trait Mandate {
34 fn mandate_type(&self) -> MandateType;
35 fn issuer(&self) -> &str;
36 fn created_at(&self) -> DateTime<Utc>;
37 fn expires_at(&self) -> Option<DateTime<Utc>>;
38 fn status(&self) -> MandateStatus;
39 fn is_valid(&self) -> bool {
40 let now = Utc::now();
41 match self.expires_at() {
42 Some(expiry) => now < expiry && self.status() == MandateStatus::Active,
43 None => self.status() == MandateStatus::Active,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct IntentMandate {
51 pub id: String,
52 pub issuer: String, pub subject_agent: String, pub intent_description: String,
55 pub permissions: Vec<Permission>,
56 pub constraints: HashMap<String, serde_json::Value>,
57 pub created_at: DateTime<Utc>,
58 pub expires_at: Option<DateTime<Utc>>,
59 pub status: MandateStatus,
60 pub metadata: HashMap<String, String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
65pub struct Permission {
66 pub action: String,
67 pub resource: String,
68 pub conditions: Vec<String>,
69}
70
71impl IntentMandate {
72 pub fn new(issuer: String, subject_agent: String, intent_description: String) -> Self {
73 Self {
74 id: format!("intent:{}", uuid::Uuid::new_v4()),
75 issuer,
76 subject_agent,
77 intent_description,
78 permissions: Vec::new(),
79 constraints: HashMap::new(),
80 created_at: Utc::now(),
81 expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
82 status: MandateStatus::Active,
83 metadata: HashMap::new(),
84 }
85 }
86
87 pub fn add_permission(&mut self, permission: Permission) {
88 self.permissions.push(permission);
89 }
90
91 pub fn add_constraint(&mut self, key: String, value: serde_json::Value) {
92 self.constraints.insert(key, value);
93 }
94
95 pub fn has_permission(&self, action: &str, resource: &str) -> bool {
96 self.permissions
97 .iter()
98 .any(|p| p.action == action && p.resource == resource)
99 }
100
101 pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
102 self.expires_at = Some(expires_at);
103 self
104 }
105
106 pub fn cancel(&mut self) {
107 self.status = MandateStatus::Cancelled;
108 }
109}
110
111impl Mandate for IntentMandate {
112 fn mandate_type(&self) -> MandateType {
113 MandateType::Intent
114 }
115
116 fn issuer(&self) -> &str {
117 &self.issuer
118 }
119
120 fn created_at(&self) -> DateTime<Utc> {
121 self.created_at
122 }
123
124 fn expires_at(&self) -> Option<DateTime<Utc>> {
125 self.expires_at
126 }
127
128 fn status(&self) -> MandateStatus {
129 self.status.clone()
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct CartMandate {
136 pub id: String,
137 pub issuer: String, pub merchant: String, pub items: Vec<CartItem>,
140 pub total_amount: u64, pub currency: String,
142 pub tax_amount: Option<u64>,
143 pub shipping_amount: Option<u64>,
144 pub discount_amount: Option<u64>,
145 pub created_at: DateTime<Utc>,
146 pub expires_at: Option<DateTime<Utc>>,
147 pub status: MandateStatus,
148 pub metadata: HashMap<String, String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct CartItem {
154 pub id: String,
155 pub name: String,
156 pub description: Option<String>,
157 pub quantity: u32,
158 pub unit_price: u64,
159 pub total_price: u64,
160 pub metadata: HashMap<String, String>,
161}
162
163impl CartItem {
164 pub fn new(id: String, name: String, quantity: u32, unit_price: u64) -> Self {
165 Self {
166 id,
167 name,
168 description: None,
169 quantity,
170 unit_price,
171 total_price: unit_price * quantity as u64,
172 metadata: HashMap::new(),
173 }
174 }
175
176 pub fn with_description(mut self, description: String) -> Self {
177 self.description = Some(description);
178 self
179 }
180
181 pub fn with_metadata(mut self, key: String, value: String) -> Self {
182 self.metadata.insert(key, value);
183 self
184 }
185}
186
187impl CartMandate {
188 pub fn new(issuer: String, items: Vec<CartItem>, total_amount: u64, currency: String) -> Self {
189 Self {
190 id: format!("cart:{}", uuid::Uuid::new_v4()),
191 issuer,
192 merchant: String::new(),
193 items,
194 total_amount,
195 currency,
196 tax_amount: None,
197 shipping_amount: None,
198 discount_amount: None,
199 created_at: Utc::now(),
200 expires_at: Some(Utc::now() + chrono::Duration::hours(1)),
201 status: MandateStatus::Active,
202 metadata: HashMap::new(),
203 }
204 }
205
206 pub fn calculate_total(&self) -> u64 {
207 let items_total: u64 = self.items.iter().map(|item| item.total_price).sum();
208 let tax = self.tax_amount.unwrap_or(0);
209 let shipping = self.shipping_amount.unwrap_or(0);
210 let discount = self.discount_amount.unwrap_or(0);
211
212 items_total + tax + shipping - discount
213 }
214
215 pub fn verify_total(&self) -> bool {
216 self.calculate_total() == self.total_amount
217 }
218
219 pub fn with_merchant(mut self, merchant: String) -> Self {
220 self.merchant = merchant;
221 self
222 }
223
224 pub fn with_tax(mut self, tax_amount: u64) -> Self {
225 self.tax_amount = Some(tax_amount);
226 self
227 }
228
229 pub fn with_shipping(mut self, shipping_amount: u64) -> Self {
230 self.shipping_amount = Some(shipping_amount);
231 self
232 }
233
234 pub fn with_discount(mut self, discount_amount: u64) -> Self {
235 self.discount_amount = Some(discount_amount);
236 self
237 }
238
239 pub fn complete(&mut self) {
240 self.status = MandateStatus::Completed;
241 }
242
243 pub fn cancel(&mut self) {
244 self.status = MandateStatus::Cancelled;
245 }
246}
247
248impl Mandate for CartMandate {
249 fn mandate_type(&self) -> MandateType {
250 MandateType::Cart
251 }
252
253 fn issuer(&self) -> &str {
254 &self.issuer
255 }
256
257 fn created_at(&self) -> DateTime<Utc> {
258 self.created_at
259 }
260
261 fn expires_at(&self) -> Option<DateTime<Utc>> {
262 self.expires_at
263 }
264
265 fn status(&self) -> MandateStatus {
266 self.status.clone()
267 }
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct PaymentMandate {
273 pub id: String,
274 pub issuer: String, pub recipient: String, pub amount: u64,
277 pub currency: String,
278 pub payment_method: PaymentMethod,
279 pub payment_network: Option<String>,
280 pub reference: Option<String>,
281 pub cart_mandate_id: Option<String>,
282 pub created_at: DateTime<Utc>,
283 pub expires_at: Option<DateTime<Utc>>,
284 pub status: MandateStatus,
285 pub metadata: HashMap<String, String>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290#[serde(rename_all = "snake_case")]
291pub enum PaymentMethod {
292 CreditCard { last_four: String },
293 BankTransfer { account_id: String },
294 Cryptocurrency { chain: String, token: String },
295 DigitalWallet { provider: String },
296 Other { method_type: String },
297}
298
299impl PaymentMandate {
300 pub fn new(
301 issuer: String,
302 recipient: String,
303 amount: u64,
304 currency: String,
305 payment_method_type: String,
306 ) -> Self {
307 Self {
308 id: format!("payment:{}", uuid::Uuid::new_v4()),
309 issuer,
310 recipient,
311 amount,
312 currency,
313 payment_method: PaymentMethod::Other {
314 method_type: payment_method_type,
315 },
316 payment_network: None,
317 reference: None,
318 cart_mandate_id: None,
319 created_at: Utc::now(),
320 expires_at: Some(Utc::now() + chrono::Duration::minutes(30)),
321 status: MandateStatus::Pending,
322 metadata: HashMap::new(),
323 }
324 }
325
326 pub fn with_payment_method(mut self, method: PaymentMethod) -> Self {
327 self.payment_method = method;
328 self
329 }
330
331 pub fn with_payment_network(mut self, network: String) -> Self {
332 self.payment_network = Some(network);
333 self
334 }
335
336 pub fn with_reference(mut self, reference: String) -> Self {
337 self.reference = Some(reference);
338 self
339 }
340
341 pub fn link_cart_mandate(mut self, cart_mandate_id: String) -> Self {
342 self.cart_mandate_id = Some(cart_mandate_id);
343 self
344 }
345
346 pub fn activate(&mut self) {
347 self.status = MandateStatus::Active;
348 }
349
350 pub fn complete(&mut self) {
351 self.status = MandateStatus::Completed;
352 }
353
354 pub fn cancel(&mut self) {
355 self.status = MandateStatus::Cancelled;
356 }
357}
358
359impl Mandate for PaymentMandate {
360 fn mandate_type(&self) -> MandateType {
361 MandateType::Payment
362 }
363
364 fn issuer(&self) -> &str {
365 &self.issuer
366 }
367
368 fn created_at(&self) -> DateTime<Utc> {
369 self.created_at
370 }
371
372 fn expires_at(&self) -> Option<DateTime<Utc>> {
373 self.expires_at
374 }
375
376 fn status(&self) -> MandateStatus {
377 self.status.clone()
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_intent_mandate_creation() {
387 let mandate = IntentMandate::new(
388 "did:example:user".to_string(),
389 "did:example:agent".to_string(),
390 "Purchase items on behalf of user".to_string(),
391 );
392
393 assert_eq!(mandate.mandate_type(), MandateType::Intent);
394 assert_eq!(mandate.status(), MandateStatus::Active);
395 assert!(mandate.is_valid());
396 }
397
398 #[test]
399 fn test_cart_mandate_total_calculation() {
400 let items = vec![
401 CartItem::new("item1".to_string(), "Product A".to_string(), 2, 1000),
402 CartItem::new("item2".to_string(), "Product B".to_string(), 1, 1500),
403 ];
404
405 let mut mandate = CartMandate::new(
406 "did:example:user".to_string(),
407 items,
408 3500,
409 "USD".to_string(),
410 );
411
412 assert!(mandate.verify_total());
413
414 mandate = mandate.with_tax(350);
415 assert_eq!(mandate.calculate_total(), 3850);
416 }
417
418 #[test]
419 fn test_payment_mandate_lifecycle() {
420 let mut mandate = PaymentMandate::new(
421 "did:example:payer".to_string(),
422 "did:example:payee".to_string(),
423 5000,
424 "USD".to_string(),
425 "credit_card".to_string(),
426 );
427
428 assert_eq!(mandate.status(), MandateStatus::Pending);
429
430 mandate.activate();
431 assert_eq!(mandate.status(), MandateStatus::Active);
432
433 mandate.complete();
434 assert_eq!(mandate.status(), MandateStatus::Completed);
435 }
436
437 #[test]
438 fn test_cart_item_calculation() {
439 let item = CartItem::new("item1".to_string(), "Test Item".to_string(), 3, 500);
440 assert_eq!(item.total_price, 1500);
441 }
442}