@@ -264,20 +264,16 @@ mod tests {
264264 }
265265}
266266
267- #[ cfg_attr( feature = "uniffi" , uniffi:: export) ]
268267impl Bolt11Payment {
269- /// Send a payment given an invoice.
270- ///
271- /// If `route_parameters` are provided they will override the default as well as the
272- /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
273- pub fn send (
274- & self , invoice : & Bolt11Invoice , route_parameters : Option < RouteParametersConfig > ,
268+ fn send_internal (
269+ & self , invoice : & LdkBolt11Invoice , amount_msat : Option < u64 > ,
270+ route_parameters : Option < RouteParametersConfig > ,
271+ declared_total_mpp_value_msat_override : Option < u64 > , invalid_amount_log : & ' static str ,
275272 ) -> Result < PaymentId , Error > {
276273 if !* self . is_running . read ( ) . expect ( "lock" ) {
277274 return Err ( Error :: NotRunning ) ;
278275 }
279276
280- let invoice = maybe_deref ( invoice) ;
281277 let payment_hash = invoice. payment_hash ( ) ;
282278 let payment_id = PaymentId ( invoice. payment_hash ( ) . 0 ) ;
283279 if let Some ( payment) = self . payment_store . get ( & payment_id) {
@@ -293,23 +289,34 @@ impl Bolt11Payment {
293289 route_parameters. or ( self . config . route_parameters ) . unwrap_or_default ( ) ;
294290 let retry_strategy = Retry :: Timeout ( LDK_PAYMENT_RETRY_TIMEOUT ) ;
295291 let payment_secret = Some ( * invoice. payment_secret ( ) ) ;
292+ let payment_amount_msat = match amount_msat. or_else ( || invoice. amount_milli_satoshis ( ) ) {
293+ Some ( amount_msat) => amount_msat,
294+ None => {
295+ log_error ! ( self . logger, "{}" , invalid_amount_log) ;
296+ return Err ( Error :: InvalidInvoice ) ;
297+ } ,
298+ } ;
296299
297300 let optional_params = OptionalBolt11PaymentParams {
298301 retry_strategy,
299302 route_params_config,
303+ declared_total_mpp_value_msat_override,
300304 ..Default :: default ( )
301305 } ;
302306 match self . channel_manager . pay_for_bolt11_invoice (
303307 invoice,
304308 payment_id,
305- None ,
309+ amount_msat ,
306310 optional_params,
307311 ) {
308312 Ok ( ( ) ) => {
309313 let payee_pubkey = invoice. recover_payee_pub_key ( ) ;
310- let amt_msat =
311- invoice. amount_milli_satoshis ( ) . expect ( "invoice amount should be set" ) ;
312- log_info ! ( self . logger, "Initiated sending {}msat to {}" , amt_msat, payee_pubkey) ;
314+ log_info ! (
315+ self . logger,
316+ "Initiated sending {} msat to {}" ,
317+ payment_amount_msat,
318+ payee_pubkey
319+ ) ;
313320
314321 let kind = PaymentKind :: Bolt11 {
315322 hash : payment_hash,
@@ -320,7 +327,7 @@ impl Bolt11Payment {
320327 let payment = PaymentDetails :: new (
321328 payment_id,
322329 kind,
323- invoice . amount_milli_satoshis ( ) ,
330+ Some ( payment_amount_msat ) ,
324331 None ,
325332 PaymentDirection :: Outbound ,
326333 PaymentStatus :: Pending ,
@@ -331,9 +338,7 @@ impl Bolt11Payment {
331338 Ok ( payment_id)
332339 } ,
333340 Err ( Bolt11PaymentError :: InvalidAmount ) => {
334- log_error ! ( self . logger,
335- "Failed to send payment due to the given invoice being \" zero-amount\" . Please use send_using_amount instead."
336- ) ;
341+ log_error ! ( self . logger, "{}" , invalid_amount_log) ;
337342 return Err ( Error :: InvalidInvoice ) ;
338343 } ,
339344 Err ( Bolt11PaymentError :: SendingFailed ( e) ) => {
@@ -350,7 +355,7 @@ impl Bolt11Payment {
350355 let payment = PaymentDetails :: new (
351356 payment_id,
352357 kind,
353- invoice . amount_milli_satoshis ( ) ,
358+ Some ( payment_amount_msat ) ,
354359 None ,
355360 PaymentDirection :: Outbound ,
356361 PaymentStatus :: Failed ,
@@ -363,6 +368,30 @@ impl Bolt11Payment {
363368 } ,
364369 }
365370 }
371+ }
372+
373+ #[ cfg_attr( feature = "uniffi" , uniffi:: export) ]
374+ impl Bolt11Payment {
375+ /// Send a payment given an invoice.
376+ ///
377+ /// If `route_parameters` are provided they will override the default as well as the
378+ /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
379+ pub fn send (
380+ & self , invoice : & Bolt11Invoice , route_parameters : Option < RouteParametersConfig > ,
381+ ) -> Result < PaymentId , Error > {
382+ if !* self . is_running . read ( ) . expect ( "lock" ) {
383+ return Err ( Error :: NotRunning ) ;
384+ }
385+
386+ let invoice = maybe_deref ( invoice) ;
387+ self . send_internal (
388+ invoice,
389+ None ,
390+ route_parameters,
391+ None ,
392+ "Failed to send payment due to the given invoice being \" zero-amount\" . Please use send_using_amount instead." ,
393+ )
394+ }
366395
367396 /// Send a payment given an invoice and an amount in millisatoshis.
368397 ///
@@ -391,94 +420,58 @@ impl Bolt11Payment {
391420 }
392421 }
393422
394- let payment_hash = invoice. payment_hash ( ) ;
395- let payment_id = PaymentId ( invoice. payment_hash ( ) . 0 ) ;
396- if let Some ( payment) = self . payment_store . get ( & payment_id) {
397- if payment. status == PaymentStatus :: Pending
398- || payment. status == PaymentStatus :: Succeeded
399- {
400- log_error ! ( self . logger, "Payment error: an invoice must not be paid twice." ) ;
401- return Err ( Error :: DuplicatePayment ) ;
402- }
403- }
404-
405- let route_params_config =
406- route_parameters. or ( self . config . route_parameters ) . unwrap_or_default ( ) ;
407- let retry_strategy = Retry :: Timeout ( LDK_PAYMENT_RETRY_TIMEOUT ) ;
408- let payment_secret = Some ( * invoice. payment_secret ( ) ) ;
409-
410- let optional_params = OptionalBolt11PaymentParams {
411- retry_strategy,
412- route_params_config,
413- ..Default :: default ( )
414- } ;
415- match self . channel_manager . pay_for_bolt11_invoice (
423+ self . send_internal (
416424 invoice,
417- payment_id,
418425 Some ( amount_msat) ,
419- optional_params,
420- ) {
421- Ok ( ( ) ) => {
422- let payee_pubkey = invoice. recover_payee_pub_key ( ) ;
423- log_info ! (
424- self . logger,
425- "Initiated sending {} msat to {}" ,
426- amount_msat,
427- payee_pubkey
428- ) ;
429-
430- let kind = PaymentKind :: Bolt11 {
431- hash : payment_hash,
432- preimage : None ,
433- secret : payment_secret,
434- counterparty_skimmed_fee_msat : None ,
435- } ;
426+ route_parameters,
427+ None ,
428+ "Failed to send payment due to amount given being insufficient." ,
429+ )
430+ }
436431
437- let payment = PaymentDetails :: new (
438- payment_id,
439- kind,
440- Some ( amount_msat) ,
441- None ,
442- PaymentDirection :: Outbound ,
443- PaymentStatus :: Pending ,
444- ) ;
445- self . runtime . block_on ( self . payment_store . insert ( payment) ) ?;
432+ /// Send a payment given an invoice and an amount lower than the invoice amount.
433+ ///
434+ /// This uses LDK's partial MPP support by declaring the invoice amount as the total MPP value
435+ /// while only sending `amount_msat` from this node. The receiving node must be willing to
436+ /// accept underpaying HTLCs for the payment to complete.
437+ ///
438+ /// This will fail if the invoice is a zero-amount invoice, or if the amount given is greater
439+ /// than or equal to the value required by the invoice. Use [`Self::send_using_amount`] instead
440+ /// when paying a zero-amount invoice or paying at least the invoice amount.
441+ ///
442+ /// If `route_parameters` are provided they will override the default as well as the
443+ /// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
444+ pub fn send_using_amount_underpaying (
445+ & self , invoice : & Bolt11Invoice , amount_msat : u64 ,
446+ route_parameters : Option < RouteParametersConfig > ,
447+ ) -> Result < PaymentId , Error > {
448+ if !* self . is_running . read ( ) . expect ( "lock" ) {
449+ return Err ( Error :: NotRunning ) ;
450+ }
446451
447- Ok ( payment_id)
448- } ,
449- Err ( Bolt11PaymentError :: InvalidAmount ) => {
450- log_error ! (
451- self . logger,
452- "Failed to send payment due to amount given being insufficient."
453- ) ;
454- return Err ( Error :: InvalidInvoice ) ;
455- } ,
456- Err ( Bolt11PaymentError :: SendingFailed ( e) ) => {
457- log_error ! ( self . logger, "Failed to send payment: {:?}" , e) ;
458- match e {
459- RetryableSendFailure :: DuplicatePayment => Err ( Error :: DuplicatePayment ) ,
460- _ => {
461- let kind = PaymentKind :: Bolt11 {
462- hash : payment_hash,
463- preimage : None ,
464- secret : payment_secret,
465- counterparty_skimmed_fee_msat : None ,
466- } ;
467- let payment = PaymentDetails :: new (
468- payment_id,
469- kind,
470- Some ( amount_msat) ,
471- None ,
472- PaymentDirection :: Outbound ,
473- PaymentStatus :: Failed ,
474- ) ;
452+ let invoice = maybe_deref ( invoice) ;
453+ let invoice_amount_msat = invoice. amount_milli_satoshis ( ) . ok_or_else ( || {
454+ log_error ! ( self . logger, "Failed to underpay as the given invoice is \" zero-amount\" ." ) ;
455+ Error :: InvalidInvoice
456+ } ) ?;
475457
476- self . runtime . block_on ( self . payment_store . insert ( payment) ) ?;
477- Err ( Error :: PaymentSendingFailed )
478- } ,
479- }
480- } ,
458+ if amount_msat >= invoice_amount_msat {
459+ log_error ! (
460+ self . logger,
461+ "Failed to underpay as the given amount needs to be less than the invoice amount: required less than {}msat, gave {}msat." ,
462+ invoice_amount_msat,
463+ amount_msat
464+ ) ;
465+ return Err ( Error :: InvalidAmount ) ;
481466 }
467+
468+ self . send_internal (
469+ invoice,
470+ Some ( amount_msat) ,
471+ route_parameters,
472+ Some ( invoice_amount_msat) ,
473+ "Failed to send payment due to amount given being insufficient." ,
474+ )
482475 }
483476
484477 /// Allows to attempt manually claiming payments with the given preimage that have previously
0 commit comments