1use bitcoin::Amount;
4use serde::Serialize;
5use std::collections::HashMap;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
10pub enum ConfirmationLevel {
11 Unconfirmed,
13 OneConfirmation,
15 ThreeConfirmations,
17 SixConfirmations,
19}
20
21impl ConfirmationLevel {
22 pub fn from_count(confirmations: u32) -> Self {
24 match confirmations {
25 0 => Self::Unconfirmed,
26 1..=2 => Self::OneConfirmation,
27 3..=5 => Self::ThreeConfirmations,
28 _ => Self::SixConfirmations,
29 }
30 }
31
32 pub fn min_confirmations(&self) -> u32 {
34 match self {
35 Self::Unconfirmed => 0,
36 Self::OneConfirmation => 1,
37 Self::ThreeConfirmations => 3,
38 Self::SixConfirmations => 6,
39 }
40 }
41
42 pub fn meets_threshold(&self, required: ConfirmationLevel) -> bool {
44 *self >= required
45 }
46
47 pub fn description(&self) -> &'static str {
49 match self {
50 Self::Unconfirmed => "Unconfirmed (waiting for first block)",
51 Self::OneConfirmation => "1 confirmation (initial confirmation)",
52 Self::ThreeConfirmations => "3+ confirmations (reasonably secure)",
53 Self::SixConfirmations => "6+ confirmations (highly secure)",
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct TrackedOrder {
61 pub order_id: Uuid,
63 pub address: String,
65 pub expected_amount: Amount,
67 pub txid: Option<String>,
69 pub confirmations: u32,
71 pub level: ConfirmationLevel,
73 pub previous_level: ConfirmationLevel,
75 pub received_amount: Option<Amount>,
77}
78
79impl TrackedOrder {
80 pub fn new(order_id: Uuid, address: String, expected_amount: Amount) -> Self {
82 Self {
83 order_id,
84 address,
85 expected_amount,
86 txid: None,
87 confirmations: 0,
88 level: ConfirmationLevel::Unconfirmed,
89 previous_level: ConfirmationLevel::Unconfirmed,
90 received_amount: None,
91 }
92 }
93
94 pub fn update_confirmations(&mut self, confirmations: u32) -> Option<ConfirmationEvent> {
96 self.confirmations = confirmations;
97 let new_level = ConfirmationLevel::from_count(confirmations);
98
99 if new_level != self.level {
100 let event = ConfirmationEvent {
101 order_id: self.order_id,
102 previous_level: self.level,
103 new_level,
104 confirmations,
105 txid: self.txid.clone(),
106 };
107 self.previous_level = self.level;
108 self.level = new_level;
109 Some(event)
110 } else {
111 None
112 }
113 }
114
115 pub fn is_confirmed(&self, required: ConfirmationLevel) -> bool {
117 self.level.meets_threshold(required)
118 }
119}
120
121#[derive(Debug, Clone, Serialize)]
123pub struct ConfirmationEvent {
124 pub order_id: Uuid,
126 pub previous_level: ConfirmationLevel,
128 pub new_level: ConfirmationLevel,
130 pub confirmations: u32,
132 pub txid: Option<String>,
134}
135
136impl ConfirmationEvent {
137 pub fn notification_message(&self) -> String {
139 match self.new_level {
140 ConfirmationLevel::Unconfirmed => {
141 format!("Payment detected in mempool for order {}", self.order_id)
142 }
143 ConfirmationLevel::OneConfirmation => {
144 format!(
145 "Payment received first confirmation for order {} (tx: {})",
146 self.order_id,
147 self.txid.as_deref().unwrap_or("unknown")
148 )
149 }
150 ConfirmationLevel::ThreeConfirmations => {
151 format!(
152 "Payment reached 3 confirmations for order {} - reasonably secure",
153 self.order_id
154 )
155 }
156 ConfirmationLevel::SixConfirmations => {
157 format!(
158 "Payment fully confirmed for order {} - 6+ confirmations",
159 self.order_id
160 )
161 }
162 }
163 }
164
165 pub fn is_significant(&self) -> bool {
167 true
169 }
170}
171
172#[derive(Debug, Clone, Serialize)]
174pub enum NotificationType {
175 PaymentDetected,
177 FirstConfirmation,
179 ReasonablySecure,
181 FullyConfirmed,
183}
184
185impl From<ConfirmationLevel> for NotificationType {
186 fn from(level: ConfirmationLevel) -> Self {
187 match level {
188 ConfirmationLevel::Unconfirmed => Self::PaymentDetected,
189 ConfirmationLevel::OneConfirmation => Self::FirstConfirmation,
190 ConfirmationLevel::ThreeConfirmations => Self::ReasonablySecure,
191 ConfirmationLevel::SixConfirmations => Self::FullyConfirmed,
192 }
193 }
194}
195
196pub struct ConfirmationTracker {
198 orders: HashMap<Uuid, TrackedOrder>,
200 required_level: ConfirmationLevel,
202}
203
204impl ConfirmationTracker {
205 pub fn new(required_level: ConfirmationLevel) -> Self {
207 Self {
208 orders: HashMap::new(),
209 required_level,
210 }
211 }
212
213 pub fn with_defaults() -> Self {
215 Self::new(ConfirmationLevel::ThreeConfirmations)
216 }
217
218 pub fn track(&mut self, order_id: Uuid, address: String, expected_amount: Amount) {
220 let order = TrackedOrder::new(order_id, address, expected_amount);
221 self.orders.insert(order_id, order);
222 }
223
224 pub fn untrack(&mut self, order_id: Uuid) -> Option<TrackedOrder> {
226 self.orders.remove(&order_id)
227 }
228
229 pub fn update(
231 &mut self,
232 order_id: Uuid,
233 confirmations: u32,
234 txid: Option<String>,
235 received_amount: Option<Amount>,
236 ) -> Option<ConfirmationEvent> {
237 if let Some(order) = self.orders.get_mut(&order_id) {
238 if let Some(txid) = txid {
239 order.txid = Some(txid);
240 }
241 if let Some(amount) = received_amount {
242 order.received_amount = Some(amount);
243 }
244 order.update_confirmations(confirmations)
245 } else {
246 None
247 }
248 }
249
250 pub fn get(&self, order_id: Uuid) -> Option<&TrackedOrder> {
252 self.orders.get(&order_id)
253 }
254
255 pub fn is_confirmed(&self, order_id: Uuid) -> bool {
257 self.orders
258 .get(&order_id)
259 .map(|o| o.is_confirmed(self.required_level))
260 .unwrap_or(false)
261 }
262
263 pub fn orders_at_level(&self, level: ConfirmationLevel) -> Vec<&TrackedOrder> {
265 self.orders.values().filter(|o| o.level == level).collect()
266 }
267
268 pub fn confirmed_orders(&self) -> Vec<&TrackedOrder> {
270 self.orders
271 .values()
272 .filter(|o| o.is_confirmed(self.required_level))
273 .collect()
274 }
275
276 pub fn pending_orders(&self) -> Vec<&TrackedOrder> {
278 self.orders
279 .values()
280 .filter(|o| !o.is_confirmed(self.required_level))
281 .collect()
282 }
283
284 pub fn stats(&self) -> ConfirmationStats {
286 let mut stats = ConfirmationStats::default();
287 for order in self.orders.values() {
288 match order.level {
289 ConfirmationLevel::Unconfirmed => stats.unconfirmed += 1,
290 ConfirmationLevel::OneConfirmation => stats.one_confirmation += 1,
291 ConfirmationLevel::ThreeConfirmations => stats.three_confirmations += 1,
292 ConfirmationLevel::SixConfirmations => stats.six_confirmations += 1,
293 }
294 }
295 stats.total = self.orders.len();
296 stats
297 }
298}
299
300#[derive(Debug, Clone, Default, Serialize)]
302pub struct ConfirmationStats {
303 pub total: usize,
305 pub unconfirmed: usize,
307 pub one_confirmation: usize,
309 pub three_confirmations: usize,
311 pub six_confirmations: usize,
313}
314
315impl Default for ConfirmationTracker {
316 fn default() -> Self {
317 Self::with_defaults()
318 }
319}