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 dev_fee: i64,
140    pub dev_fee_paid: bool,
141    pub dev_fee_payment_hash: Option<String>,
142    pub fiat_code: String,
143    pub fiat_amount: i64,
144    pub buyer_invoice: Option<String>,
145    pub range_parent_id: Option<Uuid>,
146    pub invoice_held_at: i64,
147    pub taken_at: i64,
148    pub created_at: i64,
149    pub buyer_sent_rate: bool,
150    pub seller_sent_rate: bool,
151    pub failed_payment: bool,
152    pub payment_attempts: i64,
153    pub expires_at: i64,
154    pub trade_index_seller: Option<i64>,
155    pub trade_index_buyer: Option<i64>,
156    pub next_trade_pubkey: Option<String>,
157    pub next_trade_index: Option<i64>,
158}
159
160impl From<SmallOrder> for Order {
161    fn from(small_order: SmallOrder) -> Self {
162        Self {
163            id: Uuid::new_v4(),
164            // order will be overwritten with the real one before publishing
165            kind: small_order
166                .kind
167                .map_or_else(|| Kind::Buy.to_string(), |k| k.to_string()),
168            status: small_order
169                .status
170                .map_or_else(|| Status::Active.to_string(), |s| s.to_string()),
171            amount: small_order.amount,
172            fiat_code: small_order.fiat_code,
173            min_amount: small_order.min_amount,
174            max_amount: small_order.max_amount,
175            fiat_amount: small_order.fiat_amount,
176            payment_method: small_order.payment_method,
177            premium: small_order.premium,
178            event_id: String::new(),
179            creator_pubkey: String::new(),
180            price_from_api: false,
181            fee: 0,
182            routing_fee: 0,
183            dev_fee: 0,
184            dev_fee_paid: false,
185            dev_fee_payment_hash: None,
186            invoice_held_at: 0,
187            taken_at: 0,
188            created_at: small_order.created_at.unwrap_or(0),
189            expires_at: small_order.expires_at.unwrap_or(0),
190            payment_attempts: 0,
191            ..Default::default()
192        }
193    }
194}
195
196impl Order {
197    /// Convert an order to a small order
198    pub fn as_new_order(&self) -> SmallOrder {
199        SmallOrder::new(
200            Some(self.id),
201            Some(Kind::from_str(&self.kind).unwrap()),
202            Some(Status::from_str(&self.status).unwrap()),
203            self.amount,
204            self.fiat_code.clone(),
205            self.min_amount,
206            self.max_amount,
207            self.fiat_amount,
208            self.payment_method.clone(),
209            self.premium,
210            None,
211            None,
212            self.buyer_invoice.clone(),
213            Some(self.created_at),
214            Some(self.expires_at),
215        )
216    }
217    /// Get the kind of the order
218    pub fn get_order_kind(&self) -> Result<Kind, ServiceError> {
219        if let Ok(kind) = Kind::from_str(&self.kind) {
220            Ok(kind)
221        } else {
222            Err(ServiceError::InvalidOrderKind)
223        }
224    }
225
226    /// Get the status of the order in case
227    pub fn get_order_status(&self) -> Result<Status, ServiceError> {
228        if let Ok(status) = Status::from_str(&self.status) {
229            Ok(status)
230        } else {
231            Err(ServiceError::InvalidOrderStatus)
232        }
233    }
234
235    /// Compare the status of the order
236    pub fn check_status(&self, status: Status) -> Result<(), CantDoReason> {
237        match Status::from_str(&self.status) {
238            Ok(s) => match s == status {
239                true => Ok(()),
240                false => Err(CantDoReason::InvalidOrderStatus),
241            },
242            Err(_) => Err(CantDoReason::InvalidOrderStatus),
243        }
244    }
245
246    /// Check if the order is a buy order
247    pub fn is_buy_order(&self) -> Result<(), CantDoReason> {
248        if self.kind != Kind::Buy.to_string() {
249            return Err(CantDoReason::InvalidOrderKind);
250        }
251        Ok(())
252    }
253    /// Check if the order is a sell order
254    pub fn is_sell_order(&self) -> Result<(), CantDoReason> {
255        if self.kind != Kind::Sell.to_string() {
256            return Err(CantDoReason::InvalidOrderKind);
257        }
258        Ok(())
259    }
260
261    /// Check if the sender is the creator of the order
262    pub fn sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
263        let sender = sender.to_string();
264        if self.creator_pubkey != sender {
265            return Err(CantDoReason::InvalidPubkey);
266        }
267        Ok(())
268    }
269
270    /// Check if the sender is the creator of the order
271    pub fn not_sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
272        let sender = sender.to_string();
273        if self.creator_pubkey == sender {
274            return Err(CantDoReason::InvalidPubkey);
275        }
276        Ok(())
277    }
278
279    /// Get the creator pubkey
280    pub fn get_creator_pubkey(&self) -> Result<PublicKey, ServiceError> {
281        match PublicKey::from_str(self.creator_pubkey.as_ref()) {
282            Ok(pk) => Ok(pk),
283            Err(_) => Err(ServiceError::InvalidPubkey),
284        }
285    }
286
287    /// Get the buyer pubkey
288    pub fn get_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
289        if let Some(pk) = self.buyer_pubkey.as_ref() {
290            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
291        } else {
292            Err(ServiceError::InvalidPubkey)
293        }
294    }
295    /// Get the seller pubkey
296    pub fn get_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
297        if let Some(pk) = self.seller_pubkey.as_ref() {
298            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
299        } else {
300            Err(ServiceError::InvalidPubkey)
301        }
302    }
303    /// Get the master buyer pubkey
304    pub fn get_master_buyer_pubkey(
305        &self,
306        password: Option<&SecretString>,
307    ) -> Result<String, ServiceError> {
308        if let Some(pk) = self.master_buyer_pubkey.as_ref() {
309            CryptoUtils::decrypt_data(pk.clone(), password).map_err(|_| ServiceError::InvalidPubkey)
310        } else {
311            Err(ServiceError::InvalidPubkey)
312        }
313    }
314    /// Get the master seller pubkey
315    pub fn get_master_seller_pubkey(
316        &self,
317        password: Option<&SecretString>,
318    ) -> Result<String, ServiceError> {
319        if let Some(pk) = self.master_seller_pubkey.as_ref() {
320            CryptoUtils::decrypt_data(pk.clone(), password).map_err(|_| ServiceError::InvalidPubkey)
321        } else {
322            Err(ServiceError::InvalidPubkey)
323        }
324    }
325
326    /// Check if the order is a range order
327    pub fn is_range_order(&self) -> bool {
328        self.min_amount.is_some() && self.max_amount.is_some()
329    }
330
331    pub fn count_failed_payment(&mut self, retries_number: i64) {
332        if !self.failed_payment {
333            self.failed_payment = true;
334            self.payment_attempts = 1;
335        } else if self.payment_attempts < retries_number {
336            self.payment_attempts += 1;
337        }
338    }
339
340    /// Check if the order has no amount
341    pub fn has_no_amount(&self) -> bool {
342        self.amount == 0
343    }
344
345    /// Set the timestamp to now
346    pub fn set_timestamp_now(&mut self) {
347        self.taken_at = Timestamp::now().as_u64() as i64
348    }
349
350    /// Check if a user is creating a full privacy order so he doesn't to have reputation
351    /// compare master keys with the order keys if they are the same the user is in full privacy mode
352    /// otherwise the user is not in normal mode and has a reputation
353    pub fn is_full_privacy_order(
354        &self,
355        password: Option<&SecretString>,
356    ) -> Result<(Option<String>, Option<String>), ServiceError> {
357        let (mut normal_buyer_idkey, mut normal_seller_idkey) = (None, None);
358
359        // Get master pubkeys to get users data from db
360        let master_buyer_pubkey = self.get_master_buyer_pubkey(password).ok();
361        let master_seller_pubkey = self.get_master_seller_pubkey(password).ok();
362
363        // Check if the buyer is in full privacy mode
364        if self.buyer_pubkey.as_ref() != master_buyer_pubkey.as_ref() {
365            normal_buyer_idkey = master_buyer_pubkey;
366        }
367
368        // Check if the seller is in full privacy mode
369        if self.seller_pubkey.as_ref() != master_seller_pubkey.as_ref() {
370            normal_seller_idkey = master_seller_pubkey;
371        }
372
373        Ok((normal_buyer_idkey, normal_seller_idkey))
374    }
375    /// Setup the dispute status
376    ///
377    /// If the pubkey is the buyer, set the buyer dispute to true
378    /// If the pubkey is the seller, set the seller dispute to true
379    pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
380        // Get the opposite dispute status
381        let is_seller_dispute = !is_buyer_dispute;
382
383        // Update dispute flags based on who initiated
384        let mut update_seller_dispute = false;
385        let mut update_buyer_dispute = false;
386
387        if is_seller_dispute && !self.seller_dispute {
388            update_seller_dispute = true;
389            self.seller_dispute = update_seller_dispute;
390        } else if is_buyer_dispute && !self.buyer_dispute {
391            update_buyer_dispute = true;
392            self.buyer_dispute = update_buyer_dispute;
393        };
394        // Set the status to dispute
395        self.status = Status::Dispute.to_string();
396
397        // Update the database with dispute information
398        // Save the dispute to DB
399        if !update_buyer_dispute && !update_seller_dispute {
400            return Err(CantDoReason::DisputeCreationError);
401        }
402
403        Ok(())
404    }
405}
406
407/// We use this struct to create a new order
408#[derive(Debug, Default, Deserialize, Serialize, Clone)]
409#[serde(deny_unknown_fields)]
410pub struct SmallOrder {
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub id: Option<Uuid>,
413    pub kind: Option<Kind>,
414    pub status: Option<Status>,
415    pub amount: i64,
416    pub fiat_code: String,
417    pub min_amount: Option<i64>,
418    pub max_amount: Option<i64>,
419    pub fiat_amount: i64,
420    pub payment_method: String,
421    pub premium: i64,
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub buyer_trade_pubkey: Option<String>,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub seller_trade_pubkey: Option<String>,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub buyer_invoice: Option<String>,
428    pub created_at: Option<i64>,
429    pub expires_at: Option<i64>,
430}
431
432#[allow(dead_code)]
433impl SmallOrder {
434    #[allow(clippy::too_many_arguments)]
435    pub fn new(
436        id: Option<Uuid>,
437        kind: Option<Kind>,
438        status: Option<Status>,
439        amount: i64,
440        fiat_code: String,
441        min_amount: Option<i64>,
442        max_amount: Option<i64>,
443        fiat_amount: i64,
444        payment_method: String,
445        premium: i64,
446        buyer_trade_pubkey: Option<String>,
447        seller_trade_pubkey: Option<String>,
448        buyer_invoice: Option<String>,
449        created_at: Option<i64>,
450        expires_at: Option<i64>,
451    ) -> Self {
452        Self {
453            id,
454            kind,
455            status,
456            amount,
457            fiat_code,
458            min_amount,
459            max_amount,
460            fiat_amount,
461            payment_method,
462            premium,
463            buyer_trade_pubkey,
464            seller_trade_pubkey,
465            buyer_invoice,
466            created_at,
467            expires_at,
468        }
469    }
470    /// New order from json string
471    pub fn from_json(json: &str) -> Result<Self, ServiceError> {
472        serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
473    }
474
475    /// Get order as json string
476    pub fn as_json(&self) -> Result<String, ServiceError> {
477        serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
478    }
479
480    /// Get the amount of sats or the string "Market price"
481    pub fn sats_amount(&self) -> String {
482        if self.amount == 0 {
483            "Market price".to_string()
484        } else {
485            self.amount.to_string()
486        }
487    }
488    /// Check if the order has a zero amount and a premium or fiat amount
489    pub fn check_zero_amount_with_premium(&self) -> Result<(), CantDoReason> {
490        let premium = (self.premium != 0).then_some(self.premium);
491        let sats_amount = (self.amount != 0).then_some(self.amount);
492
493        if premium.is_some() && sats_amount.is_some() {
494            return Err(CantDoReason::InvalidParameters);
495        }
496        Ok(())
497    }
498    /// Check if the order is a range order and if the amount is zero
499    pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
500        // Check if the min and max amount are valid and update the vector
501        if let (Some(min), Some(max)) = (self.min_amount, self.max_amount) {
502            if min < 0 || max < 0 {
503                return Err(CantDoReason::InvalidAmount);
504            }
505            if min >= max {
506                return Err(CantDoReason::InvalidAmount);
507            }
508            if self.amount != 0 {
509                return Err(CantDoReason::InvalidAmount);
510            }
511            amounts.clear();
512            amounts.push(min);
513            amounts.push(max);
514        }
515        Ok(())
516    }
517
518    /// Check if the fiat currency is accepted
519    pub fn check_fiat_currency(
520        &self,
521        fiat_currencies_accepted: &[String],
522    ) -> Result<(), CantDoReason> {
523        if !fiat_currencies_accepted.contains(&self.fiat_code)
524            && !fiat_currencies_accepted.is_empty()
525        {
526            return Err(CantDoReason::InvalidFiatCurrency);
527        }
528        Ok(())
529    }
530}
531
532impl From<Order> for SmallOrder {
533    fn from(order: Order) -> Self {
534        let id = Some(order.id);
535        let kind = Kind::from_str(&order.kind).unwrap();
536        let status = Status::from_str(&order.status).unwrap();
537        let amount = order.amount;
538        let fiat_code = order.fiat_code.clone();
539        let min_amount = order.min_amount;
540        let max_amount = order.max_amount;
541        let fiat_amount = order.fiat_amount;
542        let payment_method = order.payment_method.clone();
543        let premium = order.premium;
544        let buyer_trade_pubkey = order.buyer_pubkey.clone();
545        let seller_trade_pubkey = order.seller_pubkey.clone();
546        let buyer_invoice = order.buyer_invoice.clone();
547
548        Self {
549            id,
550            kind: Some(kind),
551            status: Some(status),
552            amount,
553            fiat_code,
554            min_amount,
555            max_amount,
556            fiat_amount,
557            payment_method,
558            premium,
559            buyer_trade_pubkey,
560            seller_trade_pubkey,
561            buyer_invoice,
562            created_at: Some(order.created_at),
563            expires_at: Some(order.expires_at),
564        }
565    }
566}