What changed, and why it matters
This commit fixes a minor user-interface timing bug in Electrum's mobile/QML invoice view. When paying a Lightning invoice, two internal notifications could arrive in different orders, causing the screen to briefly or incorrectly show 'This invoice was already paid' instead of 'Paid!'. The fix tracks whether the payment was started in the current session so the right message is always shown. There is no security issue here—only a confusing label.
No security action required. Treat as a normal UI bug fix.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch addresses a race condition in electrum/gui/qml/qeinvoice.py between on_event_payment_succeeded and on_event_invoice_status callbacks. Previously, depending on callback ordering, update_userinfo() could set the userinfo string to the generic already-paid message rather than the session-specific ‘Paid!’ message. A new _paid_in_this_session flag is introduced, reset in set_effective_invoice(), set to True when pay_lightning_invoice() is invoked, and checked in update_userinfo() to prefer the ‘Paid!’ label when the invoice was paid during the current session. This is purely a UI state correctness fix.
Changed components
electrum/gui/qml/qeinvoice.pyInspect captured patch +6 / −1
diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py
index 696506f..7069663 100644
--- a/electrum/gui/qml/qeinvoice.py
+++ b/electrum/gui/qml/qeinvoice.py
@@ -65,6 +65,7 @@ class QEInvoice(QObject, QtEventListener):
self._invoiceType = QEInvoice.Type.Invalid
self._effectiveInvoice = None # type: Optional[Invoice]
self._userinfo = ''
+ self._paid_in_this_session = False
self._lnprops = {}
self._amount = QEAmount()
self._amountOverride = QEAmount()
@@ -88,7 +89,7 @@ class QEInvoice(QObject, QtEventListener):
if wallet == self._wallet.wallet and key == self.key:
self.statusChanged.emit()
self.determine_can_pay()
- self.userinfo = _('Paid!')
+ self.update_userinfo()
@event_listener
def on_event_payment_failed(self, wallet, key, reason):
@@ -265,6 +266,7 @@ class QEInvoice(QObject, QtEventListener):
return (lnworker.lnpeermgr.get_node_alias(node_id) if lnworker else None) or node_id.hex()
def set_effective_invoice(self, invoice: Invoice):
+ self._paid_in_this_session = False
self._effectiveInvoice = invoice
if invoice is None:
@@ -333,6 +335,8 @@ class QEInvoice(QObject, QtEventListener):
if status in [PR_UNPAID, PR_FAILED]:
x, self.userinfo = self.check_can_pay_amount(amount)
+ elif status == PR_PAID and self._paid_in_this_session:
+ self.userinfo = _('Paid!')
else:
self.userinfo = userinfo_for_invoice_status(status)
@@ -389,6 +393,7 @@ class QEInvoice(QObject, QtEventListener):
raise Exception('can not pay 0 amount')
amount_msat = self.amountOverride.msatsInt
+ self._paid_in_this_session = True
self._wallet.pay_lightning_invoice(self._effectiveInvoice, amount_msat)
def get_max_spendable_onchain(self):
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.