Skip to main content

stateset_core/errors/
payment.rs

1//! Payment-domain errors.
2
3use uuid::Uuid;
4
5/// Errors specific to payment operations.
6///
7/// # Example
8///
9/// ```rust
10/// use stateset_core::errors::PaymentError;
11///
12/// let err = PaymentError::not_found(uuid::Uuid::nil());
13/// assert!(err.to_string().contains("not found"));
14/// ```
15#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum PaymentError {
18    /// Payment with the given ID was not found.
19    #[error("payment not found: {0}")]
20    NotFound(Uuid),
21
22    /// Payment was declined by the processor.
23    #[error("payment declined: {reason}")]
24    Declined {
25        /// The decline reason from the processor.
26        reason: String,
27    },
28
29    /// Refund cannot be processed.
30    #[error("refund failed: {reason}")]
31    RefundFailed {
32        /// The reason the refund failed.
33        reason: String,
34    },
35
36    /// Currency mismatch between payment and order.
37    #[error("currency mismatch: expected {expected}, got {got}")]
38    CurrencyMismatch {
39        /// The expected currency code.
40        expected: String,
41        /// The actual currency code provided.
42        got: String,
43    },
44
45    /// Invalid status transition for a payment.
46    #[error("{0}")]
47    InvalidTransition(super::StateTransitionError<String>),
48
49    /// Extensibility.
50    #[error(transparent)]
51    Other(Box<dyn std::error::Error + Send + Sync>),
52}
53
54impl PaymentError {
55    /// Convenience constructor for `NotFound`.
56    #[inline]
57    #[track_caller]
58    #[must_use]
59    pub const fn not_found(id: Uuid) -> Self {
60        Self::NotFound(id)
61    }
62
63    /// Convenience constructor for `Declined`.
64    #[track_caller]
65    pub fn declined(reason: impl Into<String>) -> Self {
66        Self::Declined { reason: reason.into() }
67    }
68
69    /// Convenience constructor for `RefundFailed`.
70    #[track_caller]
71    pub fn refund_failed(reason: impl Into<String>) -> Self {
72        Self::RefundFailed { reason: reason.into() }
73    }
74
75    /// Convenience constructor for `CurrencyMismatch`.
76    #[track_caller]
77    pub fn currency_mismatch(expected: impl Into<String>, got: impl Into<String>) -> Self {
78        Self::CurrencyMismatch { expected: expected.into(), got: got.into() }
79    }
80
81    /// Convenience constructor for `InvalidTransition`.
82    #[track_caller]
83    pub fn invalid_transition(from: impl std::fmt::Display, to: impl std::fmt::Display) -> Self {
84        Self::InvalidTransition(super::StateTransitionError::new(from.to_string(), to.to_string()))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn not_found_display() {
94        let err = PaymentError::not_found(Uuid::nil());
95        assert!(err.to_string().contains("not found"));
96    }
97
98    #[test]
99    fn declined_display() {
100        let err = PaymentError::declined("insufficient funds");
101        assert!(err.to_string().contains("insufficient funds"));
102    }
103
104    #[test]
105    fn refund_failed_display() {
106        let err = PaymentError::refund_failed("already refunded");
107        assert!(err.to_string().contains("already refunded"));
108    }
109
110    #[test]
111    fn currency_mismatch_display() {
112        let err = PaymentError::currency_mismatch("USD", "EUR");
113        let msg = err.to_string();
114        assert!(msg.contains("USD"));
115        assert!(msg.contains("EUR"));
116    }
117
118    #[test]
119    fn invalid_transition_display() {
120        let err = PaymentError::invalid_transition("pending", "refunded");
121        assert!(err.to_string().contains("pending"));
122        assert!(err.to_string().contains("refunded"));
123    }
124
125    #[test]
126    fn converts_to_commerce_error() {
127        use crate::errors::CommerceError;
128
129        let pay_err = PaymentError::not_found(Uuid::nil());
130        let commerce_err: CommerceError = pay_err.into();
131        assert!(commerce_err.is_not_found());
132    }
133}