Skip to main content

use_payment/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_money::Money;
8
9/// Common payment primitives.
10pub mod prelude {
11    pub use crate::{
12        Payment, PaymentDirection, PaymentError, PaymentMethod, PaymentReference, PaymentStatus,
13    };
14}
15
16/// A non-empty payment reference.
17#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub struct PaymentReference(String);
19
20impl PaymentReference {
21    /// Creates a payment reference from non-empty text.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`PaymentError::EmptyReference`] when the trimmed input is empty.
26    pub fn new(value: impl AsRef<str>) -> Result<Self, PaymentError> {
27        let value = value.as_ref().trim();
28        if value.is_empty() {
29            return Err(PaymentError::EmptyReference);
30        }
31
32        Ok(Self(value.to_string()))
33    }
34
35    /// Returns the payment reference.
36    #[must_use]
37    pub fn as_str(&self) -> &str {
38        &self.0
39    }
40}
41
42impl fmt::Display for PaymentReference {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        formatter.write_str(self.as_str())
45    }
46}
47
48impl FromStr for PaymentReference {
49    type Err = PaymentError;
50
51    fn from_str(value: &str) -> Result<Self, Self::Err> {
52        Self::new(value)
53    }
54}
55
56/// Conservative payment method vocabulary.
57#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum PaymentMethod {
59    /// Automated Clearing House payment.
60    Ach,
61    /// Wire transfer.
62    Wire,
63    /// Card payment.
64    Card,
65    /// Check payment.
66    Check,
67    /// Cash payment.
68    Cash,
69    /// Bank transfer.
70    BankTransfer,
71    /// Other payment method.
72    Other,
73}
74
75/// Payment lifecycle status.
76#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub enum PaymentStatus {
78    /// Payment has been created but not completed.
79    Pending,
80    /// Payment is processing.
81    Processing,
82    /// Payment has completed.
83    Completed,
84    /// Payment failed.
85    Failed,
86    /// Payment was canceled.
87    Canceled,
88    /// Payment was returned.
89    Returned,
90}
91
92/// Payment direction.
93#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
94pub enum PaymentDirection {
95    /// Incoming payment.
96    Inbound,
97    /// Outgoing payment.
98    Outbound,
99}
100
101/// A payment value with reference, amount, method, direction, and status.
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct Payment {
104    reference: PaymentReference,
105    amount: Money,
106    method: PaymentMethod,
107    direction: PaymentDirection,
108    status: PaymentStatus,
109}
110
111impl Payment {
112    /// Creates a pending payment.
113    #[must_use]
114    pub const fn new(
115        reference: PaymentReference,
116        amount: Money,
117        method: PaymentMethod,
118        direction: PaymentDirection,
119    ) -> Self {
120        Self {
121            reference,
122            amount,
123            method,
124            direction,
125            status: PaymentStatus::Pending,
126        }
127    }
128
129    /// Returns the payment reference.
130    #[must_use]
131    pub const fn reference(&self) -> &PaymentReference {
132        &self.reference
133    }
134
135    /// Returns the payment amount.
136    #[must_use]
137    pub const fn amount(&self) -> &Money {
138        &self.amount
139    }
140
141    /// Returns the payment method.
142    #[must_use]
143    pub const fn method(&self) -> PaymentMethod {
144        self.method
145    }
146
147    /// Returns the payment direction.
148    #[must_use]
149    pub const fn direction(&self) -> PaymentDirection {
150        self.direction
151    }
152
153    /// Returns the payment status.
154    #[must_use]
155    pub const fn status(&self) -> PaymentStatus {
156        self.status
157    }
158
159    /// Sets the payment status.
160    #[must_use]
161    pub const fn with_status(mut self, status: PaymentStatus) -> Self {
162        self.status = status;
163        self
164    }
165}
166
167/// Errors returned by payment primitives.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub enum PaymentError {
170    /// The reference was empty after trimming whitespace.
171    EmptyReference,
172}
173
174impl fmt::Display for PaymentError {
175    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Self::EmptyReference => formatter.write_str("payment reference cannot be empty"),
178        }
179    }
180}
181
182impl Error for PaymentError {}
183
184#[cfg(test)]
185mod tests {
186    use use_amount::Amount;
187    use use_currency::CurrencyCode;
188    use use_money::Money;
189
190    use super::{
191        Payment, PaymentDirection, PaymentError, PaymentMethod, PaymentReference, PaymentStatus,
192    };
193
194    #[test]
195    fn creates_payment() -> Result<(), Box<dyn std::error::Error>> {
196        let payment = Payment::new(
197            PaymentReference::new("pay-1001")?,
198            Money::new(
199                Amount::from_minor_units(12_345, 2)?,
200                CurrencyCode::new("USD")?,
201            ),
202            PaymentMethod::Ach,
203            PaymentDirection::Inbound,
204        )
205        .with_status(PaymentStatus::Completed);
206
207        assert_eq!(payment.reference().as_str(), "pay-1001");
208        assert_eq!(payment.status(), PaymentStatus::Completed);
209        assert_eq!(payment.method(), PaymentMethod::Ach);
210        Ok(())
211    }
212
213    #[test]
214    fn rejects_empty_reference() {
215        assert_eq!(PaymentReference::new(""), Err(PaymentError::EmptyReference));
216    }
217}