cashu/nuts/
nut15.rs

1//! NUT-15: Multipart payments
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/15.md>
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use super::{CurrencyUnit, PaymentMethod};
8use crate::Amount;
9
10/// Multi-part payment
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename = "lowercase")]
13#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
14pub struct Mpp {
15    /// Amount
16    pub amount: Amount,
17}
18
19/// Mpp Method Settings
20#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
22pub struct MppMethodSettings {
23    /// Payment Method e.g. bolt11
24    pub method: PaymentMethod,
25    /// Currency Unit e.g. sat
26    pub unit: CurrencyUnit,
27}
28
29/// Mpp Settings
30#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
31#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema), schema(as = nut15::Settings))]
32pub struct Settings {
33    /// Method settings
34    pub methods: Vec<MppMethodSettings>,
35}
36
37// Custom deserialization to handle both array and object formats
38impl<'de> Deserialize<'de> for Settings {
39    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40    where
41        D: Deserializer<'de>,
42    {
43        #[derive(Deserialize)]
44        #[serde(untagged)]
45        enum SettingsFormat {
46            Array(Vec<MppMethodSettings>),
47            Object { methods: Vec<MppMethodSettings> },
48        }
49
50        let format = SettingsFormat::deserialize(deserializer)?;
51        match format {
52            SettingsFormat::Array(methods) => Ok(Settings { methods }),
53            SettingsFormat::Object { methods } => Ok(Settings { methods }),
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::PaymentMethod;
62
63    #[test]
64    fn test_nut15_settings_deserialization() {
65        // Test array format
66        let array_json = r#"[{"method":"bolt11","unit":"sat"}]"#;
67        let settings: Settings = serde_json::from_str(array_json).unwrap();
68        assert_eq!(settings.methods.len(), 1);
69        assert_eq!(settings.methods[0].method, PaymentMethod::Bolt11);
70        assert_eq!(settings.methods[0].unit, CurrencyUnit::Sat);
71
72        // Test object format
73        let object_json = r#"{"methods":[{"method":"bolt11","unit":"sat"}]}"#;
74        let settings: Settings = serde_json::from_str(object_json).unwrap();
75        assert_eq!(settings.methods.len(), 1);
76        assert_eq!(settings.methods[0].method, PaymentMethod::Bolt11);
77        assert_eq!(settings.methods[0].unit, CurrencyUnit::Sat);
78    }
79
80    #[test]
81    fn test_nut15_settings_serialization() {
82        let settings = Settings {
83            methods: vec![MppMethodSettings {
84                method: PaymentMethod::Bolt11,
85                unit: CurrencyUnit::Sat,
86            }],
87        };
88
89        let json = serde_json::to_string(&settings).unwrap();
90        assert_eq!(json, r#"{"methods":[{"method":"bolt11","unit":"sat"}]}"#);
91    }
92}