From 283800b1381ffd16b8226e58f056181dca996519 Mon Sep 17 00:00:00 2001 From: Bartok9 <259807879+Bartok9@users.noreply.github.com> Date: Sun, 13 Sep 2026 09:13:44 -0400 Subject: [PATCH] fix: BOLT12 arm of UnifiedPayment::send returns DuplicatePayment/PersistenceFailed errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BOLT12 arm used .map_err() + if let Ok, so DuplicatePayment and PersistenceFailed fell through to BOLT11/onchain — risking double payment. Now treats both as terminal, mirroring the BOLT11 fix in #1038. Fixes: #1060 AI-assisted: generated by Sera (Hermes Agent), verified manually. Signed-off-by: Bartok9 <259807879+Bartok9@users.noreply.github.com> --- src/payment/unified.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/payment/unified.rs b/src/payment/unified.rs index cdbfa7e7b2..32cf560241 100644 --- a/src/payment/unified.rs +++ b/src/payment/unified.rs @@ -309,14 +309,29 @@ impl UnifiedPayment { ) } else { self.bolt12_payment.send(&offer, None, None, route_parameters) - } - .map_err(|e| { - log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e); - e - }); + }; - if let Ok(payment_id) = payment_result { - return Ok(UnifiedPaymentResult::Bolt12 { payment_id }); + match payment_result { + Ok(payment_id) => { + return Ok(UnifiedPaymentResult::Bolt12 { payment_id }); + }, + // A duplicate payment already exists, so falling back to the + // BOLT11 invoice would pay the same offer a second time. + Err(Error::DuplicatePayment) => { + log_error!(self.logger, "Failed to send BOLT12 offer: DuplicatePayment. This is part of a unified payment. Aborting to avoid duplicate payment."); + return Err(Error::DuplicatePayment); + }, + // A persistence failure may occur after the Lightning payment has + // already been initiated with the ChannelManager. Falling back to + // the BOLT11 invoice in that case would double-pay, so we abort + // instead of proceeding to the next payment method. + Err(Error::PersistenceFailed) => { + log_error!(self.logger, "Failed to send BOLT12 offer: PersistenceFailed. This is part of a unified payment. Aborting to avoid a potential duplicate payment."); + return Err(Error::PersistenceFailed); + }, + Err(e) => { + log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e); + }, } }, PaymentMethod::LightningBolt11(invoice) => {