Skip to content

Commit e11c5ab

Browse files
storage: persist paid BOLT12 invoices in payment details
Store the paid BOLT12 invoice in BOLT12 offer and refund payment records so successful outbound payments retain the invoice in persisted payment details. This keeps invoice storage separate from payer-proof support and extends the existing BOLT12 integration coverage. AI-assisted-by: Codex
1 parent bf7713d commit e11c5ab

File tree

4 files changed

+60
-11
lines changed

4 files changed

+60
-11
lines changed

src/event.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ where
860860
offer_id,
861861
payer_note,
862862
quantity,
863+
bolt12_invoice: None,
863864
};
864865

865866
let payment = PaymentDetails::new(
@@ -1073,12 +1074,14 @@ where
10731074
debug_assert!(false, "payment_id should always be set.");
10741075
return Ok(());
10751076
};
1077+
let bolt12_invoice = bolt12_invoice.map(Into::into);
10761078

10771079
let update = PaymentDetailsUpdate {
10781080
hash: Some(Some(payment_hash)),
10791081
preimage: Some(Some(payment_preimage)),
10801082
fee_paid_msat: Some(fee_paid_msat),
10811083
status: Some(PaymentStatus::Succeeded),
1084+
bolt12_invoice: Some(bolt12_invoice.clone()),
10821085
..PaymentDetailsUpdate::new(payment_id)
10831086
};
10841087

@@ -1110,7 +1113,7 @@ where
11101113
payment_hash,
11111114
payment_preimage: Some(payment_preimage),
11121115
fee_paid_msat,
1113-
bolt12_invoice: bolt12_invoice.map(Into::into),
1116+
bolt12_invoice,
11141117
};
11151118

