Skip to main content

bark/movement/
payment_method.rs

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, OfferAmountExt};
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/// Provides a typed mechanism for describing the recipient in a
28/// [MovementDestination](crate::movement::MovementDestination).
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum PaymentMethod {
31	/// An [ark::Address] for bark.
32	Ark(ark::Address),
33	/// An onchain [bitcoin::Address].
34	Bitcoin(bitcoin::Address<NetworkUnchecked>),
35	/// An onchain [bitcoin::ScriptBuf] output, typically used for non-address formats like
36	/// OP_RETURN.
37	OutputScript(bitcoin::ScriptBuf),
38	/// Any supported form of lightning [Invoice], e.g., [Bolt11Invoice] and [Bolt12Invoice].
39	Invoice(Invoice),
40	/// A reusable BOLT12 [Offer] for lightning payments.
41	Offer(Offer),
42	/// An email-like format used to retrieve a [Bolt11Invoice].
43	LightningAddress(LightningAddress),
44	/// A bech32-encoded LNURL-pay link (`lnurl1…`) resolved to a [Bolt11Invoice]
45	/// via an LNURL-Pay request.
46	Lnurl(LnUrl),
47	/// An alternative payment method that isn't native to bark.
48	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	/// Returns whether the payment method is a lightning payment method, e.g., BOLT11.
92	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	/// Returns the type tag string for this payment method.
106	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	/// Returns the value as a plain string for this payment method.
120	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	/// Returns whether the payment method supports a comment on payment.
134	pub fn supports_comment(&self) -> bool {
135		match self {
136			PaymentMethod::Ark(_) => false,
137			PaymentMethod::Bitcoin(_) => false,
138			PaymentMethod::OutputScript(_) => false,
139			PaymentMethod::Invoice(_) => false,
140			PaymentMethod::Offer(_) => false,
141			PaymentMethod::LightningAddress(_) => true,
142			PaymentMethod::Lnurl(_) => true,
143			PaymentMethod::Custom(_) => false,
144		}
145	}
146
147	/// Returns whether the payment method needs an externally provided amount,
148	/// either because it cannot carry one itself or because it doesn't carry
149	/// one.
150	///
151	/// Invoices and offers can carry their own amount; only amountless ones
152	/// require an amount upfront. An offer denominated in a fiat currency has
153	/// no bitcoin amount, so it also requires one.
154	///
155	/// For [PaymentMethod::Custom] we can't tell whether an amount is needed,
156	/// so we never require one and leave it to whoever handles the custom
157	/// method.
158	pub fn requires_amount(&self) -> bool {
159		match self {
160			PaymentMethod::Ark(_) => true,
161			PaymentMethod::Bitcoin(_) => true,
162			PaymentMethod::OutputScript(_) => true,
163			PaymentMethod::Invoice(invoice) => invoice.amount_msat().is_none(),
164			PaymentMethod::Offer(offer) => {
165				offer.amount().and_then(|a| a.to_bitcoin_amount()).is_none()
166			},
167			PaymentMethod::LightningAddress(_) => true,
168			PaymentMethod::Lnurl(_) => true,
169			PaymentMethod::Custom(_) => false,
170		}
171	}
172
173	/// Construct a PaymentMethod from a type tag and value string.
174	pub fn from_type_value(type_str: &str, value: &str) -> anyhow::Result<Self> {
175		match type_str {
176			PAYMENT_METHOD_ARK => {
177				let addr = ark::Address::from_str(value)
178					.context("invalid ark address")?;
179				Ok(PaymentMethod::Ark(addr))
180			},
181			PAYMENT_METHOD_BITCOIN => {
182				let addr = bitcoin::Address::from_str(value)
183					.context("invalid bitcoin address")?;
184				Ok(PaymentMethod::Bitcoin(addr))
185			},
186			PAYMENT_METHOD_OUTPUT_SCRIPT => {
187				let script = bitcoin::ScriptBuf::from_hex(value)
188					.context("invalid output script hex")?;
189				Ok(PaymentMethod::OutputScript(script))
190			},
191			PAYMENT_METHOD_INVOICE => {
192				let invoice = Invoice::from_str(value)
193					.context("invalid invoice")?;
194				Ok(PaymentMethod::Invoice(invoice))
195			},
196			PAYMENT_METHOD_OFFER => {
197				let offer = value.parse()
198					.map_err(|e| anyhow!("{:?}", e))
199					.context("invalid offer")?;
200				Ok(PaymentMethod::Offer(offer))
201			},
202			PAYMENT_METHOD_LIGHTNING_ADDRESS => {
203				let addr = LightningAddress::from_str(value)
204					.context("invalid lightning address")?;
205				Ok(PaymentMethod::LightningAddress(addr))
206			},
207			PAYMENT_METHOD_LNURL => {
208				let lnurl = LnUrl::from_str(value)
209					.context("invalid lnurl")?;
210				Ok(PaymentMethod::Lnurl(lnurl))
211			},
212			PAYMENT_METHOD_CUSTOM => {
213				Ok(PaymentMethod::Custom(value.to_string()))
214			},
215			_ => bail!("unknown payment method type: {}", type_str),
216		}
217	}
218}
219
220impl fmt::Display for PaymentMethod {
221	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222	    match self {
223			PaymentMethod::Ark(a) => fmt::Display::fmt(a, f),
224			PaymentMethod::Bitcoin(b) => fmt::Display::fmt(b.assume_checked_ref(), f),
225			PaymentMethod::OutputScript(o) => fmt::Display::fmt(&o.as_bytes().as_hex(), f),
226			PaymentMethod::Invoice(i) => fmt::Display::fmt(i, f),
227			PaymentMethod::Offer(o) => fmt::Display::fmt(o, f),
228			PaymentMethod::LightningAddress(a) => fmt::Display::fmt(a, f),
229			PaymentMethod::Lnurl(l) => fmt::Display::fmt(l, f),
230			PaymentMethod::Custom(v) => fmt::Display::fmt(v, f),
231		}
232	}
233}
234
235impl From<ark::Address> for PaymentMethod {
236	fn from(addr: ark::Address) -> Self {
237		PaymentMethod::Ark(addr)
238	}
239}
240
241impl From<bitcoin::Address<NetworkUnchecked>> for PaymentMethod {
242	fn from(addr: bitcoin::Address<NetworkUnchecked>) -> Self {
243		PaymentMethod::Bitcoin(addr)
244	}
245}
246
247impl From<bitcoin::Address<NetworkChecked>> for PaymentMethod {
248	fn from(addr: bitcoin::Address<NetworkChecked>) -> Self {
249		PaymentMethod::Bitcoin(addr.into_unchecked())
250	}
251}
252
253impl From<Bolt11Invoice> for PaymentMethod {
254	fn from(invoice: Bolt11Invoice) -> Self {
255		PaymentMethod::Invoice(invoice.into())
256	}
257}
258
259impl From<Bolt12Invoice> for PaymentMethod {
260	fn from(invoice: Bolt12Invoice) -> Self {
261		PaymentMethod::Invoice(invoice.into())
262	}
263}
264
265impl From<Invoice> for PaymentMethod {
266	fn from(invoice: Invoice) -> Self {
267		PaymentMethod::Invoice(invoice)
268	}
269}
270
271impl From<Offer> for PaymentMethod {
272	fn from(offer: Offer) -> Self {
273		PaymentMethod::Offer(offer)
274	}
275}
276
277impl From<LightningAddress> for PaymentMethod {
278	fn from(addr: LightningAddress) -> Self {
279		PaymentMethod::LightningAddress(addr)
280	}
281}
282
283impl From<LnUrl> for PaymentMethod {
284	fn from(lnurl: LnUrl) -> Self {
285		PaymentMethod::Lnurl(lnurl)
286	}
287}
288
289impl Serialize for PaymentMethod {
290	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
291	where
292		S: serde::Serializer,
293	{
294		use serde::ser::SerializeStruct;
295		let mut state = serializer.serialize_struct("PaymentMethod", 2)?;
296		state.serialize_field(PAYMENT_METHOD_TAG, self.type_str())?;
297		state.serialize_field(PAYMENT_METHOD_VALUE, &self.value_string())?;
298		state.end()
299	}
300}
301
302impl<'de> Deserialize<'de> for PaymentMethod {
303	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304	where
305		D: serde::Deserializer<'de>,
306	{
307		use serde::de::{self, MapAccess, Visitor};
308		use std::fmt;
309
310		struct PaymentMethodVisitor;
311
312		impl<'de> Visitor<'de> for PaymentMethodVisitor {
313			type Value = PaymentMethod;
314
315			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
316				formatter.write_str(&format!(
317					"a PaymentMethod with {} and {} fields", PAYMENT_METHOD_TAG, PAYMENT_METHOD_VALUE,
318				))
319			}
320
321			fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
322			where
323				A: MapAccess<'de>,
324			{
325				let mut type_value: Option<String> = None;
326				let mut value_string: Option<String> = None;
327
328				while let Some(key) = map.next_key::<String>()? {
329					match key.as_str() {
330						PAYMENT_METHOD_TAG => {
331							if type_value.is_some() {
332								return Err(de::Error::duplicate_field(PAYMENT_METHOD_TAG));
333							}
334							type_value = Some(map.next_value()?);
335						}
336						PAYMENT_METHOD_VALUE => {
337							if value_string.is_some() {
338								return Err(de::Error::duplicate_field(PAYMENT_METHOD_VALUE));
339							}
340							value_string = Some(map.next_value()?);
341						}
342						_ => {
343							let _: de::IgnoredAny = map.next_value()?;
344						}
345					}
346				}
347
348				let type_str = type_value.ok_or_else(|| de::Error::missing_field(PAYMENT_METHOD_TAG))?;
349				let value = value_string.ok_or_else(|| de::Error::missing_field(PAYMENT_METHOD_VALUE))?;
350
351				PaymentMethod::from_type_value(&type_str, &value).map_err(de::Error::custom)
352			}
353		}
354
355		deserializer.deserialize_struct(
356			"PaymentMethod", &[PAYMENT_METHOD_TAG, PAYMENT_METHOD_VALUE], PaymentMethodVisitor,
357		)
358	}
359}
360
361#[cfg(test)]
362mod test {
363	use std::str::FromStr;
364	use std::time::Duration;
365
366	use bitcoin::hashes::{sha256, Hash};
367	use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
368	use lightning::offers::offer::OfferBuilder;
369	use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret};
370
371	use super::*;
372
373	fn test_invoice(amount_msat: Option<u64>) -> Bolt11Invoice {
374		let secp = Secp256k1::new();
375		let secret = SecretKey::from_slice(&[2; 32]).unwrap();
376		let hash = sha256::Hash::hash(b"preimage");
377		let builder = InvoiceBuilder::new(Currency::Regtest)
378			.description("test".into())
379			.payment_hash(hash)
380			.payment_secret(PaymentSecret([42; 32]))
381			.duration_since_epoch(Duration::from_secs(1_700_000_000))
382			.min_final_cltv_expiry_delta(144);
383		let builder = match amount_msat {
384			Some(msat) => builder.amount_milli_satoshis(msat),
385			None => builder,
386		};
387		builder.build_signed(|hash: &Message| secp.sign_ecdsa_recoverable(hash, &secret)).unwrap()
388	}
389
390	#[test]
391	fn test_requires_amount() {
392		// Methods that cannot carry an amount always require one.
393		let ark_str = "tark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2y3zqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yscufs5u";
394		assert!(PaymentMethod::Ark(ark::Address::from_str(ark_str).unwrap()).requires_amount());
395		let bitcoin_str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
396		assert!(PaymentMethod::Bitcoin(bitcoin::Address::from_str(bitcoin_str).unwrap()).requires_amount());
397		let script = bitcoin::ScriptBuf::from_hex("6a0474657374").unwrap();
398		assert!(PaymentMethod::OutputScript(script).requires_amount());
399		let lnaddr = LightningAddress::from_str("byte@second.tech").unwrap();
400		assert!(PaymentMethod::LightningAddress(lnaddr).requires_amount());
401		let lnurl_str = "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS";
402		assert!(PaymentMethod::Lnurl(LnUrl::from_str(lnurl_str).unwrap()).requires_amount());
403
404		// An invoice only requires an amount when it is amountless.
405		assert!(PaymentMethod::from(test_invoice(None)).requires_amount());
406		assert!(!PaymentMethod::from(test_invoice(Some(100_000))).requires_amount());
407
408		// Same for offers.
409		let secp = Secp256k1::new();
410		let pubkey = PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&[43; 32]).unwrap());
411		let amountless_offer = OfferBuilder::new(pubkey).build().unwrap();
412		assert!(PaymentMethod::Offer(amountless_offer).requires_amount());
413		let offer = OfferBuilder::new(pubkey).amount_msats(100_000).build().unwrap();
414		assert!(!PaymentMethod::Offer(offer).requires_amount());
415
416		// For custom methods we can't tell, so we never require an amount.
417		assert!(!PaymentMethod::Custom("custom".into()).requires_amount());
418	}
419
420	#[test]
421	fn test_serialization() {
422		let ark_str = "tark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2y3zqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yscufs5u";
423		let serialised = r#"{"type":"ark","value":"tark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2y3zqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yscufs5u"}"#;
424		let ark_method = PaymentMethod::Ark(ark::Address::from_str(ark_str).unwrap());
425		assert_eq!(serde_json::to_string(&ark_method).unwrap(), serialised);
426		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), ark_method);
427
428		let bitcoin_str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
429		let serialised = r#"{"type":"bitcoin","value":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"}"#;
430		let bitcoin_method = PaymentMethod::Bitcoin(bitcoin::Address::from_str(bitcoin_str).unwrap());
431		assert_eq!(serde_json::to_string(&bitcoin_method).unwrap(), serialised);
432		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), bitcoin_method);
433
434		let script_str = "6a0474657374"; // OP_RETURN, push 4 bytes with the string "test"
435		let serialised = r#"{"type":"output-script","value":"6a0474657374"}"#;
436		let output_method = PaymentMethod::OutputScript(bitcoin::ScriptBuf::from_hex(script_str).unwrap());
437		assert_eq!(serde_json::to_string(&output_method).unwrap(), serialised);
438		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), output_method);
439
440		let invoice_str = "lntbs100u1p5j0x82sp5d0rwfh7tgrrlwsegy9rx3tzpt36cqwjqza5x4wvcjxjzscfaf6jspp5d8q7354dg3p8h0kywhqq5dq984r8f5en98hf9ln85ug0w8fx6hhsdqqcqzpc9qyysgqyk54v7tpzprxll7e0jyvtxcpgwttzk84wqsfjsqvcdtq47zt2wssxsmtjhz8dka62mdnf9jafhu3l4cpyfnsx449v4wstrwzzql2w5qqs8uh7p";
441		let serialised = r#"{"type":"invoice","value":"lntbs100u1p5j0x82sp5d0rwfh7tgrrlwsegy9rx3tzpt36cqwjqza5x4wvcjxjzscfaf6jspp5d8q7354dg3p8h0kywhqq5dq984r8f5en98hf9ln85ug0w8fx6hhsdqqcqzpc9qyysgqyk54v7tpzprxll7e0jyvtxcpgwttzk84wqsfjsqvcdtq47zt2wssxsmtjhz8dka62mdnf9jafhu3l4cpyfnsx449v4wstrwzzql2w5qqs8uh7p"}"#;
442		let invoice_method = PaymentMethod::Invoice(Bolt11Invoice::from_str(invoice_str).unwrap().into());
443		assert_eq!(serde_json::to_string(&invoice_method).unwrap(), serialised);
444		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), invoice_method);
445
446		let offer_str = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj";
447		let serialised = r#"{"type":"offer","value":"lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah82ru5rdpnpj"}"#;
448		let offer_method = PaymentMethod::Offer(Offer::from_str(offer_str).unwrap());
449		assert_eq!(serde_json::to_string(&offer_method).unwrap(), serialised);
450		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), offer_method);
451
452		let lnaddr_str = "byte@second.tech";
453		let serialised = r#"{"type":"lightning-address","value":"byte@second.tech"}"#;
454		let lnaddr_method = PaymentMethod::LightningAddress(LightningAddress::from_str(lnaddr_str).unwrap());
455		assert_eq!(serde_json::to_string(&lnaddr_method).unwrap(), serialised);
456		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), lnaddr_method);
457
458		let lnurl_str = "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS";
459		let serialised = r#"{"type":"lnurl","value":"lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hxqurzepexejxxepnxscrvwfnv9nxzcn9xq6xyefhvgcxxcmyxymnserxfq5fns"}"#;
460		let lnurl_method = PaymentMethod::Lnurl(LnUrl::from_str(lnurl_str).unwrap());
461		assert_eq!(serde_json::to_string(&lnurl_method).unwrap(), serialised);
462		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), lnurl_method);
463
464		let custom_str = "THIS IS AN EXAMPLE OF A CUSTOM STRING";
465		let serialised = r#"{"type":"custom","value":"THIS IS AN EXAMPLE OF A CUSTOM STRING"}"#;
466		let custom_method = PaymentMethod::Custom(String::from(custom_str));
467		assert_eq!(serde_json::to_string(&custom_method).unwrap(), serialised);
468		assert_eq!(serde_json::from_str::<PaymentMethod>(serialised).unwrap(), custom_method);
469	}
470}