16
16
//!
17
17
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
18
18
//! [`Offer`]: crate::offers::offer::Offer
19
+ //!
20
+ //! ```ignore
21
+ //! ```
19
22
20
23
use bitcoin:: blockdata:: constants:: ChainHash ;
21
24
use bitcoin:: network:: constants:: Network ;
@@ -26,10 +29,10 @@ use core::time::Duration;
26
29
use crate :: io;
27
30
use crate :: ln:: features:: InvoiceRequestFeatures ;
28
31
use 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 } ;
31
34
use crate :: offers:: parse:: { Bech32Encode , ParseError , ParsedMessage , SemanticError } ;
32
- use crate :: offers:: payer:: { PayerContents , PayerTlvStream } ;
35
+ use crate :: offers:: payer:: { PayerContents , PayerTlvStream , PayerTlvStreamRef } ;
33
36
use crate :: onion_message:: BlindedPath ;
34
37
use crate :: util:: ser:: { SeekReadable , WithoutLength , Writeable , Writer } ;
35
38
use crate :: util:: string:: PrintableString ;
@@ -39,6 +42,102 @@ use crate::prelude::*;
39
42
#[ cfg( feature = "std" ) ]
40
43
use std:: time:: SystemTime ;
41
44
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
+
42
141
/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
43
142
///
44
143
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
@@ -118,7 +217,7 @@ impl Refund {
118
217
119
218
/// A chain that the refund is valid for.
120
219
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 ( ) )
122
221
}
123
222
124
223
/// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
@@ -142,6 +241,11 @@ impl Refund {
142
241
pub fn payer_note ( & self ) -> Option < PrintableString > {
143
242
self . contents . payer_note . as_ref ( ) . map ( |payer_note| PrintableString ( payer_note. as_str ( ) ) )
144
243
}
244
+
245
+ #[ cfg( test) ]
246
+ fn as_tlv_stream ( & self ) -> RefundTlvStreamRef {
247
+ self . contents . as_tlv_stream ( )
248
+ }
145
249
}
146
250
147
251
impl AsRef < [ u8 ] > for Refund {
@@ -150,14 +254,72 @@ impl AsRef<[u8]> for Refund {
150
254
}
151
255
}
152
256
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
+
153
303
impl Writeable for Refund {
154
304
fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
155
305
WithoutLength ( & self . bytes ) . write ( writer)
156
306
}
157
307
}
158
308
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
+
159
315
type RefundTlvStream = ( PayerTlvStream , OfferTlvStream , InvoiceRequestTlvStream ) ;
160
316
317
+ type RefundTlvStreamRef < ' a > = (
318
+ PayerTlvStreamRef < ' a > ,
319
+ OfferTlvStreamRef < ' a > ,
320
+ InvoiceRequestTlvStreamRef < ' a > ,
321
+ ) ;
322
+
161
323
impl SeekReadable for RefundTlvStream {
162
324
fn read < R : io:: Read + io:: Seek > ( r : & mut R ) -> Result < Self , DecodeError > {
163
325
let payer = SeekReadable :: read ( r) ?;
0 commit comments