@@ -29,8 +29,11 @@ use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
2929
3030use bip21:: de:: ParamKind ;
3131use bip21:: { DeserializationError , DeserializeParams , Param , SerializeParams } ;
32- use bitcoin:: address:: { NetworkChecked , NetworkUnchecked } ;
32+ use bitcoin:: address:: NetworkChecked ;
3333use bitcoin:: { Amount , Txid } ;
34+ use bitcoin_payment_instructions:: {
35+ amount:: Amount as BPIAmount , PaymentInstructions , PaymentMethod ,
36+ } ;
3437
3538type Uri < ' a > = bip21:: Uri < ' a , NetworkChecked , Extras > ;
3639
@@ -137,56 +140,112 @@ impl UnifiedPayment {
137140 Ok ( format_uri ( uri) )
138141 }
139142
140- /// Sends a payment given a [BIP 21] URI.
143+ /// Sends a payment given a [BIP 21] URI or [BIP 353] HRN .
141144 ///
142145 /// This method parses the provided URI string and attempts to send the payment. If the URI
143146 /// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
144147 /// If they both fail, the on-chain payment will be paid.
145148 ///
146- /// Returns a `QrPaymentResult ` indicating the outcome of the payment. If an error
149+ /// Returns a `UnifiedPaymentResult ` indicating the outcome of the payment. If an error
147150 /// occurs, an `Error` is returned detailing the issue encountered.
148151 ///
149152 /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
150- pub fn send ( & self , uri_str : & str ) -> Result < UnifiedPaymentResult , Error > {
151- let uri: bip21:: Uri < NetworkUnchecked , Extras > =
152- uri_str. parse ( ) . map_err ( |_| Error :: InvalidUri ) ?;
153-
154- let _resolver = & self . hrn_resolver ;
153+ /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
154+ pub async fn send (
155+ & self , uri_str : & str , amount_msat : Option < u64 > ,
156+ ) -> Result < UnifiedPaymentResult , Error > {
157+ let instructions = PaymentInstructions :: parse (
158+ uri_str,
159+ self . config . network ,
160+ self . hrn_resolver . as_ref ( ) ,
161+ false ,
162+ )
163+ . await
164+ . map_err ( |e| {
165+ log_error ! ( self . logger, "Failed to parse payment instructions: {:?}" , e) ;
166+ Error :: UriParameterParsingFailed
167+ } ) ?;
168+
169+ let resolved = match instructions {
170+ PaymentInstructions :: ConfigurableAmount ( instr) => {
171+ let amount = amount_msat. ok_or_else ( || {
172+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
173+ Error :: InvalidAmount
174+ } ) ?;
175+
176+ let amt = BPIAmount :: from_milli_sats ( amount) . map_err ( |e| {
177+ log_error ! ( self . logger, "Error while converting amount : {:?}" , e) ;
178+ Error :: InvalidAmount
179+ } ) ?;
180+
181+ instr. set_amount ( amt, self . hrn_resolver . as_ref ( ) ) . await . map_err ( |e| {
182+ log_error ! ( self . logger, "Failed to set amount: {:?}" , e) ;
183+ Error :: InvalidAmount
184+ } ) ?
185+ } ,
186+ PaymentInstructions :: FixedAmount ( instr) => {
187+ if let Some ( user_amount) = amount_msat {
188+ if instr. max_amount ( ) . map_or ( false , |amt| user_amount < amt. milli_sats ( ) ) {
189+ log_error ! ( self . logger, "Amount specified is less than the amount in the parsed URI. Aborting the payment." ) ;
190+ return Err ( Error :: InvalidAmount ) ;
191+ }
192+ }
193+ instr
194+ } ,
195+ } ;
155196
156- let uri_network_checked =
157- uri. clone ( ) . require_network ( self . config . network ) . map_err ( |_| Error :: InvalidNetwork ) ?;
197+ if let Some ( PaymentMethod :: LightningBolt12 ( offer) ) =
198+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt12 ( _) ) )
199+ {
200+ let offer = maybe_wrap ( offer. clone ( ) ) ;
201+ let payment_result = if let Some ( amount_msat) = amount_msat {
202+ self . bolt12_payment . send_using_amount ( & offer, amount_msat, None , None )
203+ } else {
204+ self . bolt12_payment . send ( & offer, None , None )
205+ }
206+ . map_err ( |e| {
207+ log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice." , e) ;
208+ e
209+ } ) ;
158210
159- if let Some ( offer) = uri_network_checked. extras . bolt12_offer {
160- let offer = maybe_wrap ( offer) ;
161- match self . bolt12_payment . send ( & offer, None , None ) {
162- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ,
163- Err ( e) => log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice." , e) ,
211+ if let Ok ( payment_id) = payment_result {
212+ return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ;
164213 }
165214 }
166215
167- if let Some ( invoice) = uri_network_checked. extras . bolt11_invoice {
168- let invoice = maybe_wrap ( invoice) ;
169- match self . bolt11_invoice . send ( & invoice, None ) {
170- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ,
171- Err ( e) => log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction." , e) ,
216+ if let Some ( PaymentMethod :: LightningBolt11 ( invoice) ) =
217+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt11 ( _) ) )
218+ {
219+ let invoice = maybe_wrap ( invoice. clone ( ) ) ;
220+ let payment_result = self . bolt11_invoice . send ( & invoice, None )
221+ . map_err ( |e| {
222+ log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction." , e) ;
223+ e
224+ } ) ;
225+
226+ if let Ok ( payment_id) = payment_result {
227+ return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ;
172228 }
173229 }
174230
175- let amount = match uri_network_checked. amount {
176- Some ( amount) => amount,
177- None => {
178- log_error ! ( self . logger, "No amount specified in the URI. Aborting the payment." ) ;
179- return Err ( Error :: InvalidAmount ) ;
180- } ,
181- } ;
182-
183- let txid = self . onchain_payment . send_to_address (
184- & uri_network_checked. address ,
185- amount. to_sat ( ) ,
186- None ,
187- ) ?;
188-
189- Ok ( UnifiedPaymentResult :: Onchain { txid } )
231+ if let Some ( PaymentMethod :: OnChain ( address) ) =
232+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: OnChain ( _) ) )
233+ {
234+ let amount = resolved. onchain_payment_amount ( ) . ok_or_else ( || {
235+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
236+ Error :: InvalidAmount
237+ } ) ?;
238+
239+ let amt_sats = amount. sats ( ) . map_err ( |_| {
240+ log_error ! ( self . logger, "Amount in sats returned an error. Aborting the payment." ) ;
241+ Error :: InvalidAmount
242+ } ) ?;
243+
244+ let txid = self . onchain_payment . send_to_address ( & address, amt_sats, None ) ?;
245+ return Ok ( UnifiedPaymentResult :: Onchain { txid } ) ;
246+ }
247+ log_error ! ( self . logger, "Payable methods not found in URI" ) ;
248+ Err ( Error :: PaymentSendingFailed )
190249 }
191250}
192251
@@ -313,7 +372,8 @@ impl DeserializationError for Extras {
313372
314373#[ cfg( test) ]
315374mod tests {
316- use super :: { Amount , Bolt11Invoice , Extras , Offer } ;
375+ use super :: * ;
376+ use crate :: payment:: unified:: Extras ;
317377 use bitcoin:: { address:: NetworkUnchecked , Address , Network } ;
318378 use std:: str:: FromStr ;
319379
0 commit comments