fix: sync buyer_invoice to the paid invoice on successful retry#866
fix: sync buyer_invoice to the paid invoice on successful retry#866grunch wants to merge 1 commit into
Conversation
When a payout to a buyer fails and they run /setinvoice, the retry pays a new invoice stored only on the pending payment, while order.buyer_invoice kept pointing at the original (failed) invoice. Orders that ended up SUCCESS via a retry therefore recorded an invoice that was never paid, breaking reconciliation and auditing against the node. Set order.buyer_invoice = pending.payment_request in the payment-confirmed block of attemptPendingPayments so the order faithfully records the invoice that was actually paid. Doing it on confirmation (not in /setinvoice) preserves the double-payment defense that relies on the old invoice to detect an in-flight HTLC. Also log the real payment hash (payment.id) instead of pending.hash, which stores the hold invoice hash and made the "Invoice with hash ... paid" log misleading. Adds a regression spec covering both the successful-retry and failed-payment paths. Closes #864. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X5uNqd26cC88p5HVqErRDU
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughModifies attemptPendingPayments to update order.buyer_invoice with the pending payment's payment_request on successful retry confirmation, and to log payment.id instead of pending.hash. Adds a new regression test file covering both successful and failed payment retry scenarios. ChangesPending payment retry fix and tests
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Luquitasjeffrey
left a comment
There was a problem hiding this comment.
It is a good idea to persist which invoice was actually paid in the order.
I disagree with the idea of erasing the first buyer invoice, i think it will be optimally to persist the invoice paid (according to the bot) to facilitate concilliation, but I dont like the idea of the original invoice to be lost. Debugging can get harder
| // on the pending payment, while order.buyer_invoice still points at the | ||
| // original (failed) invoice. Sync it here so the order is a faithful | ||
| // record of the payment (needed for reconciliation/auditing). See #864. | ||
| order.buyer_invoice = pending.payment_request; |
There was a problem hiding this comment.
Original buyer_invoice is lost permanently.
Instead what I would do is to persist the paid invoice in a new Order field 'buyer_invoice_paid'.
With that approach, conciliation is easier but also no data is lost if more traceabillity and debigging may be required
|
I think we should close this PR in favor of #876 |
Closes #864.
Problem
When a payout to a buyer fails and the buyer runs
/setinvoice, the retry pays a new invoice that is stored only on thePendingPayment(payment_request), whileorder.buyer_invoicekeeps pointing at the original, failed invoice. So any order that ended upSUCCESSvia a retry recorded an invoice that was never paid — the real payment lived only inpendingpayments.payment_request.This breaks reconciliation and auditing against the node (verifying the
SUCCESSby the payment hash oforder.buyer_invoiceyields a false "payment not found"), and misleads anyone inspecting the order.Fix
In the
payment.confirmed_atblock ofattemptPendingPayments(jobs/pending_payments.ts), set:so the order faithfully records the invoice that was actually paid.
Doing it on confirmation (and not in
/setinvoice) is deliberate: it preserves the double-payment defense atjobs/pending_payments.ts:41(isPendingOldPayment), which needs the old invoice around to detect a previous HTLC still in flight. Touching the field only on the success path does not interfere with that logic.Also logs the real payment hash (
payment.id) instead ofpending.hash— the latter stores the hold invoice hash, which made theInvoice with hash ... paidlog line misleading. This matches the existing log style inpayToBuyer.Scope note
This fixes the data going forward. Orders already completed via a retry keep the old inconsistency, so external DB consumers (e.g. the stats dashboard) still need their
pendingpaymentsfallback for the historical backlog. The two are complementary.Tests
Adds
tests/jobs/pending_payments.spec.tswith two cases:order.buyer_invoiceis synced to the paid invoice, statusSUCCESS, routing fee recorded;buyer_invoiceis left untouched and the pending payment is not marked paid.Both pass;
npx tsccompiles cleanly and Prettier reports no changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01X5uNqd26cC88p5HVqErRDU
Generated by Claude Code
Summary by CodeRabbit