11161119
match self.event_queue.add_event(event).await {

src/payment/bolt12.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::sync::{Arc, RwLock};
1414
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1515

1616
use lightning::blinded_path::message::BlindedMessagePath;
17+
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
1718
use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId};
1819
use lightning::ln::outbound_payment::Retry;
1920
use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity};
@@ -154,6 +155,7 @@ impl Bolt12Payment {
154155
offer_id: offer.id(),
155156
payer_note: payer_note.map(UntrustedString),
156157
quantity,
158+
bolt12_invoice: None,
157159
};
158160
let payment = PaymentDetails::new(
159161
payment_id,
@@ -179,6 +181,7 @@ impl Bolt12Payment {
179181
offer_id: offer.id(),
180182
payer_note: payer_note.map(UntrustedString),
181183
quantity,
184+
bolt12_invoice: None,
182185
};
183186
let payment = PaymentDetails::new(
184187
payment_id,
@@ -314,6 +317,7 @@ impl Bolt12Payment {
314317
offer_id: offer.id(),
315318
payer_note: payer_note.map(UntrustedString),
316319
quantity,
320+
bolt12_invoice: None,
317321
};
318322
let payment = PaymentDetails::new(
319323
payment_id,
@@ -339,6 +343,7 @@ impl Bolt12Payment {
339343
offer_id: offer.id(),
340344
payer_note: payer_note.map(UntrustedString),
341345
quantity,
346+
bolt12_invoice: None,
342347
};
343348
let payment = PaymentDetails::new(
344349
payment_id,
@@ -444,6 +449,7 @@ impl Bolt12Payment {
444449
secret: None,
445450
payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
446451
quantity: refund.quantity(),
452+
bolt12_invoice: Some(LdkPaidBolt12Invoice::Bolt12Invoice(invoice.clone()).into()),
447453
};
448454

449455
let payment = PaymentDetails::new(
@@ -514,6 +520,7 @@ impl Bolt12Payment {
514520
secret: None,
515521
payer_note: payer_note.map(|note| UntrustedString(note)),
516522
quantity,
523+
bolt12_invoice: None,
517524
};
518525
let payment = PaymentDetails::new(
519526
payment_id,

src/payment/store.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use std::time::{Duration, SystemTime, UNIX_EPOCH};
99

1010
use bitcoin::{BlockHash, Txid};
11+
#[cfg(not(feature = "uniffi"))]
12+
use lightning::events::PaidBolt12Invoice;
1113
use lightning::ln::channelmanager::PaymentId;
1214
use lightning::ln::msgs::DecodeError;
1315
use lightning::offers::offer::OfferId;
@@ -20,6 +22,8 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
2022
use lightning_types::string::UntrustedString;
2123

2224
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
25+
#[cfg(feature = "uniffi")]
26+
use crate::ffi::PaidBolt12Invoice;
2327
use crate::hex_utils;
2428

2529
/// Represents a payment.
@@ -267,6 +271,18 @@ impl StorableObject for PaymentDetails {
267271
update_if_necessary!(self.fee_paid_msat, fee_paid_msat_opt);
268272
}
269273

274+
if let Some(ref bolt12_invoice_opt) = update.bolt12_invoice {
275+
match self.kind {
276+
PaymentKind::Bolt12Offer { ref mut bolt12_invoice, .. } => {
277+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
278+
},
279+
PaymentKind::Bolt12Refund { ref mut bolt12_invoice, .. } => {
280+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
281+
},
282+
_ => {},
283+
}
284+
}
285+
270286
if let Some(skimmed_fee_msat) = update.counterparty_skimmed_fee_msat {
271287
match self.kind {
272288
PaymentKind::Bolt11Jit { ref mut counterparty_skimmed_fee_msat, .. } => {
@@ -428,6 +444,8 @@ pub enum PaymentKind {
428444
///
429445
/// This will always be `None` for payments serialized with version `v0.3.0`.
430446
quantity: Option<u64>,
447+
/// The BOLT12 invoice associated with the payment, once available.
448+
bolt12_invoice: Option<PaidBolt12Invoice>,
431449
},
432450
/// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`].
433451
///
@@ -448,6 +466,8 @@ pub enum PaymentKind {
448466
///
449467
/// This will always be `None` for payments serialized with version `v0.3.0`.
450468
quantity: Option<u64>,
469+
/// The BOLT12 invoice associated with the payment, once available.
470+
bolt12_invoice: Option<PaidBolt12Invoice>,
451471
},
452472
/// A spontaneous ("keysend") payment.
453473
Spontaneous {
@@ -482,6 +502,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
482502
(3, quantity, option),
483503
(4, secret, option),
484504
(6, offer_id, required),
505+
(8, bolt12_invoice, option),
485506
},
486507
(8, Spontaneous) => {
487508
(0, hash, required),
@@ -493,6 +514,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
493514
(2, preimage, option),
494515
(3, quantity, option),
495516
(4, secret, option),
517+
(6, bolt12_invoice, option),
496518
}
497519
);
498520

@@ -555,6 +577,7 @@ pub(crate) struct PaymentDetailsUpdate {
555577
pub direction: Option<PaymentDirection>,
556578
pub status: Option<PaymentStatus>,
557579
pub confirmation_status: Option<ConfirmationStatus>,
580+
pub bolt12_invoice: Option<Option<PaidBolt12Invoice>>,
558581
pub txid: Option<Txid>,
559582
}
560583

@@ -571,30 +594,39 @@ impl PaymentDetailsUpdate {
571594
direction: None,
572595
status: None,
573596
confirmation_status: None,
597+
bolt12_invoice: None,
574598
txid: None,
575599
}
576600
}
577601
}
578602

579603
impl From<&PaymentDetails> for PaymentDetailsUpdate {
580604
fn from(value: &PaymentDetails) -> Self {
581-
let (hash, preimage, secret) = match value.kind {
582-
PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
583-
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
584-
PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret),
585-
PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret),
586-
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None),
587-
_ => (None, None, None),
605+
let (hash, preimage, secret, bolt12_invoice) = match &value.kind {
606+
PaymentKind::Bolt11 { hash, preimage, secret, .. } => {
607+
(Some(*hash), *preimage, *secret, None)
608+
},
609+
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => {
610+
(Some(*hash), *preimage, *secret, None)
611+
},
612+
PaymentKind::Bolt12Offer { hash, preimage, secret, bolt12_invoice, .. } => {
613+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
614+
},
615+
PaymentKind::Bolt12Refund { hash, preimage, secret, bolt12_invoice, .. } => {
616+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
617+
},
618+
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(*hash), *preimage, None, None),
619+
_ => (None, None, None, None),
588620
};
589621

590622
let (confirmation_status, txid) = match &value.kind {
591623
PaymentKind::Onchain { status, txid, .. } => (Some(*status), Some(*txid)),
592624
_ => (None, None),
593625
};
594626

595-
let counterparty_skimmed_fee_msat = match value.kind {
627+
let counterparty_skimmed_fee_msat = match &value.kind {
596628
PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => {
597-
Some(counterparty_skimmed_fee_msat)
629+
Some(*counterparty_skimmed_fee_msat)
598630
},
599631
_ => None,
600632
};
@@ -610,6 +642,7 @@ impl From<&PaymentDetails> for PaymentDetailsUpdate {
610642
direction: Some(value.direction),
611643
status: Some(value.status),
612644
confirmation_status,
645+
bolt12_invoice,
613646
txid,
614647
}
615648
}

tests/integration_tests_rust.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,12 +1115,14 @@ async fn simple_bolt12_send_receive() {
11151115
offer_id,
11161116
quantity: ref qty,
11171117
payer_note: ref note,
1118+
bolt12_invoice: ref invoice,
11181119
} => {
11191120
assert!(hash.is_some());
11201121
assert!(preimage.is_some());
11211122
assert_eq!(offer_id, offer.id());
11221123
assert_eq!(&expected_quantity, qty);
11231124
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1125+
assert!(invoice.is_some());
11241126
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11251127
// API currently doesn't allow to do that.
11261128
},
@@ -1182,12 +1184,14 @@ async fn simple_bolt12_send_receive() {
11821184
offer_id,
11831185
quantity: ref qty,
11841186
payer_note: ref note,
1187+
bolt12_invoice: ref invoice,
11851188
} => {
11861189
assert!(hash.is_some());
11871190
assert!(preimage.is_some());
11881191
assert_eq!(offer_id, offer.id());
11891192
assert_eq!(&expected_quantity, qty);
11901193
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1194+
assert!(invoice.is_some());
11911195
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11921196
// API currently doesn't allow to do that.
11931197
hash.unwrap()
@@ -1255,11 +1259,13 @@ async fn simple_bolt12_send_receive() {
12551259
secret: _,
12561260
quantity: ref qty,
12571261
payer_note: ref note,
1262+
bolt12_invoice: ref invoice,
12581263
} => {
12591264
assert!(hash.is_some());
12601265
assert!(preimage.is_some());
12611266
assert_eq!(&expected_quantity, qty);
1262-
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0)
1267+
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1268+
assert!(invoice.is_some());
12631269
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
12641270
// API currently doesn't allow to do that.
12651271
},

0 commit comments

Comments
 (0)