fix(wallet): read payment status from status, not the unserialized pending #161

Merged
padreug merged 1 commit from fix/wallet-payment-status-mapping into dev 2026-09-22 21:05:44 +00:00
Owner

Every payment in the wallet history rendered as "confirmed". Pending invoices looked settled, and failed payments looked successful. The receive dialog's "Paid" indicator reads the same data, so it inherited the defect.

Cause

Payment.pending is a Python @property on the LNbits model, and LNbits pins pydantic 1.x, which never serializes properties. The field is absent from every REST and WebSocket payload, so payment.pending was always undefined, and the ternary fell through to confirmed for every row.

The payload actually carries status, one of pending / success / failed.

Verification

Against the live LNbits on this machine, a payment payload has these keys and no pending:

amount, bolt11, checking_id, created_at, expiry, extension, extra, fee,
fiat_provider, labels, memo, payment_hash, payment_request, preimage,
status, tag, time, updated_at, wallet_id, webhook, webhook_status

Creating an unpaid invoice and listing it back:

status: 'pending'
OLD mapper -> confirmed   (wrong)
NEW mapper -> pending     (correct)

Second bug, same cause

The WebSocket mapper read fee_msat, which does not exist either. The field is fee, signed millisats like amount. Live-added rows never showed a fee.

Two near-duplicate mappers had drifted apart, which is how the fields fell out of sync. loadTransactions now delegates to the single shared mapper, so there is one place to get this right.

An unrecognized status maps to pending rather than confirmed, and warns. Showing a payment as still in flight is the safe direction to be wrong.

vue-tsc --noEmit is clean.

Every payment in the wallet history rendered as "confirmed". Pending invoices looked settled, and failed payments looked successful. The receive dialog's "Paid" indicator reads the same data, so it inherited the defect. ## Cause `Payment.pending` is a Python `@property` on the LNbits model, and LNbits pins pydantic 1.x, which never serializes properties. The field is absent from every REST and WebSocket payload, so `payment.pending` was always `undefined`, and the ternary fell through to `confirmed` for every row. The payload actually carries `status`, one of `pending` / `success` / `failed`. ## Verification Against the live LNbits on this machine, a payment payload has these keys and no `pending`: ``` amount, bolt11, checking_id, created_at, expiry, extension, extra, fee, fiat_provider, labels, memo, payment_hash, payment_request, preimage, status, tag, time, updated_at, wallet_id, webhook, webhook_status ``` Creating an unpaid invoice and listing it back: ``` status: 'pending' OLD mapper -> confirmed (wrong) NEW mapper -> pending (correct) ``` ## Second bug, same cause The WebSocket mapper read `fee_msat`, which does not exist either. The field is `fee`, signed millisats like `amount`. Live-added rows never showed a fee. Two near-duplicate mappers had drifted apart, which is how the fields fell out of sync. `loadTransactions` now delegates to the single shared mapper, so there is one place to get this right. An unrecognized status maps to `pending` rather than `confirmed`, and warns. Showing a payment as still in flight is the safe direction to be wrong. `vue-tsc --noEmit` is clean.
Every payment in the history list rendered as "confirmed" — pending
invoices looked settled and failed payments looked successful. The
receive dialog's "Paid" indicator inherited the same defect.

`Payment.pending` is a Python `@property` on the LNbits model, and
LNbits pins pydantic 1.x, which never serializes properties. The field
is therefore absent from every REST and WebSocket payload, so
`payment.pending` was always `undefined` — falsy — and the ternary fell
through to "confirmed" for every row.

Verified against a live LNbits instance: the payload carries `status`
("pending" | "success" | "failed") and no `pending` key. A freshly
created, unpaid invoice now maps to "pending" where it previously
mapped to "confirmed".

The same drift hid a second bug: the WebSocket mapper read `fee_msat`,
which does not exist either. The field is `fee`, signed millisats like
`amount`, so live-added rows never showed a fee.

Both mappers existed as near-duplicates that had diverged, which is how
the two fields fell out of sync in the first place. `loadTransactions`
now delegates to the single shared mapper.
padreug deleted branch fix/wallet-payment-status-mapping 2026-09-22 21:05:45 +00:00
Sign in to join this conversation.
No description provided.