Skip to content

Commit 95b6b69

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 d7e92be commit 95b6b69

File tree

2 files changed

+71
-21
lines changed

2 files changed

+71
-21
lines changed

lightning/src/offers/invoice.rs

Lines changed: 41 additions & 19 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, SeekReadable, WithoutLength, Writeable, Writer};
3434

@@ -60,14 +60,6 @@ impl<'a> InvoiceBuilder<'a> {
6060
invoice_request: &'a InvoiceRequest, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
6161
created_at: Duration, payment_hash: PaymentHash
6262
) -> Result<Self, SemanticError> {
63-
if paths.is_empty() {
64-
return Err(SemanticError::MissingPaths);
65-
}
66-
67-
if paths.len() != payinfo.len() {
68-
return Err(SemanticError::InvalidPayInfo);
69-
}
70-
7163
let amount_msats = match invoice_request.amount_msats() {
7264
Some(amount_msats) => amount_msats,
7365
None => match invoice_request.contents.offer.amount() {
@@ -79,17 +71,47 @@ impl<'a> InvoiceBuilder<'a> {
7971
},
8072
};
8173

82-
Ok(Self {
83-
bytes: &invoice_request.bytes,
84-
invoice: InvoiceContents::ForOffer {
85-
invoice_request: invoice_request.contents.clone(),
86-
fields: InvoiceFields {
87-
paths, payinfo, created_at, relative_expiry: None, payment_hash, amount_msats,
88-
fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
89-
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
90-
},
74+
let contents = InvoiceContents::ForOffer {
75+
invoice_request: invoice_request.contents.clone(),
76+
fields: InvoiceFields {
77+
paths, payinfo, created_at, relative_expiry: None, payment_hash, amount_msats,
78+
fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
79+
signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
9180
},
92-
})
81+
};
82+
83+
Self::new(&invoice_request.bytes, contents)
84+
}
85+
86+
pub(super) fn for_refund(
87+
refund: &'a Refund, paths: Vec<BlindedPath>, payinfo: Vec<BlindedPayInfo>,
88+
created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey
89+
) -> Result<Self, SemanticError> {
90+
let contents = InvoiceContents::ForRefund {
91+
refund: refund.contents.clone(),
92+
fields: InvoiceFields {
93+
paths, payinfo, 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(bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
103+
let paths = &contents.fields().paths;
104+
let payinfo = &contents.fields().payinfo;
105+
106+
if paths.is_empty() {
107+
return Err(SemanticError::MissingPaths);
108+
}
109+
110+
if paths.len() != payinfo.len() {
111+
return Err(SemanticError::InvalidPayInfo);
112+
}
113+
114+
Ok(Self { bytes, invoice: contents })
93115
}
94116

95117
/// 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
@@ -77,8 +77,10 @@ use core::convert::TryFrom;
7777
use core::str::FromStr;
7878
use core::time::Duration;
7979
use crate::io;
80+
use crate::ln::PaymentHash;
8081
use crate::ln::features::InvoiceRequestFeatures;
8182
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
83+
use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
8284
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
8385
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
8486
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
@@ -200,8 +202,8 @@ impl RefundBuilder {
200202
/// [`Offer`]: crate::offers::offer::Offer
201203
#[derive(Clone, Debug)]
202204
pub struct Refund {
203-
bytes: Vec<u8>,
204-
contents: RefundContents,
205+
pub(super) bytes: Vec<u8>,
206+
pub(super) contents: RefundContents,
205207
}
206208

207209
/// The contents of a [`Refund`], which may be shared with an `Invoice`.
@@ -291,6 +293,32 @@ impl Refund {
291293
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
292294
}
293295

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

0 commit comments

Comments
 (0)