Skip to content

fix(restore): show new-invoice prompt for failed payout in settled-hold-invoice#623

Open
AndreaDiazCorreia wants to merge 1 commit into
mainfrom
feat/restore-failed-payout-invoice-prompt
Open

fix(restore): show new-invoice prompt for failed payout in settled-hold-invoice#623
AndreaDiazCorreia wants to merge 1 commit into
mainfrom
feat/restore-failed-payout-invoice-prompt

Conversation

@AndreaDiazCorreia

@AndreaDiazCorreia AndreaDiazCorreia commented Jun 18, 2026

Copy link
Copy Markdown
Member

Closes #615

When Mostro's Lightning payout to the buyer fails, the order stays at settled-hold-invoice and the daemon re-sends add-invoice. On restore, the state was rebuilt purely from the snapshot status, regressing this substate into the "released / paying sats" screen instead of the new-invoice prompt.

settled-hold-invoice is overloaded for the buyer — only the action distinguishes "sats in flight" from "payout failed". This change detects the failed-payout substate from the re-sent payment-failed/add-invoice messages and replays them so the order lands on payment-failed + add-invoice (the manual invoice prompt), matching the pre-restore state.

Changes

  • Add restoreHasFailedPayoutSignal() to detect the failed-payout substate (treats payment-failed as definitive; add-invoice only when it follows the release).
  • Rebuild the correct state for settled-hold-invoice + buyer by replaying payment-failed then add-invoice.
  • Add regression tests.

Acceptance criteria

  • Restore with a failed payout shows the new-invoice prompt, not "paying sats".
  • Happy path (settled-hold-invoice with no failed payout) still shows "released / paying sats".
  • Post-restore state matches pre-restore state.
  • Regression test: snapshot settled-hold-invoice + payment-failed/add-invoice in storage → final state payment-failed.

Verification

  • fvm flutter analyze: no issues
  • fvm flutter test: all tests passing

Summary by CodeRabbit

  • Bug Fixes

    • Corrected order state restoration when Lightning payment fails and a new invoice is required, ensuring proper replay of recovery actions during session recovery.
  • Tests

    • Added regression tests validating order restoration behavior for failed payout scenarios.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3a6769a8-62df-4f9d-9cc8-8b886939c239

📥 Commits

Reviewing files that changed from the base of the PR and between 3a07acf and 498ec58.

📒 Files selected for processing (2)
  • lib/features/restore/restore_manager.dart
  • test/features/restore/restore_failed_payout_test.dart

Walkthrough

The PR fixes a session-restore regression for buyers in the settledHoldInvoice+failed-payout substate. A new restoreHasFailedPayoutSignal helper inspects stored message history to distinguish failed payout from the normal "paying sats" path. The restore replay is updated to emit a paymentFailedaddInvoice sequence (with staggered timestamps) when that signal is detected. Regression tests cover both paths.

Changes

Failed Payout Restore Fix

Layer / File(s) Summary
restoreHasFailedPayoutSignal helper
lib/features/restore/restore_manager.dart
New top-level function that sorts stored MostroMessages by timestamp, locates the hold-release point (release/released/holdInvoicePaymentSettled), and returns true if paymentFailed is present or addInvoice appears after the release point.
Two-step buyer replay in order restore
lib/features/restore/restore_manager.dart
Replay block now builds actionsToApply; for buyer sessions where restoreHasFailedPayoutSignal is true, it substitutes the single status-derived action with paymentFailed then addInvoice. Replay messages receive incremental timestamp offsets and storage keys that include action name and index.
Regression tests
test/features/restore/restore_failed_payout_test.dart
New test file with _msg/_replay helpers; six cases assert restoreHasFailedPayoutSignal boolean correctness across message orderings, plus two integration cases verifying end state for failed-payout versus normal happy-path restore.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • BraCR10
  • Catrya

Poem

🐇 Hoppity-hop through the blockchain trail,
A payout once failed and restore would wail—
"Paying sats!" it cried, but the sats never flew,
Now paymentFailed + addInvoice rings true!
With timestamps staggered and signals in line,
The buyer's new invoice arrives just fine. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: fixing restore to show the new-invoice prompt for failed payouts in settled-hold-invoice orders.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from #615: detects failed-payout substates via restoreHasFailedPayoutSignal(), replays payment-failed/add-invoice messages, ensures post-restore state matches pre-restore, includes regression tests, and maintains happy-path behavior.
Out of Scope Changes check ✅ Passed All changes are scoped to restore logic for settled-hold-invoice with failed payouts: restore_manager.dart additions handle detection and state replay, and the new test file covers the specific failure scenario.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/restore-failed-payout-invoice-prompt

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 498ec58e05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

id: orderDetail.id,
action: replayAction,
payload: order,
timestamp: baseTimestamp + i,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Timestamp replayed actions after restored history

When historical messages were saved during the 10-second restore window, these synthetic payment-failed/add-invoice messages are stored with the order creation time, so the next OrderNotifier.sync() (which replays storage sorted by timestamp) can replay older historical actions like released after the failed-payout pair and move the buyer back to settled-hold-invoice. This makes the fix non-persistent across provider recreation/app restart; the replayed actions need timestamps after the restored history, e.g. after the max stored timestamp.

Useful? React with 👍 / 👎.

for (var i = 0; i < sorted.length; i++) {
final action = sorted[i].action;
if (action == Action.paymentFailed) return true;
if (action == Action.addInvoice && i > releaseIndex) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a post-release add-invoice signal

With releaseIndex initialized to -1, any lone add-invoice satisfies i > releaseIndex, even though add-invoice is also the normal early buyer-invoice request. During restore the code explicitly waits for historical messages but relays can deliver only that early event before the later release within the fixed window, so a happy-path settled-hold-invoice buyer can be restored as a failed payout. Treat add-invoice as a failed-payout signal only when a release/settle marker was actually seen before it, or require payment-failed for the single-message case.

Useful? React with 👍 / 👎.

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the current head and I do see one real blocking issue before merge.

Blocking issue:

  • restoreHasFailedPayoutSignal([addInvoice]) currently returns true, treating a lone add-invoice as a definitive failed-payout signal.
  • But add-invoice is not unique to the failed-payout substate; it also exists in the normal buyer flow earlier in the trade.
  • The helper comments themselves acknowledge that add-invoice is only diagnostic when it appears after hold release/settlement, yet the implementation special-cases the storage-cleared restore path by making a bare add-invoice enough to force the failed-payout rebuild.

Why this matters:

  • Restore logic is reconstructing durable state, so ambiguous evidence should not be upgraded into a deterministic substate.
  • A partial or incomplete message history that happens to contain only add-invoice would now push the order into payment-failed + add-invoice, even though that same single action can also belong to the normal happy-path buyer flow.
  • In other words, the heuristic is currently biased toward the failed-payout interpretation in a case where the evidence is not actually unique.

What I would want:

  • Only treat payment-failed as definitive on its own.
  • Treat add-invoice as a failed-payout signal only when it is ordered after a release/settled marker or when some other unambiguous failed-payout evidence exists.
  • If the restore path truly relies on storage being empty as an invariant, I would want that invariant enforced much more explicitly than “a lone add-invoice means failed payout”.

Because this can reconstruct the wrong buyer substate from ambiguous history, I think it is a real blocker for merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore shows "released / paying sats" instead of the new-invoice prompt when the LN payout failed

1 participant