Skip to main content

mostro_core/
order.rs

1use crate::prelude::*;
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
12/// Orders can be only Buy or Sell
13#[wasm_bindgen]
14#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
15#[serde(rename_all = "kebab-case")]
16pub enum Kind {
17    Buy,
18    Sell,
19}
20
21impl FromStr for Kind {
22    type Err = ();
23
24    fn from_str(kind: &str) -> std::result::Result<Self, Self::Err> {
25        match kind.to_lowercase().as_str() {
26            "buy" => std::result::Result::Ok(Self::Buy),
27            "sell" => std::result::Result::Ok(Self::Sell),
28            _ => Err(()),
29        }
30    }
31}
32
33impl Display for Kind {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Kind::Sell => write!(f, "sell"),
37            Kind::Buy => write!(f, "buy"),
38        }
39    }
40}
41
42/// Each status that an order can have
43#[wasm_bindgen]
44#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
45#[serde(rename_all = "kebab-case")]
46pub enum Status {
47    Active,
48    Canceled,
49    CanceledByAdmin,
50    SettledByAdmin,
51    CompletedByAdmin,
52    Dispute,
53    Expired,
54    FiatSent,
55    SettledHoldInvoice,
56    Pending,
57    Success,
58    WaitingBuyerInvoice,
59    WaitingPayment,
60    CooperativelyCanceled,
61    InProgress,
62}
63
64impl Display for Status {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            Status::Active => write!(f, "active"),
68            Status::Canceled => write!(f, "canceled"),
69            Status::CanceledByAdmin => write!(f, "canceled-by-admin"),
70            Status::SettledByAdmin => write!(f, "settled-by-admin"),
71            Status::CompletedByAdmin => write!(f, "completed-by-admin"),
72            Status::Dispute => write!(f, "dispute"),
73            Status::Expired => write!(f, "expired"),
74            Status::FiatSent => write!(f, "fiat-sent"),
75            Status::SettledHoldInvoice => write!(f, "settled-hold-invoice"),
76            Status::Pending => write!(f, "pending"),
77            Status::Success => write!(f, "success"),
78            Status::WaitingBuyerInvoice => write!(f, "waiting-buyer-invoice"),
79            Status::WaitingPayment => write!(f, "waiting-payment"),
80            Status::CooperativelyCanceled => write!(f, "cooperatively-canceled"),
81            Status::InProgress => write!(f, "in-progress"),
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            "in-progress" => std::result::Result::Ok(Self::InProgress),
106            _ => Err(()),
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 dev_fee: i64,
139    pub dev_fee_paid: bool,
140    pub dev_fee_payment_hash: Option<String>,
141    pub fiat_code: String,
142    pub fiat_amount: i64,
143    pub buyer_invoice: Option<String>,
144    pub range_parent_id: Option<Uuid>,
145    pub invoice_held_at: i64,
146    pub taken_at: i64,
147    pub created_at: i64,
148    pub buyer_sent_rate: bool,
149    pub seller_sent_rate: bool,
150    pub failed_payment: bool,
151    pub payment_attempts: i64,
152    pub expires_at: i64,
153    pub trade_index_seller: Option<i64>,
154    pub trade_index_buyer: Option<i64>,
155    pub next_trade_pubkey: Option<String>,
156    pub next_trade_index: Option<i64>,
157}
158
159impl From<SmallOrder> for Order {
160    fn from(small_order: SmallOrder) -> Self {
161        Self {
162            id: Uuid::new_v4(),
163            // order will be overwritten with the real one before publishing
164            kind: small_order
165                .kind
166                .map_or_else(|| Kind::Buy.to_string(), |k| k.to_string()),
167            status: small_order
168                .status
169                .map_or_else(|| Status::Active.to_string(), |s| s.to_string()),
170            amount: small_order.amount,
171            fiat_code: small_order.fiat_code,
172            min_amount: small_order.min_amount,
173            max_amount: small_order.max_amount,
174            fiat_amount: small_order.fiat_amount,
175            payment_method: small_order.payment_method,
176            premium: small_order.premium,
177            event_id: String::new(),
178            creator_pubkey: String::new(),
179            price_from_api: false,
180            fee: 0,
181            routing_fee: 0,
182            dev_fee: 0,
183            dev_fee_paid: false,
184            dev_fee_payment_hash: None,
185            invoice_held_at: 0,
186            taken_at: 0,
187            created_at: small_order.created_at.unwrap_or(0),
188            expires_at: small_order.expires_at.unwrap_or(0),
189            payment_attempts: 0,
190            ..Default::default()
191        }
192    }
193}
194
195impl Order {
196    /// Convert an order to a small order
197    pub fn as_new_order(&self) -> SmallOrder {
198        SmallOrder::new(
199            Some(self.id),
200            Some(Kind::from_str(&self.kind).unwrap()),
201            Some(Status::from_str(&self.status).unwrap()),
202            self.amount,
203            self.fiat_code.clone(),
204            self.min_amount,
205            self.max_amount,
206            self.fiat_amount,
207            self.payment_method.clone(),
208            self.premium,
209            None,
210            None,
211            self.buyer_invoice.clone(),
212            Some(self.created_at),
213            Some(self.expires_at),
214        )
215    }
216    /// Get the kind of the order
217    pub fn get_order_kind(&self) -> Result<Kind, ServiceError> {
218        if let Ok(kind) = Kind::from_str(&self.kind) {
219            Ok(kind)
220        } else {
221            Err(ServiceError::InvalidOrderKind)
222        }
223    }
224
225    /// Get the status of the order in case
226    pub fn get_order_status(&self) -> Result<Status, ServiceError> {
227        if let Ok(status) = Status::from_str(&self.status) {
228            Ok(status)
229        } else {
230            Err(ServiceError::InvalidOrderStatus)
231        }
232    }
233
234    /// Compare the status of the order
235    pub fn check_status(&self, status: Status) -> Result<(), CantDoReason> {
236        match Status::from_str(&self.status) {
237            Ok(s) => match s == status {
238                true => Ok(()),
239                false => Err(CantDoReason::InvalidOrderStatus),
240            },
241            Err(_) => Err(CantDoReason::InvalidOrderStatus),
242        }
243    }
244
245    /// Check if the order is a buy order
246    pub fn is_buy_order(&self) -> Result<(), CantDoReason> {
247        if self.kind != Kind::Buy.to_string() {
248            return Err(CantDoReason::InvalidOrderKind);
249        }
250        Ok(())
251    }
252    /// Check if the order is a sell order
253    pub fn is_sell_order(&self) -> Result<(), CantDoReason> {
254        if self.kind != Kind::Sell.to_string() {
255            return Err(CantDoReason::InvalidOrderKind);
256        }
257        Ok(())
258    }
259
260    /// Check if the sender is the creator of the order
261    pub fn sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
262        let sender = sender.to_string();
263        if self.creator_pubkey != sender {
264            return Err(CantDoReason::InvalidPubkey);
265        }
266        Ok(())
267    }
268
269    /// Check if the sender is the creator of the order
270    pub fn not_sent_from_maker(&self, sender: PublicKey) -> Result<(), CantDoReason> {
271        let sender = sender.to_string();
272        if self.creator_pubkey == sender {
273            return Err(CantDoReason::InvalidPubkey);
274        }
275        Ok(())
276    }
277
278    /// Get the creator pubkey
279    pub fn get_creator_pubkey(&self) -> Result<PublicKey, ServiceError> {
280        match PublicKey::from_str(self.creator_pubkey.as_ref()) {
281            Ok(pk) => Ok(pk),
282            Err(_) => Err(ServiceError::InvalidPubkey),
283        }
284    }
285
286    /// Get the buyer pubkey
287    pub fn get_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
288        if let Some(pk) = self.buyer_pubkey.as_ref() {
289            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
290        } else {
291            Err(ServiceError::InvalidPubkey)
292        }
293    }
294    /// Get the seller pubkey
295    pub fn get_seller_pubkey(&self) -> Result<PublicKey, ServiceError> {
296        if let Some(pk) = self.seller_pubkey.as_ref() {
297            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
298        } else {
299            Err(ServiceError::InvalidPubkey)
300        }
301    }
302    /// Get the master buyer pubkey
303    pub fn get_master_buyer_pubkey(&self) -> Result<PublicKey, ServiceError> {
304        if let Some(pk) = self.master_buyer_pubkey.as_ref() {
305            PublicKey::from_str(pk).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(&self) -> Result<PublicKey, ServiceError> {
312        if let Some(pk) = self.master_seller_pubkey.as_ref() {
313            PublicKey::from_str(pk).map_err(|_| ServiceError::InvalidPubkey)
314        } else {
315            Err(ServiceError::InvalidPubkey)
316        }
317    }
318
319    /// Check if the order is a range order
320    pub fn is_range_order(&self) -> bool {
321        self.min_amount.is_some() && self.max_amount.is_some()
322    }
323
324    pub fn count_failed_payment(&mut self, retries_number: i64) {
325        if !self.failed_payment {
326            self.failed_payment = true;
327            self.payment_attempts = 1;
328        } else if self.payment_attempts < retries_number {
329            self.payment_attempts += 1;
330        }
331    }
332
333    /// Check if the order has no amount
334    pub fn has_no_amount(&self) -> bool {
335        self.amount == 0
336    }
337
338    /// Set the timestamp to now
339    pub fn set_timestamp_now(&mut self) {
340        self.taken_at = Timestamp::now().as_u64() as i64
341    }
342
343    /// Check if a user is creating a full privacy order so he doesn't to have reputation
344    /// compare master keys with the order keys if they are the same the user is in full privacy mode
345    /// otherwise the user is not in normal mode and has a reputation
346    pub fn is_full_privacy_order(&self) -> Result<(Option<String>, Option<String>), ServiceError> {
347        let (mut normal_buyer_idkey, mut normal_seller_idkey) = (None, None);
348
349        // Get master pubkeys to get users data from db
350        let master_buyer_pubkey = self.get_master_buyer_pubkey().ok();
351        let master_seller_pubkey = self.get_master_seller_pubkey().ok();
352
353        // Check if the buyer is in full privacy mode
354        if self.buyer_pubkey != master_buyer_pubkey.map(|pk| pk.to_string()) {
355            normal_buyer_idkey = master_buyer_pubkey.map(|pk| pk.to_string());
356        }
357
358        // Check if the seller is in full privacy mode
359        if self.seller_pubkey != master_seller_pubkey.map(|pk| pk.to_string()) {
360            normal_seller_idkey = master_seller_pubkey.map(|pk| pk.to_string());
361        }
362
363        Ok((normal_buyer_idkey, normal_seller_idkey))
364    }
365    /// Setup the dispute status
366    ///
367    /// If the pubkey is the buyer, set the buyer dispute to true
368    /// If the pubkey is the seller, set the seller dispute to true
369    pub fn setup_dispute(&mut self, is_buyer_dispute: bool) -> Result<(), CantDoReason> {
370        // Get the opposite dispute status
371        let is_seller_dispute = !is_buyer_dispute;
372
373        // Update dispute flags based on who initiated
374        let mut update_seller_dispute = false;
375        let mut update_buyer_dispute = false;
376
377        if is_seller_dispute && !self.seller_dispute {
378            update_seller_dispute = true;
379            self.seller_dispute = update_seller_dispute;
380        } else if is_buyer_dispute && !self.buyer_dispute {
381            update_buyer_dispute = true;
382            self.buyer_dispute = update_buyer_dispute;
383        };
384        // Set the status to dispute
385        self.status = Status::Dispute.to_string();
386
387        // Update the database with dispute information
388        // Save the dispute to DB
389        if !update_buyer_dispute && !update_seller_dispute {
390            return Err(CantDoReason::DisputeCreationError);
391        }
392
393        Ok(())
394    }
395}
396
397/// We use this struct to create a new order
398#[derive(Debug, Default, Deserialize, Serialize, Clone)]
399#[serde(deny_unknown_fields)]
400pub struct SmallOrder {
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub id: Option<Uuid>,
403    pub kind: Option<Kind>,
404    pub status: Option<Status>,
405    pub amount: i64,
406    pub fiat_code: String,
407    pub min_amount: Option<i64>,
408    pub max_amount: Option<i64>,
409    pub fiat_amount: i64,
410    pub payment_method: String,
411    pub premium: i64,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub buyer_trade_pubkey: Option<String>,
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub seller_trade_pubkey: Option<String>,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub buyer_invoice: Option<String>,
418    pub created_at: Option<i64>,
419    pub expires_at: Option<i64>,
420}
421
422#[allow(dead_code)]
423impl SmallOrder {
424    #[allow(clippy::too_many_arguments)]
425    pub fn new(
426        id: Option<Uuid>,
427        kind: Option<Kind>,
428        status: Option<Status>,
429        amount: i64,
430        fiat_code: String,
431        min_amount: Option<i64>,
432        max_amount: Option<i64>,
433        fiat_amount: i64,
434        payment_method: String,
435        premium: i64,
436        buyer_trade_pubkey: Option<String>,
437        seller_trade_pubkey: Option<String>,
438        buyer_invoice: Option<String>,
439        created_at: Option<i64>,
440        expires_at: Option<i64>,
441    ) -> Self {
442        Self {
443            id,
444            kind,
445            status,
446            amount,
447            fiat_code,
448            min_amount,
449            max_amount,
450            fiat_amount,
451            payment_method,
452            premium,
453            buyer_trade_pubkey,
454            seller_trade_pubkey,
455            buyer_invoice,
456            created_at,
457            expires_at,
458        }
459    }
460    /// New order from json string
461    pub fn from_json(json: &str) -> Result<Self, ServiceError> {
462        serde_json::from_str(json).map_err(|_| ServiceError::MessageSerializationError)
463    }
464
465    /// Get order as json string
466    pub fn as_json(&self) -> Result<String, ServiceError> {
467        serde_json::to_string(&self).map_err(|_| ServiceError::MessageSerializationError)
468    }
469
470    /// Get the amount of sats or the string "Market price"
471    pub fn sats_amount(&self) -> String {
472        if self.amount == 0 {
473            "Market price".to_string()
474        } else {
475            self.amount.to_string()
476        }
477    }
478    /// Check if fiat_amount is positive
479    pub fn check_fiat_amount(&self) -> Result<(), CantDoReason> {
480        if self.fiat_amount <= 0 {
481            return Err(CantDoReason::InvalidAmount);
482        }
483        Ok(())
484    }
485
486    /// Check if amount (sats) is non-negative when explicitly set
487    pub fn check_amount(&self) -> Result<(), CantDoReason> {
488        if self.amount < 0 {
489            return Err(CantDoReason::InvalidAmount);
490        }
491        Ok(())
492    }
493
494    /// Check if the order has a zero amount and a premium or fiat amount
495    pub fn check_zero_amount_with_premium(&self) -> Result<(), CantDoReason> {
496        let premium = (self.premium != 0).then_some(self.premium);
497        let sats_amount = (self.amount != 0).then_some(self.amount);
498
499        if premium.is_some() && sats_amount.is_some() {
500            return Err(CantDoReason::InvalidParameters);
501        }
502        Ok(())
503    }
504
505    /// Check if the order is a range order and if the amount is zero
506    pub fn check_range_order_limits(&self, amounts: &mut Vec<i64>) -> Result<(), CantDoReason> {
507        // Check if the min and max amount are valid and update the vector
508        if let (Some(min), Some(max)) = (self.min_amount, self.max_amount) {
509            if min < 0 || max < 0 {
510                return Err(CantDoReason::InvalidAmount);
511            }
512            if min >= max {
513                return Err(CantDoReason::InvalidAmount);
514            }
515            if self.amount != 0 {
516                return Err(CantDoReason::InvalidAmount);
517            }
518            amounts.clear();
519            amounts.push(min);
520            amounts.push(max);
521        }
522        Ok(())
523    }
524
525    /// Check if the fiat currency is accepted
526    pub fn check_fiat_currency(
527        &self,
528        fiat_currencies_accepted: &[String],
529    ) -> Result<(), CantDoReason> {
530        if !fiat_currencies_accepted.contains(&self.fiat_code)
531            && !fiat_currencies_accepted.is_empty()
532        {
533            return Err(CantDoReason::InvalidFiatCurrency);
534        }
535        Ok(())
536    }
537}
538
539impl From<Order> for SmallOrder {
540    fn from(order: Order) -> Self {
541        let id = Some(order.id);
542        let kind = Kind::from_str(&order.kind).unwrap();
543        let status = Status::from_str(&order.status).unwrap();
544        let amount = order.amount;
545        let fiat_code = order.fiat_code.clone();
546        let min_amount = order.min_amount;
547        let max_amount = order.max_amount;
548        let fiat_amount = order.fiat_amount;
549        let payment_method = order.payment_method.clone();
550        let premium = order.premium;
551        let buyer_trade_pubkey = order.buyer_pubkey.clone();
552        let seller_trade_pubkey = order.seller_pubkey.clone();
553        let buyer_invoice = order.buyer_invoice.clone();
554
555        Self {
556            id,
557            kind: Some(kind),
558            status: Some(status),
559            amount,
560            fiat_code,
561            min_amount,
562            max_amount,
563            fiat_amount,
564            payment_method,
565            premium,
566            buyer_trade_pubkey,
567            seller_trade_pubkey,
568            buyer_invoice,
569            created_at: Some(order.created_at),
570            expires_at: Some(order.expires_at),
571        }
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578    use crate::error::CantDoReason;
579    use nostr_sdk::Keys;
580    use uuid::uuid;
581
582    #[test]
583    fn test_status_string() {
584        assert_eq!(Status::Active.to_string(), "active");
585        assert_eq!(Status::CompletedByAdmin.to_string(), "completed-by-admin");
586        assert_eq!(Status::FiatSent.to_string(), "fiat-sent");
587        assert_ne!(Status::Pending.to_string(), "Pending");
588    }
589
590    #[test]
591    fn test_kind_string() {
592        assert_ne!(Kind::Sell.to_string(), "active");
593        assert_eq!(Kind::Sell.to_string(), "sell");
594        assert_eq!(Kind::Buy.to_string(), "buy");
595        assert_ne!(Kind::Buy.to_string(), "active");
596    }
597
598    #[test]
599    fn test_order_message() {
600        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
601        let payment_methods = "SEPA,Bank transfer".to_string();
602        let payload = Payload::Order(SmallOrder::new(
603            Some(uuid),
604            Some(Kind::Sell),
605            Some(Status::Pending),
606            100,
607            "eur".to_string(),
608            None,
609            None,
610            100,
611            payment_methods,
612            1,
613            None,
614            None,
615            None,
616            Some(1627371434),
617            None,
618        ));
619
620        let test_message = Message::Order(MessageKind::new(
621            Some(uuid),
622            Some(1),
623            Some(2),
624            Action::NewOrder,
625            Some(payload),
626        ));
627        let test_message_json = test_message.as_json().unwrap();
628        let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":2,"action":"new-order","payload":{"order":{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"pending","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"SEPA,Bank transfer","premium":1,"created_at":1627371434}}}}"#;
629        let message = Message::from_json(sample_message).unwrap();
630        assert!(message.verify());
631        let message_json = message.as_json().unwrap();
632        assert_eq!(message_json, test_message_json);
633    }
634
635    #[test]
636    fn test_payment_request_payload_message() {
637        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
638        let test_message = Message::Order(MessageKind::new(
639            Some(uuid),
640            Some(1),
641            Some(3),
642            Action::PayInvoice,
643            Some(Payload::PaymentRequest(
644                Some(SmallOrder::new(
645                    Some(uuid),
646                    Some(Kind::Sell),
647                    Some(Status::WaitingPayment),
648                    100,
649                    "eur".to_string(),
650                    None,
651                    None,
652                    100,
653                    "Face to face".to_string(),
654                    1,
655                    None,
656                    None,
657                    None,
658                    Some(1627371434),
659                    None,
660                )),
661                "lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e".to_string(),
662                None,
663            )),
664        ));
665        let sample_message = r#"{"order":{"version":1,"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","request_id":1,"trade_index":3,"action":"pay-invoice","payload":{"payment_request":[{"id":"308e1272-d5f4-47e6-bd97-3504baea9c23","kind":"sell","status":"waiting-payment","amount":100,"fiat_code":"eur","fiat_amount":100,"payment_method":"Face to face","premium":1,"created_at":1627371434},"lnbcrt78510n1pj59wmepp50677g8tffdqa2p8882y0x6newny5vtz0hjuyngdwv226nanv4uzsdqqcqzzsxqyz5vqsp5skn973360gp4yhlpmefwvul5hs58lkkl3u3ujvt57elmp4zugp4q9qyyssqw4nzlr72w28k4waycf27qvgzc9sp79sqlw83j56txltz4va44j7jda23ydcujj9y5k6k0rn5ms84w8wmcmcyk5g3mhpqepf7envhdccp72nz6e",null]}}}"#;
666        let message = Message::from_json(sample_message).unwrap();
667        assert!(message.verify());
668        let message_json = message.as_json().unwrap();
669        let test_message_json = test_message.as_json().unwrap();
670        assert_eq!(message_json, test_message_json);
671    }
672
673    #[test]
674    fn test_message_payload_signature() {
675        let uuid = uuid!("308e1272-d5f4-47e6-bd97-3504baea9c23");
676        let peer = Peer::new(
677            "npub1testjsf0runcqdht5apkfcalajxkf8txdxqqk5kgm0agc38ke4vsfsgzf8".to_string(),
678            None,
679        );
680        let payload = Payload::Peer(peer);
681        let test_message = Message::Order(MessageKind::new(
682            Some(uuid),
683            Some(1),
684            Some(2),
685            Action::FiatSentOk,
686            Some(payload),
687        ));
688        assert!(test_message.verify());
689        let test_message_json = test_message.as_json().unwrap();
690        // Message should be signed with the trade keys
691        let trade_keys =
692            Keys::parse("110e43647eae221ab1da33ddc17fd6ff423f2b2f49d809b9ffa40794a2ab996c")
693                .unwrap();
694        let sig = Message::sign(test_message_json.clone(), &trade_keys);
695
696        assert!(Message::verify_signature(
697            test_message_json,
698            trade_keys.public_key(),
699            sig
700        ));
701    }
702
703    #[test]
704    fn test_cant_do_message_serialization() {
705        // Test all CantDoReason variants
706        let reasons = vec![
707            CantDoReason::InvalidSignature,
708            CantDoReason::InvalidTradeIndex,
709            CantDoReason::InvalidAmount,
710            CantDoReason::InvalidInvoice,
711            CantDoReason::InvalidPaymentRequest,
712            CantDoReason::InvalidPeer,
713            CantDoReason::InvalidRating,
714            CantDoReason::InvalidTextMessage,
715            CantDoReason::InvalidOrderStatus,
716            CantDoReason::InvalidPubkey,
717            CantDoReason::InvalidParameters,
718            CantDoReason::OrderAlreadyCanceled,
719            CantDoReason::CantCreateUser,
720            CantDoReason::IsNotYourOrder,
721            CantDoReason::NotAllowedByStatus,
722            CantDoReason::OutOfRangeFiatAmount,
723            CantDoReason::OutOfRangeSatsAmount,
724            CantDoReason::IsNotYourDispute,
725            CantDoReason::NotFound,
726            CantDoReason::InvalidFiatCurrency,
727            CantDoReason::TooManyRequests,
728        ];
729
730        for reason in reasons {
731            let cant_do = Message::CantDo(MessageKind::new(
732                None,
733                None,
734                None,
735                Action::CantDo,
736                Some(Payload::CantDo(Some(reason.clone()))),
737            ));
738            let message = Message::from_json(&cant_do.as_json().unwrap()).unwrap();
739            assert!(message.verify());
740            assert_eq!(message.as_json().unwrap(), cant_do.as_json().unwrap());
741        }
742
743        // Test None case
744        let cant_do = Message::CantDo(MessageKind::new(
745            None,
746            None,
747            None,
748            Action::CantDo,
749            Some(Payload::CantDo(None)),
750        ));
751        let message = Message::from_json(&cant_do.as_json().unwrap()).unwrap();
752        assert!(message.verify());
753        assert_eq!(message.as_json().unwrap(), cant_do.as_json().unwrap());
754    }
755
756    // === check_fiat_amount tests ===
757
758    #[test]
759    fn test_check_fiat_amount_valid() {
760        // id, kind, status, amount, fiat_code, min_amount, max_amount, fiat_amount, payment_method, premium, buyer_pubkey, seller_pubkey, buyer_invoice, created_at, expires_at
761        let order = SmallOrder::new(
762            None,
763            None,
764            None,
765            100,
766            "VES".to_string(),
767            None,
768            None,
769            500,
770            "Bank".to_string(),
771            1,
772            None,
773            None,
774            None,
775            None,
776            None,
777        );
778        assert!(order.check_fiat_amount().is_ok());
779    }
780
781    #[test]
782    fn test_check_fiat_amount_zero() {
783        let order = SmallOrder::new(
784            None,
785            None,
786            None,
787            100,
788            "VES".to_string(),
789            None,
790            None,
791            0,
792            "Bank".to_string(),
793            1,
794            None,
795            None,
796            None,
797            None,
798            None,
799        );
800        let result = order.check_fiat_amount();
801        assert!(result.is_err());
802        assert_eq!(result.unwrap_err(), CantDoReason::InvalidAmount);
803    }
804
805    #[test]
806    fn test_check_fiat_amount_negative() {
807        let order = SmallOrder::new(
808            None,
809            None,
810            None,
811            100,
812            "VES".to_string(),
813            None,
814            None,
815            -100,
816            "Bank".to_string(),
817            1,
818            None,
819            None,
820            None,
821            None,
822            None,
823        );
824        let result = order.check_fiat_amount();
825        assert!(result.is_err());
826        assert_eq!(result.unwrap_err(), CantDoReason::InvalidAmount);
827    }
828
829    // === check_amount tests ===
830
831    #[test]
832    fn test_check_amount_valid() {
833        // amount = 100000 (positive, valid sats)
834        let order = SmallOrder::new(
835            None,
836            None,
837            None,
838            100000,
839            "VES".to_string(),
840            None,
841            None,
842            500,
843            "Bank".to_string(),
844            0,
845            None,
846            None,
847            None,
848            None,
849            None,
850        );
851        assert!(order.check_amount().is_ok());
852    }
853
854    #[test]
855    fn test_check_amount_zero() {
856        // amount = 0 is valid (seller sets exact sats amount)
857        let order = SmallOrder::new(
858            None,
859            None,
860            None,
861            0,
862            "VES".to_string(),
863            None,
864            None,
865            500,
866            "Bank".to_string(),
867            0,
868            None,
869            None,
870            None,
871            None,
872            None,
873        );
874        assert!(order.check_amount().is_ok());
875    }
876
877    #[test]
878    fn test_check_amount_negative() {
879        // amount = -1000 (negative, invalid)
880        let order = SmallOrder::new(
881            None,
882            None,
883            None,
884            -1000,
885            "VES".to_string(),
886            None,
887            None,
888            500,
889            "Bank".to_string(),
890            0,
891            None,
892            None,
893            None,
894            None,
895            None,
896        );
897        let result = order.check_amount();
898        assert!(result.is_err());
899        assert_eq!(result.unwrap_err(), CantDoReason::InvalidAmount);
900    }
901}