mostro_core/
order.rs

1use crate::prelude::*;
2use nostr_sdk::{PublicKey, Timestamp};
3use secrecy::SecretString;
4use serde::{Deserialize, Serialize};
5#[cfg(feature = "sqlx")]
6use sqlx::FromRow;
7#[cfg(feature = "sqlx")]
8use sqlx_crud::SqlxCrud;
9use std::{fmt::Display, str::FromStr};
10use uuid::Uuid;
11use wasm_bindgen::prelude::*;
12
13/// Orders can be only Buy or Sell
14#[wasm_bindgen]
15#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
16#[serde(rename_all = "kebab-case")]
17pub enum Kind {
18    Buy,
19    Sell,
20}
21
22impl FromStr for Kind {
23    type Err = ();
24
25    fn from_str(kind: &str) -> std::result::Result<Self, Self::Err> {
26        match kind.to_lowercase().as_str() {
27            "buy" => std::result::Result::Ok(Self::Buy),
28            "sell" => std::result::Result::Ok(Self::Sell),
29            _ => Err(()),
30        }
31    }
32}
33
34impl Display for Kind {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            Kind::Sell => write!(f, "sell"),
38            Kind::Buy => write!(f, "buy"),
39        }
40    }
41}
42
43/// Each status that an order can have
44#[wasm_bindgen]
45#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
46#[serde(rename_all = "kebab-case")]
47pub enum Status {
48    Active,
49    Canceled,
50    CanceledByAdmin,
51    SettledByAdmin,
52    CompletedByAdmin,
53    Dispute,
54    Expired,
55    FiatSent,
56    SettledHoldInvoice,
57    Pending,
58    Success,
59    WaitingBuyerInvoice,
60    WaitingPayment,
61    CooperativelyCanceled,
62    InProgress,
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            Status::InProgress => write!(f, "in-progress"),
83        }
84    }
85}
86
87impl FromStr for Status {
88    type Err = ();
89    /// Convert a string to a status
90    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
91        match s.to_lowercase().as_str() {
92            "active" => std::result::Result::Ok(Self::Active),
93            "canceled" => std::result::Result::Ok(Self::Canceled),
94            "canceled-by-admin" => std::result::Result::Ok(Self::CanceledByAdmin),
95            "settled-by-admin" => std::result::Result::Ok(Self::SettledByAdmin),
96            "completed-by-admin" => std::result::Result::Ok(Self::CompletedByAdmin),
97            "dispute" => std::result::Result::Ok(Self::Dispute),
98            "expired" => std::result::Result::Ok(Self::Expired),
99            "fiat-sent" => std::result::Result::Ok(Self::FiatSent),
100            "settled-hold-invoice" => std::result::Result::Ok(Self::SettledHoldInvoice),
101            "pending" => std::result::Result::Ok(Self::Pending),
102            "success" => std::result::Result::Ok(Self::Success),
103            "waiting-buyer-invoice" => std::result::Result::Ok(Self::WaitingBuyerInvoice),
104            "waiting-payment" => std::result::Result::Ok(Self::WaitingPayment),
105            "cooperatively-canceled" => std::result::Result::Ok(Self::CooperativelyCanceled),
106            "in-progress" => std::result::Result::Ok(Self::InProgress),
107            _ => Err(()),
108        }
109    }
110}
111/// Database representation of an order
112#[cfg_attr(feature = "sqlx", derive(FromRow, SqlxCrud), external_id)]
113#[derive(Debug, Default, Deserialize, Serialize, Clone)]
114pub struct Order {
115    pub id: Uuid,
116    pub kind: String,
117    pub event_id: String,
118    pub hash: Option<String>,
119    pub preimage: Option<String>,
120    pub creator_pubkey: String,
121    pub cancel_initiator_pubkey: Option<String>,
122    pub buyer_pubkey: Option<String>,
123    pub master_buyer_pubkey: Option<String>,
124    pub seller_pubkey: Option<String>,
125    pub master_seller_pubkey: Option<String>,
126    pub status: String,
127    pub price_from_api: bool,
128    pub premium: i64,
129    pub payment_method: String,
130    pub amount: i64,
131    pub min_amount: Option<i64>,
132    pub max_amount: Option<i64>,
133    pub buyer_dispute: bool,
134    pub seller_dispute: bool,
135    pub buyer_cooperativecancel: bool,
136    pub seller_cooperativecancel: bool,
137    pub fee: i64,
138    pub routing_fee: i64,
139    pub fiat_code: String,
140    pub fiat_amount: i64,
141    pub buyer_invoice: Option<String>,
142    pub range_parent_id: Option<Uuid>,
143    pub invoice_held_at: i64,
144    pub taken_at: i64,
145    pub created_at: i64,
146    pub buyer_sent_rate: bool,
147    pub seller_sent_rate: bool,
148    pub failed_payment: bool,
149    pub payment_attempts: i64,
150    pub expires_at: i64,
151    pub trade_index_seller: Option<i64>,
152    pub trade_index_buyer: Option<i64>,
153    pub next_trade_pubkey: Option<String>,
154    pub next_trade_index: Option<i64>,
155}
156
157impl From<SmallOrder> for Order {
158    fn from(small_order: SmallOrder) -> Self {
159        Self {
160            id: Uuid::new_v4(),
161            // order will be overwritten with the real one before publishing
162            kind: small_order
163                .kind
164                .map_or_else(|| Kind::Buy.to_string(), |k| k.to_string()),
165            status: small_order
166                .status
167                .map_or_else(|| Status::Active.to_string(), |s| s.to_string()),
168            amount: small_order.amount,
169            fiat_code: small_order.fiat_code,
170            min_amount: small_order.min_amount,
171            max_amount: small_order.max_amount,
172            fiat_amount: small_order.fiat_amount,
173            payment_method: small_order.payment_method,
174            premium: small_order.premium,
175            event_id: String::new(),
176            creator_pubkey: String::new(),
177            price_from_api: false,
178            fee: 0,
179            routing_fee: 0,
180            invoice_held_at: 0,
181            taken_at: 0,
182            created_at: small_order.created_at.unwrap_or(0),
183            expires_at: small_order.expires_at.unwrap_or(0),
184            payment_attempts: 0,
185            ..Default::default()
186        }
187    }
188}
189
190impl Order {
191    /// Convert an order to a small order
192    pub fn as_new_order(&self) -> SmallOrder {
193        SmallOrder::new(
194            Some(self.id),
195            Some(Kind::from_str(&self.kind).unwrap()),
196            Some(Status::from_str(&self.status).unwrap()),
197            self.amount,
198            self.fiat_code.clone(),
199            self.min_amount,
200            self.max_amount,
201            self.fiat_amount,
202            self.payment_method.clone(),
203            self.premium,
204            None,
205            None,
206            self.buyer_invoice.clone(),
207            Some(self.created_at),
208            Some(self.expires_at),
209            None,
210            None,
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(
301        &self,
302        password: Option<&SecretString>,
303    ) -> Result<String, ServiceError> {
304        if let Some(pk) = self.master_buyer_pubkey.as_ref() {
305            CryptoUtils::decrypt_data(pk.clone(), password).map_err(|_| ServiceError::InvalidPubkey)
306        } else {
307            Err(ServiceError::InvalidPubkey)
308        }
309    }
310    /// Get the master seller pubkey
311    pub fn get_master_seller_pubkey(
312        &self,
313        password: Option<&SecretString>,
314    ) -> Result<String, ServiceError> {
315        if let Some(pk) = self.master_seller_pubkey.as_ref() {
316            CryptoUtils::decrypt_data(pk.clone(), password).map_err(|_| ServiceError::InvalidPubkey)
317        } else {
318            Err(ServiceError::InvalidPubkey)
319        }
320    }
321
322    /// Check if the order is a range order
323    pub fn is_range_order(&self) -> bool {
324        self.min_amount.is_some() && self.max_amount.is_some()
325    }
326
327    pub fn count_failed_payment(&mut self, retries_number: i64) {
328        if !self.failed_payment {
329            self.failed_payment = true;
330            self.payment_attempts = 0;
331        } else if self.payment_attempts < retries_number {
332            self.payment_attempts += 1;
333        }
334    }
335
336    /// Check if the order has no amount
337    pub fn has_no_amount(&self) -> bool {
338        self.amount == 0
339    }
340
341    /// Set the timestamp to now
342    pub fn set_timestamp_now(&mut self) {
343        self.taken_at = Timestamp::now().as_u64() as i64
344    }
345
346    /// Check if a user is creating a full privacy order so he doesn't to have reputation
347    /// compare master keys with the order keys if they are the same the user is in full privacy mode
348    /// otherwise the user is not in normal mode and has a reputation
349    pub fn is_full_privacy_order(
350        &self,
351        password: Option<&SecretString>,
352    ) -> Result<(Option<String>, Option<String>), ServiceError> {
353        let (mut normal_buyer_idkey, mut normal_seller_idkey) = (None, None);
354
355        // Get master pubkeys to get users data from db
356        let master_buyer_pubkey = self.get_master_buyer_pubkey(password).ok();
357        let master_seller_pubkey = self.get_master_seller_pubkey(password).ok();
358
359        // Check if the buyer is in full privacy mode
360        if self.buyer_pubkey.as_ref() != master_buyer_pubkey.as_ref() {
361            normal_buyer_idkey = master_buyer_pubkey;
362        }
363
364        // Check if the seller is in full privacy mode
365        if self.seller_pubkey.as_ref() != master_seller_pubkey.as_ref() {
366            normal_seller_idkey = master_seller_pubkey;
367        }
368
369        Ok((normal_buyer_idkey, normal_seller_idkey))
370    }
371    /// Setup the dispute status
372    ///
373    /// If the pubkey is the buyer, set the buyer dispute to true
374    /// If the pubkey is the seller, set the seller dispute to true
375    pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
376        // Get the opposite dispute status
377        let is_seller_dispute = !is_buyer_dispute;
378
379        // Update dispute flags based on who initiated
380        let mut update_seller_dispute = false;
381        let mut update_buyer_dispute = false;
382
383        if is_seller_dispute && !self.seller_dispute {
384            update_seller_dispute = true;
385            self.seller_dispute = update_seller_dispute;
386        } else if is_buyer_dispute && !self.buyer_dispute {
387            update_buyer_dispute = true;
388            self.buyer_dispute = update_buyer_dispute;
389        };
390        // Set the status to dispute
391        self.status = Status::Dispute.to_string();
392
393        // Update the database with dispute information
394        // Save the dispute to DB
395        if !update_buyer_dispute && !update_seller_dispute {
396            return Err(CantDoReason::DisputeCreationError);
397        }
398
399        Ok(())
400    }
401}
402
403/// We use this struct to create a new order
404#[derive(Debug, Default, Deserialize, Serialize, Clone)]
405pub struct SmallOrder {
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub id: Option<Uuid>,
408    pub kind: Option<Kind>,
409    pub status: Option<Status>,
410    pub amount: i64,
411    pub fiat_code: String,
412    pub min_amount: Option<i64>,
413    pub max_amount: Option<i64>,
414    pub fiat_amount: i64,
415    pub payment_method: String,
416    pub premium: i64,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub buyer_trade_pubkey: Option<String>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub seller_trade_pubkey: Option<String>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub buyer_invoice: Option<String>,
423    pub created_at: Option<i64>,
424    pub expires_at: Option<i64>,
425    pub buyer_token: Option<u16>,
426    pub seller_token: Option<u16>,
427}
428
429#[allow(dead_code)]
430impl SmallOrder {
431    #[allow(clippy::too_many_arguments)]
432    pub fn new(
433        id: Option<Uuid>,
434        kind: Option<Kind>,
435        status: Option<Status>,
436        amount: i64,
437        fiat_code: String,
438        min_amount: Option<i64>,
439        max_amount: Option<i64>,
440        fiat_amount: i64,
441        payment_method: String,
442        premium: i64,
443        buyer_trade_pubkey: Option<String>,
444        seller_trade_pubkey: Option<String>,
445        buyer_invoice: Option<String>,
446        created_at: Option<i64>,
447        expires_at: Option<i64>,
448        buyer_token: Option<u16>,
449        seller_token: Option<u16>,
450    ) -> Self {
451        Self {
452            id,
453            kind,
454            status,
455            amount,
456            fiat_code,
457            min_amount,
458            max_amount,
459            fiat_amount,
460            payment_method,
461            premium,
462            buyer_trade_pubkey,
463            seller_trade_pubkey,
464            buyer_invoice,
465            created_at,
466            expires_at,
467            buyer_token,
468            seller_token,
469        }
470    }
471    /// New order from json string
472    pub fn from_json(json: &str) -> Result<Self, ServiceError> {
473        serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
474    }
475
476    /// Get order as json string
477    pub fn as_json(&self) -> Result<String, ServiceError> {
478        serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
479    }
480
481    /// Get the amount of sats or the string "Market price"
482    pub fn sats_amount(&self) -> String {
483        if self.amount == 0 {
484            "Market price".to_string()
485        } else {
486            self.amount.to_string()
487        }
488    }
489    /// Check if the order has a zero amount and a premium or fiat amount
490    pub fn check_zero_amount_with_premium(&self) -> Result<(), CantDoReason> {
491        let premium = (self.premium != 0).then_some(self.premium);
492        let sats_amount = (self.amount != 0).then_some(self.amount);
493
494        if premium.is_some() && sats_amount.is_some() {
495            return Err(CantDoReason::InvalidParameters);
496        }
497        Ok(())
498    }
499    /// Check if the order is a range order and if the amount is zero
500    pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
501        // Check if the min and max amount are valid and update the vector
502        if let (Some(min), Some(max)) = (self.min_amount, self.max_amount) {
503            if min < 0 || max < 0 {
504                return Err(CantDoReason::InvalidAmount);
505            }
506            if min >= max {
507                return Err(CantDoReason::InvalidAmount);
508            }
509            if self.amount != 0 {
510                return Err(CantDoReason::InvalidAmount);
511            }
512            amounts.clear();
513            amounts.push(min);
514            amounts.push(max);
515        }
516        Ok(())
517    }
518}
519
520impl From<Order> for SmallOrder {
521    fn from(order: Order) -> Self {
522        let id = Some(order.id);
523        let kind = Kind::from_str(&order.kind).unwrap();
524        let status = Status::from_str(&order.status).unwrap();
525        let amount = order.amount;
526        let fiat_code = order.fiat_code.clone();
527        let min_amount = order.min_amount;
528        let max_amount = order.max_amount;
529        let fiat_amount = order.fiat_amount;
530        let payment_method = order.payment_method.clone();
531        let premium = order.premium;
532        let buyer_trade_pubkey = order.buyer_pubkey.clone();
533        let seller_trade_pubkey = order.seller_pubkey.clone();
534        let buyer_invoice = order.buyer_invoice.clone();
535
536        Self {
537            id,
538            kind: Some(kind),
539            status: Some(status),
540            amount,
541            fiat_code,
542            min_amount,
543            max_amount,
544            fiat_amount,
545            payment_method,
546            premium,
547            buyer_trade_pubkey,
548            seller_trade_pubkey,
549            buyer_invoice,
550            created_at: None,
551            expires_at: None,
552            buyer_token: None,
553            seller_token: None,
554        }
555    }
556}