mostro_core/
order.rs

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/// Orders can be only Buy or Sell
15#[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/// Each status that an order can have
45#[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    /// Convert a string to a status
89    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/// Database representation of an order
111#[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            // order will be overwritten with the real one before publishing
161            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    /// Convert an order to a small order
191    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    /// Get the kind of the order
214    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    /// Get the status of the order in case
223    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    /// Compare the status of the order
232    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    /// Check if the order is a buy order
243    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    /// Check if the order is a sell order
250    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    /// Check if the sender is the creator of the order
258    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    /// Check if the sender is the creator of the order
267    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    /// Get the creator pubkey
276    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    /// Get the buyer pubkey
284    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    /// Get the seller pubkey
292    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    /// Get the master buyer pubkey
300    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    /// Get the master seller pubkey
308    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    /// Check if the order is a range order
317    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    /// Check if the order has no amount
331    pub fn has_no_amount(&self) -> bool {
332        self.amount == 0
333    }
334
335    /// Set the timestamp to now
336    pub fn set_timestamp_now(&mut self) {
337        self.taken_at = Timestamp::now().as_u64() as i64
338    }
339    /// Setup the dispute status
340    ///
341    /// If the pubkey is the buyer, set the buyer dispute to true
342    /// If the pubkey is the seller, set the seller dispute to true
343    pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
344        // Get the opposite dispute status
345        let is_seller_dispute = !is_buyer_dispute;
346
347        // Update dispute flags based on who initiated
348        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        // Set the status to dispute
359        self.status = Status::Dispute.to_string();
360
361        // Update the database with dispute information
362        // Save the dispute to DB
363        if !update_buyer_dispute && !update_seller_dispute {
364            return Err(CantDoReason::DisputeCreationError);
365        }
366
367        Ok(())
368    }
369}
370
371/// We use this struct to create a new order
372#[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    /// New order from json string
440    pub fn from_json(json: &str) -> Result<Self> {
441        Ok(serde_json::from_str(json)?)
442    }
443
444    /// Get order as json string
445    pub fn as_json(&self) -> Result<String> {
446        Ok(serde_json::to_string(&self)?)
447    }
448
449    /// Get the amount of sats or the string "Market price"
450    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    /// Check if the order has a zero amount and a premium or fiat amount
458    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    /// Check if the order is a range order and if the amount is zero
468    pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
469        // Check if the min and max amount are valid and update the vector
470        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    // Get the fiat amount, if the order is a range order, return the range as min-max string
488    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}