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
9pub mod prelude {
11 pub use crate::{
12 Payment, PaymentDirection, PaymentError, PaymentMethod, PaymentReference, PaymentStatus,
13 };
14}
15
16#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub struct PaymentReference(String);
19
20impl PaymentReference {
21 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 #[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#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum PaymentMethod {
59 Ach,
61 Wire,
63 Card,
65 Check,
67 Cash,
69 BankTransfer,
71 Other,
73}
74
75#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
77pub enum PaymentStatus {
78 Pending,
80 Processing,
82 Completed,
84 Failed,
86 Canceled,
88 Returned,
90}
91
92#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
94pub enum PaymentDirection {
95 Inbound,
97 Outbound,
99}
100
101#[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 #[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 #[must_use]
131 pub const fn reference(&self) -> &PaymentReference {
132 &self.reference
133 }
134
135 #[must_use]
137 pub const fn amount(&self) -> &Money {
138 &self.amount
139 }
140
141 #[must_use]
143 pub const fn method(&self) -> PaymentMethod {
144 self.method
145 }
146
147 #[must_use]
149 pub const fn direction(&self) -> PaymentDirection {
150 self.direction
151 }
152
153 #[must_use]
155 pub const fn status(&self) -> PaymentStatus {
156 self.status
157 }
158
159 #[must_use]
161 pub const fn with_status(mut self, status: PaymentStatus) -> Self {
162 self.status = status;
163 self
164 }
165}
166
167#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub enum PaymentError {
170 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}