@@ -30,8 +30,11 @@ use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
3030
3131use bip21:: de:: ParamKind ;
3232use bip21:: { DeserializationError , DeserializeParams , Param , SerializeParams } ;
33- use bitcoin:: address:: { NetworkChecked , NetworkUnchecked } ;
33+ use bitcoin:: address:: NetworkChecked ;
3434use bitcoin:: { Amount , Txid } ;
35+ use bitcoin_payment_instructions:: {
36+ amount:: Amount as BPIAmount , PaymentInstructions , PaymentMethod ,
37+ } ;
3538
3639type Uri < ' a > = bip21:: Uri < ' a , NetworkChecked , Extras > ;
3740
@@ -138,63 +141,116 @@ 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 /// If `route_parameters` are provided they will override the default as well as the
151154 /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
152155 ///
153156 /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
154- pub fn send (
155- & self , uri_str : & str , route_parameters : Option < RouteParametersConfig > ,
157+ /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
158+ pub async fn send (
159+ & self , uri_str : & str , amount_msat : Option < u64 > ,
160+ route_parameters : Option < RouteParametersConfig > ,
156161 ) -> Result < UnifiedPaymentResult , Error > {
157- let uri: bip21:: Uri < NetworkUnchecked , Extras > =
158- uri_str. parse ( ) . map_err ( |_| Error :: InvalidUri ) ?;
159-
160- let _resolver = & self . hrn_resolver ;
161-
162- let uri_network_checked =
163- uri. clone ( ) . require_network ( self . config . network ) . map_err ( |_| Error :: InvalidNetwork ) ?;
162+ let instructions = PaymentInstructions :: parse (
163+ uri_str,
164+ self . config . network ,
165+ self . hrn_resolver . as_ref ( ) ,
166+ false ,
167+ )
168+ . await
169+ . map_err ( |e| {
170+ log_error ! ( self . logger, "Failed to parse payment instructions: {:?}" , e) ;
171+ Error :: UriParameterParsingFailed
172+ } ) ?;
173+
174+ let resolved = match instructions {
175+ PaymentInstructions :: ConfigurableAmount ( instr) => {
176+ let amount = amount_msat. ok_or_else ( || {
177+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
178+ Error :: InvalidAmount
179+ } ) ?;
180+
181+ let amt = BPIAmount :: from_milli_sats ( amount) . map_err ( |e| {
182+ log_error ! ( self . logger, "Error while converting amount : {:?}" , e) ;
183+ Error :: InvalidAmount
184+ } ) ?;
185+
186+ instr. set_amount ( amt, self . hrn_resolver . as_ref ( ) ) . await . map_err ( |e| {
187+ log_error ! ( self . logger, "Failed to set amount: {:?}" , e) ;
188+ Error :: InvalidAmount
189+ } ) ?
190+ } ,
191+ PaymentInstructions :: FixedAmount ( instr) => {
192+ if let Some ( user_amount) = amount_msat {
193+ if instr. max_amount ( ) . map_or ( false , |amt| user_amount < amt. milli_sats ( ) ) {
194+ log_error ! ( self . logger, "Amount specified is less than the amount in the parsed URI. Aborting the payment." ) ;
195+ return Err ( Error :: InvalidAmount ) ;
196+ }
197+ }
198+ instr
199+ } ,
200+ } ;
164201
165- if let Some ( offer) = uri_network_checked. extras . bolt12_offer {
166- let offer = maybe_wrap ( offer) ;
202+ if let Some ( PaymentMethod :: LightningBolt12 ( offer) ) =
203+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt12 ( _) ) )
204+ {
205+ let offer = maybe_wrap ( offer. clone ( ) ) ;
206+ let payment_result = if let Some ( amount_msat) = amount_msat {
207+ self . bolt12_payment . send_using_amount ( & offer, amount_msat, None , None , route_parameters)
208+ } else {
209+ self . bolt12_payment . send ( & offer, None , None , route_parameters)
210+ }
211+ . map_err ( |e| {
212+ log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice." , e) ;
213+ e
214+ } ) ;
167215
168- match self . bolt12_payment . send ( & offer, None , None , route_parameters) {
169- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ,
170- 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) ,
216+ if let Ok ( payment_id) = payment_result {
217+ return Ok ( UnifiedPaymentResult :: Bolt12 { payment_id } ) ;
171218 }
172219 }
173220
174- if let Some ( invoice) = uri_network_checked. extras . bolt11_invoice {
175- let invoice = maybe_wrap ( invoice) ;
176-
177- match self . bolt11_invoice . send ( & invoice, route_parameters) {
178- Ok ( payment_id) => return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ,
179- 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) ,
221+ if let Some ( PaymentMethod :: LightningBolt11 ( invoice) ) =
222+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: LightningBolt11 ( _) ) )
223+ {
224+ let invoice = maybe_wrap ( invoice. clone ( ) ) ;
225+ let payment_result = self . bolt11_invoice . send ( & invoice, route_parameters)
226+ . map_err ( |e| {
227+ log_error ! ( self . logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction." , e) ;
228+ e
229+ } ) ;
230+
231+ if let Ok ( payment_id) = payment_result {
232+ return Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) ;
180233 }
181234 }
182235
183- let amount = match uri_network_checked. amount {
184- Some ( amount) => amount,
185- None => {
186- log_error ! ( self . logger, "No amount specified in the URI. Aborting the payment." ) ;
187- return Err ( Error :: InvalidAmount ) ;
188- } ,
189- } ;
190-
191- let txid = self . onchain_payment . send_to_address (
192- & uri_network_checked. address ,
193- amount. to_sat ( ) ,
194- None ,
195- ) ?;
196-
197- Ok ( UnifiedPaymentResult :: Onchain { txid } )
236+ if let Some ( PaymentMethod :: OnChain ( address) ) =
237+ resolved. methods ( ) . iter ( ) . find ( |m| matches ! ( m, PaymentMethod :: OnChain ( _) ) )
238+ {
239+ let amount = resolved. onchain_payment_amount ( ) . ok_or_else ( || {
240+ log_error ! ( self . logger, "No amount specified. Aborting the payment." ) ;
241+ Error :: InvalidAmount
242+ } ) ?;
243+
244+ let amt_sats = amount. sats ( ) . map_err ( |_| {
245+ log_error ! ( self . logger, "Amount in sats returned an error. Aborting the payment." ) ;
246+ Error :: InvalidAmount
247+ } ) ?;
248+
249+ let txid = self . onchain_payment . send_to_address ( & address, amt_sats, None ) ?;
250+ return Ok ( UnifiedPaymentResult :: Onchain { txid } ) ;
251+ }
252+ log_error ! ( self . logger, "Payable methods not found in URI" ) ;
253+ Err ( Error :: PaymentSendingFailed )
198254 }
199255}
200256
@@ -321,7 +377,8 @@ impl DeserializationError for Extras {
321377
322378#[ cfg( test) ]
323379mod tests {
324- use super :: { Amount , Bolt11Invoice , Extras , Offer } ;
380+ use super :: * ;
381+ use crate :: payment:: unified:: Extras ;
325382 use bitcoin:: { address:: NetworkUnchecked , Address , Network } ;
326383 use std:: str:: FromStr ;
327384
0 commit comments