@@ -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,54 +141,110 @@ 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 uri_network_checked =
156- uri. clone ( ) . require_network ( self . config . network ) . map_err ( |_| Error :: InvalidNetwork ) ?;
157-
158- if let Some ( offer) = uri_network_checked. extras . bolt12_offer {
159- let offer = maybe_wrap ( offer) ;
160- match self . bolt12_payment . send ( & offer, None , None ) {
161- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ,
162- 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) ,
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+ true ,
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 ( ref 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_sats ( amount) . map_err ( |e| {
178+ log_error ! ( self . logger, "Error while converting amount : {:?}" , e) ;
179+ Error :: InvalidAmount
180+ } ) ?;
181+
182+ instr. clone ( ) . 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 ( ref instr) => {
188+ let instr = instr. clone ( ) ;
189+ if let Some ( user_amount) = amount_msat {
190+ if instr. ln_payment_amount ( ) . map_or ( false , |amt| user_amount < amt. milli_sats ( ) )
191+ {
192+ log_error ! ( self . logger, "Amount specified is less than the amount in the parsed URI. Aborting the payment." ) ;
193+ return Err ( Error :: InvalidAmount ) ;
194+ }
195+ }
196+ instr
197+ } ,
198+ } ;
199+
200+ if let Some ( PaymentMethod :: LightningBolt12 ( offer) ) =
201+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt12 ( _) ) )
202+ {
203+ let offer = maybe_wrap ( offer. clone ( ) ) ;
204+ let payment_result = if let Some ( amount_msat) = amount_msat {
205+ self . bolt12_payment . send_using_amount ( & offer, amount_msat, None , None )
206+ } else {
207+ self . bolt12_payment . send ( & offer, None , None )
163208 }
164- }
209+ . map_err ( |e| {
210+ log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice." , e) ;
211+ e
212+ } ) ;
165213
166- if let Some ( invoice) = uri_network_checked. extras . bolt11_invoice {
167- let invoice = maybe_wrap ( invoice) ;
168- match self . bolt11_invoice . send ( & invoice, None ) {
169- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ,
170- 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) ,
214+ if let Ok ( payment_id) = payment_result {
215+ return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ;
171216 }
172217 }
173218
174- let amount = match uri_network_checked. amount {
175- Some ( amount) => amount,
176- None => {
177- log_error ! ( self . logger, "No amount specified in the URI. Aborting the payment." ) ;
178- return Err ( Error :: InvalidAmount ) ;
179- } ,
180- } ;
181-
182- let txid = self . onchain_payment . send_to_address (
183- & uri_network_checked. address ,
184- amount. to_sat ( ) ,
185- None ,
186- ) ?;
219+ if let Some ( PaymentMethod :: LightningBolt11 ( invoice) ) =
220+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt11 ( _) ) )
221+ {
222+ let invoice = maybe_wrap ( invoice. clone ( ) ) ;
223+ let payment_result = self . bolt11_invoice . send ( & invoice, None )
224+ . map_err ( |e| {
225+ log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction." , e) ;
226+ e
227+ } ) ;
228+
229+ if let Ok ( payment_id) = payment_result {
230+ return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ;
231+ }
232+ }
187233
188- Ok ( UnifiedPaymentResult :: Onchain { txid } )
234+ if let Some ( PaymentMethod :: OnChain ( address) ) =
235+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: OnChain ( _) ) )
236+ {
237+ let amount = resolved. onchain_payment_amount ( ) . ok_or_else ( || {
238+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
239+ Error :: InvalidAmount
240+ } ) ?;
241+
242+ let txid =
243+ self . onchain_payment . send_to_address ( & address, amount. sats ( ) . unwrap ( ) , None ) ?;
244+ return Ok ( UnifiedPaymentResult :: Onchain { txid } ) ;
245+ }
246+ log_error ! ( self . logger, "Payable methods not found in URI" ) ;
247+ Err ( Error :: PaymentSendingFailed )
189248 }
190249}
191250
@@ -314,7 +373,7 @@ impl DeserializationError for Extras {
314373mod tests {
315374 use super :: * ;
316375 use crate :: payment:: unified:: Extras ;
317- use bitcoin:: { Address , Network } ;
376+ use bitcoin:: { address :: NetworkUnchecked , Address , Network } ;
318377 use std:: str:: FromStr ;
319378
320379 #[ test]
0 commit comments