1use std::fmt;
2use std::str::FromStr;
3
4use anyhow::Context;
5use bitcoin::address::{NetworkChecked, NetworkUnchecked};
6use bitcoin::hex::DisplayHex;
7use lightning::offers::invoice::Bolt12Invoice;
8use lightning::offers::offer::Offer;
9use lightning_invoice::Bolt11Invoice;
10use lnurllib::lightning_address::LightningAddress;
11use lnurllib::lnurl::LnUrl;
12use serde::{Deserialize, Serialize};
13
14use ark::lightning::Invoice;
15
16const PAYMENT_METHOD_TAG: &str = "type";
17const PAYMENT_METHOD_VALUE: &str = "value";
18const PAYMENT_METHOD_ARK: &str = "ark";
19const PAYMENT_METHOD_BITCOIN: &str = "bitcoin";
20const PAYMENT_METHOD_OUTPUT_SCRIPT: &str = "output-script";
21const PAYMENT_METHOD_INVOICE: &str = "invoice";
22const PAYMENT_METHOD_OFFER: &str = "offer";
23const PAYMENT_METHOD_LIGHTNING_ADDRESS: &str = "lightning-address";
24const PAYMENT_METHOD_LNURL: &str = "lnurl";
25const PAYMENT_METHOD_CUSTOM: &str = "custom";
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum PaymentMethod {
31 Ark(ark::Address),
33 Bitcoin(bitcoin::Address<NetworkUnchecked>),
35 OutputScript(bitcoin::ScriptBuf),
38 Invoice(Invoice),
40 Offer(Offer),
42 LightningAddress(LightningAddress),
44 Lnurl(LnUrl),
47 Custom(String),
49}
50
51impl PaymentMethod {
52 pub fn is_ark(&self) -> bool {
53 match self {
54 PaymentMethod::Ark(_) => true,
55 PaymentMethod::Bitcoin(_) => false,
56 PaymentMethod::OutputScript(_) => false,
57 PaymentMethod::Invoice(_) => false,
58 PaymentMethod::Offer(_) => false,
59 PaymentMethod::LightningAddress(_) => false,
60 PaymentMethod::Lnurl(_) => false,
61 PaymentMethod::Custom(_) => false,
62 }
63 }
64
65 pub fn is_bitcoin(&self) -> bool {
66 match self {
67 PaymentMethod::Ark(_) => false,
68 PaymentMethod::Bitcoin(_) => true,
69 PaymentMethod::OutputScript(_) => true,
70 PaymentMethod::Invoice(_) => false,
71 PaymentMethod::Offer(_) => false,
72 PaymentMethod::LightningAddress(_) => false,
73 PaymentMethod::Lnurl(_) => false,
74 PaymentMethod::Custom(_) => false,
75 }
76 }
77
78 pub fn is_custom(&self) -> bool {
79 match self {
80 PaymentMethod::Ark(_) => false,
81 PaymentMethod::Bitcoin(_) => false,
82 PaymentMethod::OutputScript(_) => false,
83 PaymentMethod::Invoice(_) => false,
84 PaymentMethod::Offer(_) => false,
85 PaymentMethod::LightningAddress(_) => false,
86 PaymentMethod::Lnurl(_) => false,
87 PaymentMethod::Custom(_) => true,
88 }
89 }
90
91 pub fn is_lightning(&self) -> bool {
93 match self {
94 PaymentMethod::Ark(_) => false,
95 PaymentMethod::Bitcoin(_) => false,
96 PaymentMethod::OutputScript(_) => false,
97 PaymentMethod::Invoice(_) => true,
98 PaymentMethod::Offer(_) => true,
99 PaymentMethod::LightningAddress(_) => true,
100 PaymentMethod::Lnurl(_) => true,
101 PaymentMethod::Custom(_) => false,
102 }
103 }
104
105 pub fn type_str(&self) -> &'static str {
107 match self {
108 PaymentMethod::Ark(_) => PAYMENT_METHOD_ARK,
109 PaymentMethod::Bitcoin(_) => PAYMENT_METHOD_BITCOIN,
110 PaymentMethod::OutputScript(_) => PAYMENT_METHOD_OUTPUT_SCRIPT,
111 PaymentMethod::Invoice(_) => PAYMENT_METHOD_INVOICE,
112 PaymentMethod::Offer(_) => PAYMENT_METHOD_OFFER,
113 PaymentMethod::LightningAddress(_) => PAYMENT_METHOD_LIGHTNING_ADDRESS,
114 PaymentMethod::Lnurl(_) => PAYMENT_METHOD_LNURL,
115 PaymentMethod::Custom(_) => PAYMENT_METHOD_CUSTOM,
116 }
117 }
118
119 pub fn value_string(&self) -> String {
121 match self {
122 PaymentMethod::Ark(addr) => addr.to_string(),
123 PaymentMethod::Bitcoin(addr) => addr.assume_checked_ref().to_string(),
124 PaymentMethod::OutputScript(script) => script.as_bytes().to_lower_hex_string(),
125 PaymentMethod::Invoice(invoice) => invoice.to_string(),
126 PaymentMethod::Offer(offer) => offer.to_string(),
127 PaymentMethod::LightningAddress(addr) => addr.to_string(),
128 PaymentMethod::Lnurl(lnurl) => lnurl.to_string(),
129 PaymentMethod::Custom(custom) => custom.clone(),
130 }
131 }
132
133 pub fn from_type_value(type_str: &str, value: &str) -> anyhow::Result<Self> {
135 match type_str {
136 PAYMENT_METHOD_ARK => {
137 let addr = ark::Address::from_str(value)
138 .context("invalid ark address")?;
139 Ok(PaymentMethod::Ark(addr))
140 },
141 PAYMENT_METHOD_BITCOIN => {
142 let addr = bitcoin::Address::from_str(value)
143 .context("invalid bitcoin address")?;
144 Ok(PaymentMethod::Bitcoin(addr))
145 },
146 PAYMENT_METHOD_OUTPUT_SCRIPT => {
147 let script = bitcoin::ScriptBuf::from_hex(value)
148 .context("invalid output script hex")?;
149 Ok(PaymentMethod::OutputScript(script))
150 },
151 PAYMENT_METHOD_INVOICE => {
152 let invoice = Invoice::from_str(value)
153 .context("invalid invoice")?;
154 Ok(PaymentMethod::Invoice(invoice))
155 },
156 PAYMENT_METHOD_OFFER => {
157 let offer = value.parse()
158 .map_err(|e| anyhow!("{:?}", e))
159 .context("invalid offer")?;
160 Ok(PaymentMethod::Offer(offer))
161 },
162 PAYMENT_METHOD_LIGHTNING_ADDRESS => {
163 let addr = LightningAddress::from_str(value)
164 .context("invalid lightning address")?;
165 Ok(PaymentMethod::LightningAddress(addr))
166 },
167 PAYMENT_METHOD_LNURL => {
168 let lnurl = LnUrl::from_str(value)
169 .context("invalid lnurl")?;
170 Ok(PaymentMethod::Lnurl(lnurl))
171 },
172 PAYMENT_METHOD_CUSTOM => {
173 Ok(PaymentMethod::Custom(value.to_string()))
174 },
175 _ => bail!("unknown payment method type: {}", type_str),
176 }
177 }
178}
179
180impl fmt::Display for PaymentMethod {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 match self {
183 PaymentMethod::Ark(a) => fmt::Display::fmt(a, f),
184 PaymentMethod::Bitcoin(b) => fmt::Display::fmt(b.assume_checked_ref(), f),
185 PaymentMethod::OutputScript(o) => fmt::Display::fmt(&o.as_bytes().as_hex(), f),
186 PaymentMethod::Invoice(i) => fmt::Display::fmt(i, f),
187 PaymentMethod::Offer(o) => fmt::Display::fmt(o, f),
188 PaymentMethod::LightningAddress(a) => fmt::Display::fmt(a, f),
189 PaymentMethod::Lnurl(l) => fmt::Display::fmt(l, f),
190 PaymentMethod::Custom(v) => fmt::Display::fmt(v, f),
191 }
192 }
193}
194
195impl From<ark::Address> for PaymentMethod {
196 fn from(addr: ark::Address) -> Self {
197 PaymentMethod::Ark(addr)
198 }
199}
200
201impl From<bitcoin::Address<NetworkUnchecked>> for PaymentMethod {
202 fn from(addr: bitcoin::Address<NetworkUnchecked>) -> Self {
203 PaymentMethod::Bitcoin(addr)
204 }
205}
206
207impl From<bitcoin::Address<NetworkChecked>> for PaymentMethod {
208 fn from(addr: bitcoin::Address<NetworkChecked>) -> Self {
209 PaymentMethod::Bitcoin(addr.into_unchecked())
210 }
211}
212
213impl From<Bolt11Invoice> for PaymentMethod {
214 fn from(invoice: Bolt11Invoice) -> Self {
215 PaymentMethod::Invoice(invoice.into())
216 }
217}
218
219impl From<Bolt12Invoice> for PaymentMethod {
220 fn from(invoice: Bolt12Invoice) -> Self {
221 PaymentMethod::Invoice(invoice.into())
222 }
223}
224
225impl From<Invoice> for PaymentMethod {
226 fn from(invoice: Invoice) -> Self {
227 PaymentMethod::Invoice(invoice)
228 }
229}
230
231impl From<Offer> for PaymentMethod {
232 fn from(offer: Offer) -> Self {
233 PaymentMethod::Offer(offer)
234 }
235}
236
237impl From<LightningAddress> for PaymentMethod {
238 fn from(addr: LightningAddress) -> Self {
239 PaymentMethod::LightningAddress(addr)
240 }
241}
242
243impl From<LnUrl> for PaymentMethod {
244 fn from(lnurl: LnUrl) -> Self {
245 PaymentMethod::Lnurl(lnurl)
246 }
247}
248
249impl Serialize for PaymentMethod {
250 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251 where
252 S: serde::Serializer,
253 {
254 use serde::ser::SerializeStruct;
255 let mut state = serializer.serialize_struct("PaymentMethod", 2)?;
256 state.serialize_field(PAYMENT_METHOD_TAG, self.type_str())?;
257 state.serialize_field(PAYMENT_METHOD_VALUE, &self.value_string())?;
258 state.end()
259 }
260}
261
262impl<'de> Deserialize<'de> for PaymentMethod {
263 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
264 where
265 D: serde::Deserializer<'de>,
266 {
267 use serde::de::{self, MapAccess, Visitor};
268 use std::fmt;
269
270 struct PaymentMethodVisitor;
271
272 impl<'de> Visitor<'de> for PaymentMethodVisitor {
273 type Value = PaymentMethod;
274
275 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
276 formatter.write_str(&format!(
277 "a PaymentMethod with {} and {} fields", PAYMENT_METHOD_TAG, PAYMENT_METHOD_VALUE,
278 ))
279 }
280
281 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
282 where
283 A: MapAccess<'de>,
284 {
285 let mut type_value: Option<String> = None;
286 let mut value_string: Option<String> = None;
287
288 while let Some(key) = map.next_key::<String>()? {
289 match key.as_str() {
290 PAYMENT_METHOD_TAG => {
291 if type_value.is_some() {
292 return Err(de::Error::duplicate_field(PAYMENT_METHOD_TAG));
293 }
294 type_value = Some(map.next_value()?);
295 }
296 PAYMENT_METHOD_VALUE => {
297 if value_string.is_some() {
298 return Err(de::Error::duplicate_field(PAYMENT_METHOD_VALUE));
299 }
300 value_string = Some(map.next_value()?);
301 }
302 _ => {
303 let _: de::IgnoredAny = map.next_value()?;
304 }
305 }
306 }
307
308 let type_str = type_value.ok_or_else(|| de::Error::missing_field(PAYMENT_METHOD_TAG))?;
309 let value = value_string.ok_or_else(|| de::Error::missing_field(PAYMENT_METHOD_VALUE))?;
310
311 PaymentMethod::from_type_value(&type_str, &value).map_err(de::Error::custom)
312 }
313 }
314
315 deserializer.deserialize_struct(
316 "PaymentMethod", &[PAYMENT_METHOD_TAG, PAYMENT_METHOD_VALUE], PaymentMethodVisitor,
317 )
318 }
319}
320
321#[cfg(test)]
322mod test {
323 use std::str::FromStr;
324
325 use super::*;
326
327 #[test]
328 fn test_serialization() {
329 let ark_str = "tark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2y3zqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yscufs5u";
330 let serialised = r#"{"type":"ark","value":"tark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2y3zqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yscufs5u"}"#;
331 let ark_method = PaymentMethod::Ark(ark::Address::from_str(ark_str).unwrap());
332 assert_eq!(serde_json::to_string(&ark_method).unwrap(), serialised);
333 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), ark_method);
334
335 let bitcoin_str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
336 let serialised = r#"{"type":"bitcoin","value":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"}"#;
337 let bitcoin_method = PaymentMethod::Bitcoin(bitcoin::Address::from_str(bitcoin_str).unwrap());
338 assert_eq!(serde_json::to_string(&bitcoin_method).unwrap(), serialised);
339 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), bitcoin_method);
340
341 let script_str = "6a0474657374"; let serialised = r#"{"type":"output-script","value":"6a0474657374"}"#;
343 let output_method = PaymentMethod::OutputScript(bitcoin::ScriptBuf::from_hex(script_str).unwrap());
344 assert_eq!(serde_json::to_string(&output_method).unwrap(), serialised);
345 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), output_method);
346
347 let invoice_str = "lntbs100u1p5j0x82sp5d0rwfh7tgrrlwsegy9rx3tzpt36cqwjqza5x4wvcjxjzscfaf6jspp5d8q7354dg3p8h0kywhqq5dq984r8f5en98hf9ln85ug0w8fx6hhsdqqcqzpc9qyysgqyk54v7tpzprxll7e0jyvtxcpgwttzk84wqsfjsqvcdtq47zt2wssxsmtjhz8dka62mdnf9jafhu3l4cpyfnsx449v4wstrwzzql2w5qqs8uh7p";
348 let serialised = r#"{"type":"invoice","value":"lntbs100u1p5j0x82sp5d0rwfh7tgrrlwsegy9rx3tzpt36cqwjqza5x4wvcjxjzscfaf6jspp5d8q7354dg3p8h0kywhqq5dq984r8f5en98hf9ln85ug0w8fx6hhsdqqcqzpc9qyysgqyk54v7tpzprxll7e0jyvtxcpgwttzk84wqsfjsqvcdtq47zt2wssxsmtjhz8dka62mdnf9jafhu3l4cpyfnsx449v4wstrwzzql2w5qqs8uh7p"}"#;
349 let invoice_method = PaymentMethod::Invoice(Bolt11Invoice::from_str(invoice_str).unwrap().into());
350 assert_eq!(serde_json::to_string(&invoice_method).unwrap(), serialised);
351 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), invoice_method);
352
353 let offer_str = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj";
354 let serialised = r#"{"type":"offer","value":"lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj"}"#;
355 let offer_method = PaymentMethod::Offer(Offer::from_str(offer_str).unwrap());
356 assert_eq!(serde_json::to_string(&offer_method).unwrap(), serialised);
357 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), offer_method);
358
359 let lnaddr_str = "byte@second.tech";
360 let serialised = r#"{"type":"lightning-address","value":"byte@second.tech"}"#;
361 let lnaddr_method = PaymentMethod::LightningAddress(LightningAddress::from_str(lnaddr_str).unwrap());
362 assert_eq!(serde_json::to_string(&lnaddr_method).unwrap(), serialised);
363 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), lnaddr_method);
364
365 let lnurl_str = "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS";
366 let serialised = r#"{"type":"lnurl","value":"lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hxqurzepexejxxepnxscrvwfnv9nxzcn9xq6xyefhvgcxxcmyxymnserxfq5fns"}"#;
367 let lnurl_method = PaymentMethod::Lnurl(LnUrl::from_str(lnurl_str).unwrap());
368 assert_eq!(serde_json::to_string(&lnurl_method).unwrap(), serialised);
369 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), lnurl_method);
370
371 let custom_str = "THIS IS AN EXAMPLE OF A CUSTOM STRING";
372 let serialised = r#"{"type":"custom","value":"THIS IS AN EXAMPLE OF A CUSTOM STRING"}"#;
373 let custom_method = PaymentMethod::Custom(String::from(custom_str));
374 assert_eq!(serde_json::to_string(&custom_method).unwrap(), serialised);
375 assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), custom_method);
376 }
377}