Skip to content

Commit f935996

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 d76cbde commit f935996

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
@@ -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`.
@@ -292,6 +294,32 @@ impl Refund {
292294
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
293295
}
294296

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

0 commit comments

Comments
 (0)