1use crate::config::LDK_PAYMENT_RETRY_TIMEOUT;
13use crate::error::Error;
14use crate::logger::{log_error, log_info, LdkLogger, Logger};
15use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
16use crate::types::{ChannelManager, PaymentStore};
17
18use lightning::ln::channelmanager::{PaymentId, Retry};
19use lightning::offers::invoice::Bolt12Invoice;
20use lightning::offers::offer::{Amount, Offer, Quantity};
21use lightning::offers::parse::Bolt12SemanticError;
22use lightning::offers::refund::Refund;
23use lightning::util::string::UntrustedString;
24
25use rand::RngCore;
26
27use std::num::NonZeroU64;
28use std::sync::{Arc, RwLock};
29use std::time::{Duration, SystemTime, UNIX_EPOCH};
30
31pub struct Bolt12Payment {
38 runtime: Arc<RwLock<Option<Arc<tokio::runtime::Runtime>>>>,
39 channel_manager: Arc<ChannelManager>,
40 payment_store: Arc<PaymentStore>,
41 logger: Arc<Logger>,
42}
43
44impl Bolt12Payment {
45 pub(crate) fn new(
46 runtime: Arc<RwLock<Option<Arc<tokio::runtime::Runtime>>>>,
47 channel_manager: Arc<ChannelManager>, payment_store: Arc<PaymentStore>,
48 logger: Arc<Logger>,
49 ) -> Self {
50 Self { runtime, channel_manager, payment_store, logger }
51 }
52
53 pub fn send(
60 &self, offer: &Offer, quantity: Option<u64>, payer_note: Option<String>,
61 ) -> Result<PaymentId, Error> {
62 let rt_lock = self.runtime.read().unwrap();
63 if rt_lock.is_none() {
64 return Err(Error::NotRunning);
65 }
66 let mut random_bytes = [0u8; 32];
67 rand::thread_rng().fill_bytes(&mut random_bytes);
68 let payment_id = PaymentId(random_bytes);
69 let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
70 let max_total_routing_fee_msat = None;
71
72 let offer_amount_msat = match offer.amount() {
73 Some(Amount::Bitcoin { amount_msats }) => amount_msats,
74 Some(_) => {
75 log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency.");
76 return Err(Error::UnsupportedCurrency);
77 },
78 None => {
79 log_error!(self.logger, "Failed to send payment due to the given offer being \"zero-amount\". Please use send_using_amount instead.");
80 return Err(Error::InvalidOffer);
81 },
82 };
83
84 match self.channel_manager.pay_for_offer(
85 &offer,
86 quantity,
87 None,
88 payer_note.clone(),
89 payment_id,
90 retry_strategy,
91 max_total_routing_fee_msat,
92 ) {
93 Ok(()) => {
94 let payee_pubkey = offer.issuer_signing_pubkey();
95 log_info!(
96 self.logger,
97 "Initiated sending {}msat to {:?}",
98 offer_amount_msat,
99 payee_pubkey
100 );
101
102 let kind = PaymentKind::Bolt12Offer {
103 hash: None,
104 preimage: None,
105 secret: None,
106 offer_id: offer.id(),
107 payer_note: payer_note.map(UntrustedString),
108 quantity,
109 };
110 let payment = PaymentDetails::new(
111 payment_id,
112 kind,
113 Some(offer_amount_msat),
114 None,
115 PaymentDirection::Outbound,
116 PaymentStatus::Pending,
117 );
118 self.payment_store.insert(payment)?;
119
120 Ok(payment_id)
121 },
122 Err(e) => {
123 log_error!(self.logger, "Failed to send invoice request: {:?}", e);
124 match e {
125 Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment),
126 _ => {
127 let kind = PaymentKind::Bolt12Offer {
128 hash: None,
129 preimage: None,
130 secret: None,
131 offer_id: offer.id(),
132 payer_note: payer_note.map(UntrustedString),
133 quantity,
134 };
135 let payment = PaymentDetails::new(
136 payment_id,
137 kind,
138 Some(offer_amount_msat),
139 None,
140 PaymentDirection::Outbound,
141 PaymentStatus::Failed,
142 );
143 self.payment_store.insert(payment)?;
144 Err(Error::InvoiceRequestCreationFailed)
145 },
146 }
147 },
148 }
149 }
150
151 pub fn send_using_amount(
161 &self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
162 ) -> Result<PaymentId, Error> {
163 let rt_lock = self.runtime.read().unwrap();
164 if rt_lock.is_none() {
165 return Err(Error::NotRunning);
166 }
167
168 let mut random_bytes = [0u8; 32];
169 rand::thread_rng().fill_bytes(&mut random_bytes);
170 let payment_id = PaymentId(random_bytes);
171 let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
172 let max_total_routing_fee_msat = None;
173
174 let offer_amount_msat = match offer.amount() {
175 Some(Amount::Bitcoin { amount_msats }) => amount_msats,
176 Some(_) => {
177 log_error!(self.logger, "Failed to send payment as the provided offer was denominated in an unsupported currency.");
178 return Err(Error::UnsupportedCurrency);
179 },
180 None => amount_msat,
181 };
182
183 if amount_msat < offer_amount_msat {
184 log_error!(
185 self.logger,
186 "Failed to pay as the given amount needs to be at least the offer amount: required {}msat, gave {}msat.", offer_amount_msat, amount_msat);
187 return Err(Error::InvalidAmount);
188 }
189
190 match self.channel_manager.pay_for_offer(
191 &offer,
192 quantity,
193 Some(amount_msat),
194 payer_note.clone(),
195 payment_id,
196 retry_strategy,
197 max_total_routing_fee_msat,
198 ) {
199 Ok(()) => {
200 let payee_pubkey = offer.issuer_signing_pubkey();
201 log_info!(
202 self.logger,
203 "Initiated sending {}msat to {:?}",
204 amount_msat,
205 payee_pubkey
206 );
207
208 let kind = PaymentKind::Bolt12Offer {
209 hash: None,
210 preimage: None,
211 secret: None,
212 offer_id: offer.id(),
213 payer_note: payer_note.map(UntrustedString),
214 quantity,
215 };
216 let payment = PaymentDetails::new(
217 payment_id,
218 kind,
219 Some(amount_msat),
220 None,
221 PaymentDirection::Outbound,
222 PaymentStatus::Pending,
223 );
224 self.payment_store.insert(payment)?;
225
226 Ok(payment_id)
227 },
228 Err(e) => {
229 log_error!(self.logger, "Failed to send payment: {:?}", e);
230 match e {
231 Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment),
232 _ => {
233 let kind = PaymentKind::Bolt12Offer {
234 hash: None,
235 preimage: None,
236 secret: None,
237 offer_id: offer.id(),
238 payer_note: payer_note.map(UntrustedString),
239 quantity,
240 };
241 let payment = PaymentDetails::new(
242 payment_id,
243 kind,
244 Some(amount_msat),
245 None,
246 PaymentDirection::Outbound,
247 PaymentStatus::Failed,
248 );
249 self.payment_store.insert(payment)?;
250 Err(Error::PaymentSendingFailed)
251 },
252 }
253 },
254 }
255 }
256
257 pub fn receive(
260 &self, amount_msat: u64, description: &str, expiry_secs: Option<u32>, quantity: Option<u64>,
261 ) -> Result<Offer, Error> {
262 let absolute_expiry = expiry_secs.map(|secs| {
263 (SystemTime::now() + Duration::from_secs(secs as u64))
264 .duration_since(UNIX_EPOCH)
265 .unwrap()
266 });
267
268 let offer_builder =
269 self.channel_manager.create_offer_builder(absolute_expiry).map_err(|e| {
270 log_error!(self.logger, "Failed to create offer builder: {:?}", e);
271 Error::OfferCreationFailed
272 })?;
273
274 let mut offer =
275 offer_builder.amount_msats(amount_msat).description(description.to_string());
276
277 if let Some(qty) = quantity {
278 if qty == 0 {
279 log_error!(self.logger, "Failed to create offer: quantity can't be zero.");
280 return Err(Error::InvalidQuantity);
281 } else {
282 offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap()))
283 };
284 };
285
286 let finalized_offer = offer.build().map_err(|e| {
287 log_error!(self.logger, "Failed to create offer: {:?}", e);
288 Error::OfferCreationFailed
289 })?;
290
291 Ok(finalized_offer)
292 }
293
294 pub fn receive_variable_amount(
297 &self, description: &str, expiry_secs: Option<u32>,
298 ) -> Result<Offer, Error> {
299 let absolute_expiry = expiry_secs.map(|secs| {
300 (SystemTime::now() + Duration::from_secs(secs as u64))
301 .duration_since(UNIX_EPOCH)
302 .unwrap()
303 });
304
305 let offer_builder =
306 self.channel_manager.create_offer_builder(absolute_expiry).map_err(|e| {
307 log_error!(self.logger, "Failed to create offer builder: {:?}", e);
308 Error::OfferCreationFailed
309 })?;
310 let offer = offer_builder.description(description.to_string()).build().map_err(|e| {
311 log_error!(self.logger, "Failed to create offer: {:?}", e);
312 Error::OfferCreationFailed
313 })?;
314
315 Ok(offer)
316 }
317
318 pub fn request_refund_payment(&self, refund: &Refund) -> Result<Bolt12Invoice, Error> {
323 let invoice = self.channel_manager.request_refund_payment(refund).map_err(|e| {
324 log_error!(self.logger, "Failed to request refund payment: {:?}", e);
325 Error::InvoiceRequestCreationFailed
326 })?;
327
328 let payment_hash = invoice.payment_hash();
329 let payment_id = PaymentId(payment_hash.0);
330
331 let kind = PaymentKind::Bolt12Refund {
332 hash: Some(payment_hash),
333 preimage: None,
334 secret: None,
335 payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
336 quantity: refund.quantity(),
337 };
338
339 let payment = PaymentDetails::new(
340 payment_id,
341 kind,
342 Some(refund.amount_msats()),
343 None,
344 PaymentDirection::Inbound,
345 PaymentStatus::Pending,
346 );
347
348 self.payment_store.insert(payment)?;
349
350 Ok(invoice)
351 }
352
353 pub fn initiate_refund(
355 &self, amount_msat: u64, expiry_secs: u32, quantity: Option<u64>,
356 payer_note: Option<String>,
357 ) -> Result<Refund, Error> {
358 let mut random_bytes = [0u8; 32];
359 rand::thread_rng().fill_bytes(&mut random_bytes);
360 let payment_id = PaymentId(random_bytes);
361
362 let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64))
363 .duration_since(UNIX_EPOCH)
364 .unwrap();
365 let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
366 let max_total_routing_fee_msat = None;
367
368 let mut refund_builder = self
369 .channel_manager
370 .create_refund_builder(
371 amount_msat,
372 absolute_expiry,
373 payment_id,
374 retry_strategy,
375 max_total_routing_fee_msat,
376 )
377 .map_err(|e| {
378 log_error!(self.logger, "Failed to create refund builder: {:?}", e);
379 Error::RefundCreationFailed
380 })?;
381
382 if let Some(qty) = quantity {
383 refund_builder = refund_builder.quantity(qty);
384 }
385
386 if let Some(note) = payer_note.clone() {
387 refund_builder = refund_builder.payer_note(note);
388 }
389
390 let refund = refund_builder.build().map_err(|e| {
391 log_error!(self.logger, "Failed to create refund: {:?}", e);
392 Error::RefundCreationFailed
393 })?;
394
395 log_info!(self.logger, "Offering refund of {}msat", amount_msat);
396
397 let kind = PaymentKind::Bolt12Refund {
398 hash: None,
399 preimage: None,
400 secret: None,
401 payer_note: payer_note.map(|note| UntrustedString(note)),
402 quantity,
403 };
404 let payment = PaymentDetails::new(
405 payment_id,
406 kind,
407 Some(amount_msat),
408 None,
409 PaymentDirection::Outbound,
410 PaymentStatus::Pending,
411 );
412
413 self.payment_store.insert(payment)?;
414
415 Ok(refund)
416 }
417}