domain/payment/mod.rs
1//! Payment-domain values for deposits, references, and review-safe money movement state.
2//!
3//! These types record payment readiness without performing provider writes. That keeps deposit
4//! exceptions and front-desk collection work reviewable:
5//!
6//! ```
7//! use domain::{money, payment};
8//!
9//! let amount = money::Money::new(
10//! money::MinorUnits::try_new(5_000).unwrap(),
11//! money::Currency::Usd,
12//! );
13//! let deposit = payment::Deposit::paid(
14//! amount,
15//! payment::Reference::try_new("sandbox-receipt-42").unwrap(),
16//! );
17//!
18//! assert_eq!(deposit.status(), payment::DepositStatus::Paid);
19//! assert!(!deposit.requires_collection());
20//! assert!(deposit.payment_reference().is_some());
21//! ```
22
23mod error;
24
25use chrono::{DateTime, Utc};
26use serde::{Deserialize, Deserializer, Serialize};
27
28use crate::money::Money;
29
30pub use error::{Error, Result};
31
32#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
33/// External payment or POS reference used to reconcile pet-resort charges.
34pub struct Reference(String);
35
36impl Reference {
37 /// Promotes an external payment/POS reference after trimming and length validation.
38 pub fn try_new(value: impl Into<String>) -> Result<Self> {
39 let value = value.into().trim().to_string();
40 if value.is_empty() {
41 return Err(Error::EmptyReference);
42 }
43 if value.chars().count() > 160 {
44 return Err(Error::ReferenceTooLong);
45 }
46 Ok(Self(value))
47 }
48
49 /// Returns the owned inner string for storage or outbound mapping.
50 pub fn into_inner(self) -> String {
51 self.0
52 }
53}
54
55impl<'de> Deserialize<'de> for Reference {
56 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 Self::try_new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65/// Lifecycle states for collecting, waiving, refunding, or failing a reservation deposit.
66pub enum DepositStatus {
67 /// No deposit or review is needed for this reservation path.
68 NotRequired,
69 /// A deposit must be collected before the booking is secure.
70 Required,
71 /// The required deposit has been collected and reconciled.
72 Paid,
73 /// The collected deposit has been returned to the customer.
74 Refunded,
75 /// Deposit collection was attempted but did not succeed.
76 Failed,
77 /// A manager waived the deposit requirement for the booking.
78 WaivedByManager,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82/// Deposit amount, status, refund window, and payment reference for a reservation.
83pub struct Deposit {
84 amount: Money,
85 refundable_until: Option<DateTime<Utc>>,
86 status: DepositStatus,
87 payment_reference: Option<Reference>,
88}
89
90impl Deposit {
91 /// Starts a deposit record that still needs front-desk collection.
92 pub const fn required(amount: Money) -> Self {
93 Self {
94 amount,
95 refundable_until: None,
96 status: DepositStatus::Required,
97 payment_reference: None,
98 }
99 }
100
101 /// Creates a deposit already reconciled to a payment reference.
102 pub fn paid(amount: Money, payment_reference: Reference) -> Self {
103 Self::required(amount).mark_paid(payment_reference)
104 }
105
106 /// Starts a deposit record waived by manager exception.
107 pub const fn waived(amount: Money) -> Self {
108 Self {
109 amount,
110 refundable_until: None,
111 status: DepositStatus::WaivedByManager,
112 payment_reference: None,
113 }
114 }
115
116 /// Sets the deadline through which the deposit remains refundable.
117 pub fn with_refundable_until(mut self, refundable_until: DateTime<Utc>) -> Self {
118 self.refundable_until = Some(refundable_until);
119 self
120 }
121
122 /// Marks the deposit as collected and stores the payment reference.
123 pub fn mark_paid(mut self, payment_reference: Reference) -> Self {
124 self.status = DepositStatus::Paid;
125 self.payment_reference = Some(payment_reference);
126 self
127 }
128
129 /// Returns the deposit amount recorded for payment review and reconciliation.
130 pub const fn amount(&self) -> &Money {
131 &self.amount
132 }
133
134 /// Returns the deposit refundable until recorded for payment review and reconciliation.
135 pub const fn refundable_until(&self) -> Option<DateTime<Utc>> {
136 self.refundable_until
137 }
138
139 /// Returns the deposit status recorded for payment review and reconciliation.
140 pub const fn status(&self) -> DepositStatus {
141 self.status
142 }
143
144 /// Returns the deposit payment reference recorded for payment review and reconciliation.
145 pub const fn payment_reference(&self) -> Option<&Reference> {
146 self.payment_reference.as_ref()
147 }
148
149 /// Reports whether this deposit still requires collection from the customer.
150 pub const fn requires_collection(&self) -> bool {
151 matches!(self.status, DepositStatus::Required | DepositStatus::Failed)
152 }
153}