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