Skip to content

Commit 7857ceb

Browse files
committed
Builder for creating invoices for refunds
Add a builder for creating invoices for a refund and required fields. Other settings are optional and duplicative settings will override previous settings. Building produces a semantically valid `invoice` message for the refund, which then can be signed with the key associated with the provided signing pubkey.
1 parent 819da3a commit 7857ceb

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

lightning/src/offers/invoice.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef
2828
use crate::offers::offer::{Amount, OfferTlvStream, OfferTlvStreamRef};
2929
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
3030
use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef};
31-
use crate::offers::refund::RefundContents;
31+
use crate::offers::refund::{Refund, RefundContents};
3232
use crate::onion_message::BlindedPath;
3333
use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
3434

@@ -60,10 +60,6 @@ impl<'a> InvoiceBuilder<'a> {
6060
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
6161
created_at: Duration, payment_hash: PaymentHash
6262
) -> Result<Self, SemanticError> {
63-
if payment_paths.is_empty() {
64-
return Err(SemanticError::MissingPaths);
65-
}
66-
6763
let amount_msats = match invoice_request.amount_msats() {
6864
Some(amount_msats) => amount_msats,
6965
None => match invoice_request.contents.offer.amount() {
@@ -75,17 +71,40 @@ impl<'a> InvoiceBuilder<'a> {
7571
},
7672
};
7773

78-
Ok(Self {
79-
invreq_bytes: &invoice_request.bytes,
80-
invoice: InvoiceContents::ForOffer {
81-
invoice_request: invoice_request.contents.clone(),
82-
fields: InvoiceFields {
83-
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
84-
fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
85-
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
86-
},
74+
let contents = InvoiceContents::ForOffer {
75+
invoice_request: invoice_request.contents.clone(),
76+
fields: InvoiceFields {
77+
payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
78+
fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
79+
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
8780
},
88-
})
81+
};
82+
83+
Self::new(&invoice_request.bytes, contents)
84+
}
85+
86+
pub(super) fn for_refund(
87+
refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
88+
payment_hash: PaymentHash, signing_pubkey: PublicKey
89+
) -> Result<Self, SemanticError> {
90+
let contents = InvoiceContents::ForRefund {
91+
refund: refund.contents.clone(),
92+
fields: InvoiceFields {
93+
payment_paths, created_at, relative_expiry: None, payment_hash,
94+
amount_msats: refund.amount_msats(), fallbacks: None,
95+
features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
96+
},
97+
};
98+
99+
Self::new(&refund.bytes, contents)
100+
}
101+
102+
fn new(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
103+
if contents.fields().payment_paths.is_empty() {
104+
return Err(SemanticError::MissingPaths);
105+
}
106+
107+
Ok(Self { invreq_bytes, invoice: contents })
89108
}
90109

91110
/// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry

lightning/src/offers/refund.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ use core::convert::TryFrom;
7878
use core::str::FromStr;
7979
use core::time::Duration;
8080
use crate::io;
81+
use crate::ln::PaymentHash;
8182
use crate::ln::features::InvoiceRequestFeatures;
8283
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
84+
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
8385
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
8486
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
8587
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
@@ -202,8 +204,8 @@ impl RefundBuilder {
202204
/// [`Offer`]: crate::offers::offer::Offer
203205
#[derive(Clone, Debug)]
204206
pub struct Refund {
205-
bytes: Vec<u8>,
206-
contents: RefundContents,
207+
pub(super) bytes: Vec<u8>,
208+
pub(super) contents: RefundContents,
207209
}
208210

209211
/// The contents of a [`Refund`], which may be shared with an [`Invoice`].
@@ -296,6 +298,32 @@ impl Refund {
296298
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
297299
}
298300

301+
/// Creates an [`Invoice`] for the refund with the given required fields.
302+
///
303+
/// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
304+
/// `created_at`. The caller is expected to remember the preimage of `payment_hash` in order to
305+
/// claim a payment for the invoice.
306+
///
307+
/// The `signing_pubkey` is required to sign the invoice since refunds are not in response to an
308+
/// offer, which does have a `signing_pubkey`.
309+
///
310+
/// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It
311+
/// must contain one or more elements.
312+
///
313+
/// Errors if the request contains unknown required features.
314+
///
315+
/// [`Invoice`]: crate::offers::invoice::Invoice
316+
pub fn respond_with(
317+
&self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
318+
payment_hash: PaymentHash, signing_pubkey: PublicKey
319+
) -> Result<InvoiceBuilder, SemanticError> {
320+
if self.features().requires_unknown_bits() {
321+
return Err(SemanticError::UnknownRequiredFeatures);
322+
}
323+
324+
InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
325+
}
326+
299327
#[cfg(test)]
300328
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
301329
self.contents.as_tlv_stream()

0 commit comments

Comments
 (0)