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