@@ -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