Skip to main content

adk_payments/domain/
order.rs

1use serde::{Deserialize, Serialize};
2
3use crate::domain::ProtocolExtensions;
4use crate::kernel::PaymentsKernelError;
5
6/// Canonical order lifecycle state.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum OrderState {
10    Draft,
11    PendingPayment,
12    Authorized,
13    FulfillmentPending,
14    Fulfilled,
15    Completed,
16    Canceled,
17    Refunded,
18    PartiallyRefunded,
19    Failed,
20}
21
22impl OrderState {
23    /// Returns `true` when the transition is allowed by the canonical order
24    /// state machine.
25    #[must_use]
26    pub fn can_transition_to(self, next: Self) -> bool {
27        use OrderState::{
28            Authorized, Canceled, Completed, Draft, Failed, Fulfilled, FulfillmentPending,
29            PartiallyRefunded, PendingPayment, Refunded,
30        };
31
32        match (self, next) {
33            (from, to) if from == to => true,
34            (Draft, PendingPayment | Canceled | Failed) => true,
35            (PendingPayment, Authorized | Canceled | Failed) => true,
36            (Authorized, FulfillmentPending | Completed | Canceled | Failed) => true,
37            (FulfillmentPending, Fulfilled | Completed | Failed) => true,
38            (Fulfilled, Completed | PartiallyRefunded | Refunded | Failed) => true,
39            (Completed, PartiallyRefunded | Refunded) => true,
40            (PartiallyRefunded, PartiallyRefunded | Refunded) => true,
41            _ => false,
42        }
43    }
44}
45
46/// Canonical receipt or settlement lifecycle state.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum ReceiptState {
50    NotRequested,
51    Pending,
52    Authorized,
53    Settled,
54    Failed,
55    PartiallyRefunded,
56    Refunded,
57    Voided,
58}
59
60impl ReceiptState {
61    /// Returns `true` when the transition is allowed by the canonical receipt
62    /// state machine.
63    #[must_use]
64    pub fn can_transition_to(self, next: Self) -> bool {
65        use ReceiptState::{
66            Authorized, Failed, NotRequested, PartiallyRefunded, Pending, Refunded, Settled, Voided,
67        };
68
69        match (self, next) {
70            (from, to) if from == to => true,
71            (NotRequested, Pending | Authorized | Failed) => true,
72            (Pending, Authorized | Settled | Failed | Voided) => true,
73            (Authorized, Settled | Failed | Voided) => true,
74            (Settled, PartiallyRefunded | Refunded) => true,
75            (PartiallyRefunded, PartiallyRefunded | Refunded) => true,
76            _ => false,
77        }
78    }
79}
80
81/// Canonical order and receipt summary attached to a transaction.
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct OrderSnapshot {
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub order_id: Option<String>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub receipt_id: Option<String>,
89    pub state: OrderState,
90    pub receipt_state: ReceiptState,
91    #[serde(default, skip_serializing_if = "ProtocolExtensions::is_empty")]
92    pub extensions: ProtocolExtensions,
93}
94
95impl OrderSnapshot {
96    /// Applies one canonical order-state transition.
97    ///
98    /// # Errors
99    ///
100    /// Returns an error when the requested transition would skip or rewind the
101    /// canonical order lifecycle.
102    pub fn transition_order_state(
103        &mut self,
104        next: OrderState,
105    ) -> std::result::Result<(), PaymentsKernelError> {
106        if !self.state.can_transition_to(next) {
107            return Err(PaymentsKernelError::InvalidOrderTransition { from: self.state, to: next });
108        }
109
110        self.state = next;
111        Ok(())
112    }
113
114    /// Applies one canonical receipt-state transition.
115    ///
116    /// # Errors
117    ///
118    /// Returns an error when the requested transition would skip or rewind the
119    /// canonical receipt lifecycle.
120    pub fn transition_receipt_state(
121        &mut self,
122        next: ReceiptState,
123    ) -> std::result::Result<(), PaymentsKernelError> {
124        if !self.receipt_state.can_transition_to(next) {
125            return Err(PaymentsKernelError::InvalidReceiptTransition {
126                from: self.receipt_state,
127                to: next,
128            });
129        }
130
131        self.receipt_state = next;
132        Ok(())
133    }
134}