1use anyhow::Result;
2use nostr_sdk::{PublicKey, Timestamp};
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "sqlx")]
5use sqlx::FromRow;
6#[cfg(feature = "sqlx")]
7use sqlx_crud::SqlxCrud;
8use std::{fmt::Display, str::FromStr};
9use uuid::Uuid;
10use wasm_bindgen::prelude::*;
11
12use crate::error::{CantDoReason, ServiceError};
13
14#[wasm_bindgen]
16#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
17#[serde(rename_all = "kebab-case")]
18pub enum Kind {
19 Buy,
20 Sell,
21}
22
23impl FromStr for Kind {
24 type Err = ();
25
26 fn from_str(kind: &str) -> std::result::Result<Self, Self::Err> {
27 match kind.to_lowercase().as_str() {
28 "buy" => std::result::Result::Ok(Self::Buy),
29 "sell" => std::result::Result::Ok(Self::Sell),
30 _ => Err(()),
31 }
32 }
33}
34
35impl Display for Kind {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Kind::Sell => write!(f, "sell"),
39 Kind::Buy => write!(f, "buy"),
40 }
41 }
42}
43
44#[wasm_bindgen]
46#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
47#[serde(rename_all = "kebab-case")]
48pub enum Status {
49 Active,
50 Canceled,
51 CanceledByAdmin,
52 SettledByAdmin,
53 CompletedByAdmin,
54 Dispute,
55 Expired,
56 FiatSent,
57 SettledHoldInvoice,
58 Pending,
59 Success,
60 WaitingBuyerInvoice,
61 WaitingPayment,
62 CooperativelyCanceled,
63}
64
65impl Display for Status {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Status::Active => write!(f, "active"),
69 Status::Canceled => write!(f, "canceled"),
70 Status::CanceledByAdmin => write!(f, "canceled-by-admin"),
71 Status::SettledByAdmin => write!(f, "settled-by-admin"),
72 Status::CompletedByAdmin => write!(f, "completed-by-admin"),
73 Status::Dispute => write!(f, "dispute"),
74 Status::Expired => write!(f, "expired"),
75 Status::FiatSent => write!(f, "fiat-sent"),
76 Status::SettledHoldInvoice => write!(f, "settled-hold-invoice"),
77 Status::Pending => write!(f, "pending"),
78 Status::Success => write!(f, "success"),
79 Status::WaitingBuyerInvoice => write!(f, "waiting-buyer-invoice"),
80 Status::WaitingPayment => write!(f, "waiting-payment"),
81 Status::CooperativelyCanceled => write!(f, "cooperatively-canceled"),
82 }
83 }
84}
85
86impl FromStr for Status {
87 type Err = ();
88 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
90 match s.to_lowercase().as_str() {
91 "active" => std::result::Result::Ok(Self::Active),
92 "canceled" => std::result::Result::Ok(Self::Canceled),
93 "canceled-by-admin" => std::result::Result::Ok(Self::CanceledByAdmin),
94 "settled-by-admin" => std::result::Result::Ok(Self::SettledByAdmin),
95 "completed-by-admin" => std::result::Result::Ok(Self::CompletedByAdmin),
96 "dispute" => std::result::Result::Ok(Self::Dispute),
97 "expired" => std::result::Result::Ok(Self::Expired),
98 "fiat-sent" => std::result::Result::Ok(Self::FiatSent),
99 "settled-hold-invoice" => std::result::Result::Ok(Self::SettledHoldInvoice),
100 "pending" => std::result::Result::Ok(Self::Pending),
101 "success" => std::result::Result::Ok(Self::Success),
102 "waiting-buyer-invoice" => std::result::Result::Ok(Self::WaitingBuyerInvoice),
103 "waiting-payment" => std::result::Result::Ok(Self::WaitingPayment),
104 "cooperatively-canceled" => std::result::Result::Ok(Self::CooperativelyCanceled),
105 _ => Err(()),
106 }
107 }
108}
109
110#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud), external_id)]
112#[derive(Debug, Default, Deserialize, Serialize, Clone)]
113pub struct Order {
114 pub id: Uuid,
115 pub kind: String,
116 pub event_id: String,
117 pub hash: Option<String>,
118 pub preimage: Option<String>,
119 pub creator_pubkey: String,
120 pub cancel_initiator_pubkey: Option<String>,
121 pub buyer_pubkey: Option<String>,
122 pub master_buyer_pubkey: Option<String>,
123 pub seller_pubkey: Option<String>,
124 pub master_seller_pubkey: Option<String>,
125 pub status: String,
126 pub price_from_api: bool,
127 pub premium: i64,
128 pub payment_method: String,
129 pub amount: i64,
130 pub min_amount: Option<i64>,
131 pub max_amount: Option<i64>,
132 pub buyer_dispute: bool,
133 pub seller_dispute: bool,
134 pub buyer_cooperativecancel: bool,
135 pub seller_cooperativecancel: bool,
136 pub fee: i64,
137 pub routing_fee: i64,
138 pub fiat_code: String,
139 pub fiat_amount: i64,
140 pub buyer_invoice: Option<String>,
141 pub range_parent_id: Option<Uuid>,
142 pub invoice_held_at: i64,
143 pub taken_at: i64,
144 pub created_at: i64,
145 pub buyer_sent_rate: bool,
146 pub seller_sent_rate: bool,
147 pub failed_payment: bool,
148 pub payment_attempts: i64,
149 pub expires_at: i64,
150 pub trade_index_seller: Option<i64>,
151 pub trade_index_buyer: Option<i64>,
152 pub next_trade_pubkey: Option<String>,
153 pub next_trade_index: Option<i64>,
154}
155
156impl From<SmallOrder> for Order {
157 fn from(small_order: SmallOrder) -> Self {
158 Self {
159 id: Uuid::new_v4(),
160 kind: small_order
162 .kind
163 .map_or_else(|| Kind::Buy.to_string(), |k| k.to_string()),
164 status: small_order
165 .status
166 .map_or_else(|| Status::Active.to_string(), |s| s.to_string()),
167 amount: small_order.amount,
168 fiat_code: small_order.fiat_code,
169 min_amount: small_order.min_amount,
170 max_amount: small_order.max_amount,
171 fiat_amount: small_order.fiat_amount,
172 payment_method: small_order.payment_method,
173 premium: small_order.premium,
174 event_id: String::new(),
175 creator_pubkey: String::new(),
176 price_from_api: false,
177 fee: 0,
178 routing_fee: 0,
179 invoice_held_at: 0,
180 taken_at: 0,
181 created_at: small_order.created_at.unwrap_or(0),
182 expires_at: small_order.expires_at.unwrap_or(0),
183 payment_attempts: 0,
184 ..Default::default()
185 }
186 }
187}
188
189impl Order {
190 pub fn as_new_order(&self) -> SmallOrder {
192 SmallOrder::new(
193 Some(self.id),
194 Some(Kind::from_str(&self.kind).unwrap()),
195 Some(Status::from_str(&self.status).unwrap()),
196 self.amount,
197 self.fiat_code.clone(),
198 self.min_amount,
199 self.max_amount,
200 self.fiat_amount,
201 self.payment_method.clone(),
202 self.premium,
203 None,
204 None,
205 self.buyer_invoice.clone(),
206 Some(self.created_at),
207 Some(self.expires_at),
208 None,
209 None,
210 )
211 }
212
213 pub fn get_order_kind(&self) -> Result<Kind, ServiceError> {
215 if let Ok(kind) = Kind::from_str(&self.kind) {
216 Ok(kind)
217 } else {
218 Err(ServiceError::InvalidOrderKind)
219 }
220 }
221
222 pub fn get_order_status(&self) -> Result<Status, ServiceError> {
224 if let Ok(status) = Status::from_str(&self.status) {
225 Ok(status)
226 } else {
227 Err(ServiceError::InvalidOrderStatus)
228 }
229 }
230
231 pub fn check_status(&self, status: Status) -> Result<(), CantDoReason> {
233 match Status::from_str(&self.status) {
234 Ok(s) => match s == status {
235 true => Ok(()),
236 false => Err(CantDoReason::InvalidOrderStatus),
237 },
238 Err(_) => Err(CantDoReason::InvalidOrderStatus),
239 }
240 }
241
242 pub fn is_buy_order(&self) -> Result<(), CantDoReason> {
244 if self.kind != Kind::Buy.to_string() {
245 return Err(CantDoReason::InvalidOrderKind);
246 }
247 Ok(())
248 }
249 pub fn is_sell_order(&self) -> Result<(), CantDoReason> {
251 if self.kind != Kind::Sell.to_string() {
252 return Err(CantDoReason::InvalidOrderKind);
253 }
254 Ok(())
255 }
256
257 pub fn sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
259 let sender = sender.to_string();
260 if self.creator_pubkey != sender {
261 return Err(CantDoReason::InvalidPubkey);
262 }
263 Ok(())
264 }
265
266 pub fn not_sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
268 let sender = sender.to_string();
269 if self.creator_pubkey == sender {
270 return Err(CantDoReason::InvalidPubkey);
271 }
272 Ok(())
273 }
274
275 pub fn get_creator_pubkey(&self) -> Result<PublicKey, ServiceError> {
277 match PublicKey::from_str(self.creator_pubkey.as_ref()) {
278 Ok(pk) => Ok(pk),
279 Err(_) => Err(ServiceError::InvalidPubkey),
280 }
281 }
282
283 pub fn get_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
285 if let Some(pk) = self.buyer_pubkey.as_ref() {
286 PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
287 } else {
288 Err(ServiceError::InvalidPubkey)
289 }
290 }
291 pub fn get_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
293 if let Some(pk) = self.seller_pubkey.as_ref() {
294 PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
295 } else {
296 Err(ServiceError::InvalidPubkey)
297 }
298 }
299 pub fn get_master_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
301 if let Some(pk) = self.master_buyer_pubkey.as_ref() {
302 PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
303 } else {
304 Err(ServiceError::InvalidPubkey)
305 }
306 }
307 pub fn get_master_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
309 if let Some(pk) = self.master_seller_pubkey.as_ref() {
310 PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
311 } else {
312 Err(ServiceError::InvalidPubkey)
313 }
314 }
315
316 pub fn is_range_order(&self) -> bool {
318 self.min_amount.is_some() && self.max_amount.is_some()
319 }
320
321 pub fn count_failed_payment(&mut self, retries_number: i64) {
322 if !self.failed_payment {
323 self.failed_payment = true;
324 self.payment_attempts = 0;
325 } else if self.payment_attempts < retries_number {
326 self.payment_attempts += 1;
327 }
328 }
329
330 pub fn has_no_amount(&self) -> bool {
332 self.amount == 0
333 }
334
335 pub fn set_timestamp_now(&mut self) {
337 self.taken_at = Timestamp::now().as_u64() as i64
338 }
339 pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
344 let is_seller_dispute = !is_buyer_dispute;
346
347 let mut update_seller_dispute = false;
349 let mut update_buyer_dispute = false;
350
351 if is_seller_dispute && !self.seller_dispute {
352 update_seller_dispute = true;
353 self.seller_dispute = update_seller_dispute;
354 } else if is_buyer_dispute && !self.buyer_dispute {
355 update_buyer_dispute = true;
356 self.buyer_dispute = update_buyer_dispute;
357 };
358 self.status = Status::Dispute.to_string();
360
361 if !update_buyer_dispute && !update_seller_dispute {
364 return Err(CantDoReason::DisputeCreationError);
365 }
366
367 Ok(())
368 }
369}
370
371#[derive(Debug, Default, Deserialize, Serialize, Clone)]
373pub struct SmallOrder {
374 #[serde(skip_serializing_if = "Option::is_none")]
375 pub id: Option<Uuid>,
376 pub kind: Option<Kind>,
377 pub status: Option<Status>,
378 pub amount: i64,
379 pub fiat_code: String,
380 pub min_amount: Option<i64>,
381 pub max_amount: Option<i64>,
382 pub fiat_amount: i64,
383 pub payment_method: String,
384 pub premium: i64,
385 #[serde(skip_serializing_if = "Option::is_none")]
386 pub buyer_trade_pubkey: Option<String>,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub seller_trade_pubkey: Option<String>,
389 #[serde(skip_serializing_if = "Option::is_none")]
390 pub buyer_invoice: Option<String>,
391 pub created_at: Option<i64>,
392 pub expires_at: Option<i64>,
393 pub buyer_token: Option<u16>,
394 pub seller_token: Option<u16>,
395}
396
397#[allow(dead_code)]
398impl SmallOrder {
399 #[allow(clippy::too_many_arguments)]
400 pub fn new(
401 id: Option<Uuid>,
402 kind: Option<Kind>,
403 status: Option<Status>,
404 amount: i64,
405 fiat_code: String,
406 min_amount: Option<i64>,
407 max_amount: Option<i64>,
408 fiat_amount: i64,
409 payment_method: String,
410 premium: i64,
411 buyer_trade_pubkey: Option<String>,
412 seller_trade_pubkey: Option<String>,
413 buyer_invoice: Option<String>,
414 created_at: Option<i64>,
415 expires_at: Option<i64>,
416 buyer_token: Option<u16>,
417 seller_token: Option<u16>,
418 ) -> Self {
419 Self {
420 id,
421 kind,
422 status,
423 amount,
424 fiat_code,
425 min_amount,
426 max_amount,
427 fiat_amount,
428 payment_method,
429 premium,
430 buyer_trade_pubkey,
431 seller_trade_pubkey,
432 buyer_invoice,
433 created_at,
434 expires_at,
435 buyer_token,
436 seller_token,
437 }
438 }
439 pub fn from_json(json: &str) -> Result<Self> {
441 Ok(serde_json::from_str(json)?)
442 }
443
444 pub fn as_json(&self) -> Result<String> {
446 Ok(serde_json::to_string(&self)?)
447 }
448
449 pub fn sats_amount(&self) -> String {
451 if self.amount == 0 {
452 "Market price".to_string()
453 } else {
454 self.amount.to_string()
455 }
456 }
457 pub fn check_zero_amount_with_premium(&self) -> Result<(), CantDoReason> {
459 let premium = (self.premium != 0).then_some(self.premium);
460 let sats_amount = (self.amount != 0).then_some(self.amount);
461
462 if premium.is_some() && sats_amount.is_some() {
463 return Err(CantDoReason::InvalidParameters);
464 }
465 Ok(())
466 }
467 pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
469 if let (Some(min), Some(max)) = (self.min_amount, self.max_amount) {
471 if min < 0 || max < 0 {
472 return Err(CantDoReason::InvalidAmount);
473 }
474 if min >= max {
475 return Err(CantDoReason::InvalidAmount);
476 }
477 if self.amount != 0 {
478 return Err(CantDoReason::InvalidAmount);
479 }
480 amounts.clear();
481 amounts.push(min);
482 amounts.push(max);
483 }
484 Ok(())
485 }
486
487 pub fn fiat_amount(&self) -> String {
489 if self.max_amount.is_some() {
490 format!("{}-{}", self.min_amount.unwrap(), self.max_amount.unwrap())
491 } else {
492 self.fiat_amount.to_string()
493 }
494 }
495}
496
497impl From<Order> for SmallOrder {
498 fn from(order: Order) -> Self {
499 let id = Some(order.id);
500 let kind = Kind::from_str(&order.kind).unwrap();
501 let status = Status::from_str(&order.status).unwrap();
502 let amount = order.amount;
503 let fiat_code = order.fiat_code.clone();
504 let min_amount = order.min_amount;
505 let max_amount = order.max_amount;
506 let fiat_amount = order.fiat_amount;
507 let payment_method = order.payment_method.clone();
508 let premium = order.premium;
509 let buyer_trade_pubkey = order.buyer_pubkey.clone();
510 let seller_trade_pubkey = order.seller_pubkey.clone();
511 let buyer_invoice = order.buyer_invoice.clone();
512
513 Self {
514 id,
515 kind: Some(kind),
516 status: Some(status),
517 amount,
518 fiat_code,
519 min_amount,
520 max_amount,
521 fiat_amount,
522 payment_method,
523 premium,
524 buyer_trade_pubkey,
525 seller_trade_pubkey,
526 buyer_invoice,
527 created_at: None,
528 expires_at: None,
529 buyer_token: None,
530 seller_token: None,
531 }
532 }
533}