1616//!
1717//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1818//! [`Offer`]: crate::offers::offer::Offer
19+ //!
20+ //! ```ignore
21+ //! ```
1922
2023use bitcoin:: blockdata:: constants:: ChainHash ;
2124use bitcoin:: network:: constants:: Network ;
@@ -26,10 +29,10 @@ use core::time::Duration;
2629use crate :: io;
2730use crate :: ln:: features:: InvoiceRequestFeatures ;
2831use crate :: ln:: msgs:: DecodeError ;
29- use crate :: offers:: invoice_request:: InvoiceRequestTlvStream ;
30- use crate :: offers:: offer:: OfferTlvStream ;
32+ use crate :: offers:: invoice_request:: { InvoiceRequestTlvStream , InvoiceRequestTlvStreamRef } ;
33+ use crate :: offers:: offer:: { OfferTlvStream , OfferTlvStreamRef } ;
3134use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
32- use crate :: offers:: payer:: { PayerContents , PayerTlvStream } ;
35+ use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
3336use crate :: onion_message:: BlindedPath ;
3437use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
3538use crate :: util:: string:: PrintableString ;
@@ -39,6 +42,102 @@ use crate::prelude::*;
3942#[ cfg( feature = "std" ) ]
4043use std:: time:: SystemTime ;
4144
45+ /// Builds a [`Refund`] for the "offer for money" flow.
46+ ///
47+ /// See [module-level documentation] for usage.
48+ ///
49+ /// [module-level documentation]: self
50+ pub struct RefundBuilder {
51+ refund : RefundContents ,
52+ }
53+
54+ impl RefundBuilder {
55+ /// Creates a new builder for a refund using the [`Refund::payer_id`] for signing invoices. Use
56+ /// a different pubkey per refund to avoid correlating refunds.
57+ ///
58+ /// Additionally, sets the required [`Refund::description`], [`Refund::metadata`], and
59+ /// [`Refund::amount_msats`].
60+ pub fn new (
61+ description : String , metadata : Vec < u8 > , payer_id : PublicKey , amount_msats : u64
62+ ) -> Self {
63+ let refund = RefundContents {
64+ payer : PayerContents ( metadata) , metadata : None , description, absolute_expiry : None ,
65+ issuer : None , paths : None , chain : None , amount_msats,
66+ features : InvoiceRequestFeatures :: empty ( ) , payer_id, payer_note : None ,
67+ } ;
68+
69+ RefundBuilder { refund }
70+ }
71+
72+ /// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
73+ /// already passed is valid and can be checked for using [`Refund::is_expired`].
74+ ///
75+ /// Successive calls to this method will override the previous setting.
76+ pub fn absolute_expiry ( mut self , absolute_expiry : Duration ) -> Self {
77+ self . refund . absolute_expiry = Some ( absolute_expiry) ;
78+ self
79+ }
80+
81+ /// Sets the [`Refund::issuer`].
82+ ///
83+ /// Successive calls to this method will override the previous setting.
84+ pub fn issuer ( mut self , issuer : String ) -> Self {
85+ self . refund . issuer = Some ( issuer) ;
86+ self
87+ }
88+
89+ /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected
90+ /// by private channels or if [`Refund::payer_id`] is not a public node id.
91+ ///
92+ /// Successive calls to this method will add another blinded path. Caller is responsible for not
93+ /// adding duplicate paths.
94+ pub fn path ( mut self , path : BlindedPath ) -> Self {
95+ self . refund . paths . get_or_insert_with ( Vec :: new) . push ( path) ;
96+ self
97+ }
98+
99+ /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not
100+ /// called, [`Network::Bitcoin`] is assumed.
101+ ///
102+ /// Successive calls to this method will override the previous setting.
103+ pub fn chain ( mut self , network : Network ) -> Self {
104+ self . refund . chain = Some ( ChainHash :: using_genesis_block ( network) ) ;
105+ self
106+ }
107+
108+ /// Sets the [`Refund::features`].
109+ ///
110+ /// Successive calls to this method will override the previous setting.
111+ #[ cfg( test) ]
112+ pub fn features ( mut self , features : InvoiceRequestFeatures ) -> Self {
113+ self . refund . features = features;
114+ self
115+ }
116+
117+ /// Sets the [`Refund::payer_note`].
118+ ///
119+ /// Successive calls to this method will override the previous setting.
120+ pub fn payer_note ( mut self , payer_note : String ) -> Self {
121+ self . refund . payer_note = Some ( payer_note) ;
122+ self
123+ }
124+
125+ /// Builds a [`Refund`] after checking for valid semantics.
126+ pub fn build ( mut self ) -> Result < Refund , SemanticError > {
127+ if self . refund . chain ( ) == self . refund . implied_chain ( ) {
128+ self . refund . chain = None ;
129+ }
130+
131+ let mut bytes = Vec :: new ( ) ;
132+ self . refund . write ( & mut bytes) . unwrap ( ) ;
133+
134+ Ok ( Refund {
135+ bytes,
136+ contents : self . refund ,
137+ } )
138+ }
139+ }
140+
42141/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
43142///
44143/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
@@ -118,7 +217,7 @@ impl Refund {
118217
119218 /// A chain that the refund is valid for.
120219 pub fn chain ( & self ) -> ChainHash {
121- self . contents . chain . unwrap_or_else ( || ChainHash :: using_genesis_block ( Network :: Bitcoin ) )
220+ self . contents . chain . unwrap_or_else ( || self . contents . implied_chain ( ) )
122221 }
123222
124223 /// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
@@ -142,6 +241,11 @@ impl Refund {
142241 pub fn payer_note ( & self ) -> Option < PrintableString > {
143242 self . contents . payer_note . as_ref ( ) . map ( |payer_note| PrintableString ( payer_note. as_str ( ) ) )
144243 }
244+
245+ #[ cfg( test) ]
246+ fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
247+ self . contents . as_tlv_stream ( )
248+ }
145249}
146250
147251impl AsRef < [ u8 ] > for Refund {
@@ -150,14 +254,72 @@ impl AsRef<[u8]> for Refund {
150254 }
151255}
152256
257+ impl RefundContents {
258+ fn chain ( & self ) -> ChainHash {
259+ self . chain . unwrap_or_else ( || self . implied_chain ( ) )
260+ }
261+
262+ pub fn implied_chain ( & self ) -> ChainHash {
263+ ChainHash :: using_genesis_block ( Network :: Bitcoin )
264+ }
265+
266+ pub ( super ) fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
267+ let payer = PayerTlvStreamRef {
268+ metadata : Some ( & self . payer . 0 ) ,
269+ } ;
270+
271+ let offer = OfferTlvStreamRef {
272+ chains : None ,
273+ metadata : self . metadata . as_ref ( ) ,
274+ currency : None ,
275+ amount : None ,
276+ description : Some ( & self . description ) ,
277+ features : None ,
278+ absolute_expiry : self . absolute_expiry . map ( |duration| duration. as_secs ( ) ) ,
279+ paths : self . paths . as_ref ( ) ,
280+ issuer : self . issuer . as_ref ( ) ,
281+ quantity_max : None ,
282+ node_id : None ,
283+ } ;
284+
285+ let features = {
286+ if self . features == InvoiceRequestFeatures :: empty ( ) { None }
287+ else { Some ( & self . features ) }
288+ } ;
289+
290+ let invoice_request = InvoiceRequestTlvStreamRef {
291+ chain : self . chain . as_ref ( ) ,
292+ amount : Some ( self . amount_msats ) ,
293+ features,
294+ quantity : None ,
295+ payer_id : Some ( & self . payer_id ) ,
296+ payer_note : self . payer_note . as_ref ( ) ,
297+ } ;
298+
299+ ( payer, offer, invoice_request)
300+ }
301+ }
302+
153303impl Writeable for Refund {
154304 fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
155305 WithoutLength ( & self . bytes ) . write ( writer)
156306 }
157307}
158308
309+ impl Writeable for RefundContents {
310+ fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
311+ self . as_tlv_stream ( ) . write ( writer)
312+ }
313+ }
314+
159315type RefundTlvStream = ( PayerTlvStream , OfferTlvStream , InvoiceRequestTlvStream ) ;
160316
317+ type RefundTlvStreamRef < ' a > = (
318+ PayerTlvStreamRef < ' a > ,
319+ OfferTlvStreamRef < ' a > ,
320+ InvoiceRequestTlvStreamRef < ' a > ,
321+ ) ;
322+
161323impl SeekReadable for RefundTlvStream {
162324 fn read < R : io:: Read + io:: Seek > ( r : & mut R ) -> Result < Self , DecodeError > {
163325 let payer = SeekReadable :: read ( r) ?;
0 commit comments