Skip to content

fix(restore-session): re-send AddInvoice for failed payments on session restore#721

Merged
grunch merged 1 commit intoMostroP2P:mainfrom
codaMW:fix/restore-session-failed-payment-601
Apr 29, 2026
Merged

fix(restore-session): re-send AddInvoice for failed payments on session restore#721
grunch merged 1 commit intoMostroP2P:mainfrom
codaMW:fix/restore-session-failed-payment-601

Conversation

@codaMW
Copy link
Copy Markdown
Contributor

@codaMW codaMW commented Apr 28, 2026

Closes #601

Problem

When a Lightning payment fails after settled-hold-invoice, Mostro
sends Action::AddInvoice to request a new invoice. But if the buyer
performs restore-session before responding, Mostro returned the order
without re-sending AddInvoice. The buyer saw the order as settled with
no way to know a new invoice was needed.

Fix

After sending the restore-session response, query for orders with
failed_payment == true and status == settled-hold-invoice. For any
that match the restored order IDs, re-queue Action::AddInvoice to the
buyer.

Safety

The existing payment safety model is unchanged atomic state transitions
in payment_success and serial scheduler execution already prevent double
payments. This fix only adds a notification, it does not change the
payment flow.

Files changed

  • src/app/restore_session.rs Added AddInvoice re-queue logic after
    sending restore response

Summary by CodeRabbit

  • Bug Fixes
    • Session restore now re-queues and retries invoices for orders with failed payments that were part of the restored session.
    • Improved logging around the session restore flow to make response and retry events clearer.
    • Added server-side lookup to identify and handle failed-payment orders tied to a restored session.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@codaMW has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 55 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc9af08b-96bc-4efa-a32b-193c319a8465

📥 Commits

Reviewing files that changed from the base of the PR and between ae264b5 and e9c1f29.

📒 Files selected for processing (2)
  • src/app/restore_session.rs
  • src/db.rs

Walkthrough

The restore-session flow now forwards master_key into the response/result handler, collects restored order IDs, queries the DB for orders with failed payments tied to that master_key, and re-enqueues Action::AddInvoice for any matching restored orders while emitting logs for re-sends and query errors.

Changes

Cohort / File(s) Summary
Restore session handler
src/app/restore_session.rs
Forwards master_key into the background/result handler, collects restored order IDs, emits a "response sent" log, queries DB for failed-payment orders by master_key, and re-enqueues Action::AddInvoice for matching restored orders; adds logs for re-sends and DB query errors.
Database helper
src/db.rs
Adds pub async fn find_failed_payment_for_master_key(pool: &SqlitePool, master_key: &str) -> Result<Vec<Order>, MostroError> to fetch orders with failed_payment = true and status = 'settled-hold-invoice' filtered by master_buyer_pubkey (maps SQLx errors into service DB error).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Restore as RestoreSessionHandler
    participant DB as Database
    participant Queue as ActionQueue

    Client->>Restore: restore_session(master_key)
    Restore->>Restore: Build set of restored_order_ids
    Restore->>DB: find_failed_payment_for_master_key(master_key)
    DB-->>Restore: failed_payment_orders

    alt matching restored orders exist
        loop per order in failed_payment_orders ∩ restored_order_ids
            Restore->>Queue: Enqueue Action::AddInvoice(order_payload)
            Queue-->>Client: Deliver AddInvoice action
            Restore-->>Restore: Log re-send
        end
    end

    Restore-->>Client: send_restore_session_response(payload)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • arkanoider
  • Catrya

Poem

🐰 I hopped through logs and keys at night,
Restored lost orders back to light,
When invoices failed and silence grew,
I nudged AddInvoice — "retry anew!" 🥕✨

🚥 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 and concisely describes the main change: re-sending AddInvoice for failed payments when a session is restored.
Linked Issues check ✅ Passed The code changes directly address all objectives from issue #601: querying for failed-payment orders, re-queuing AddInvoice for restored orders matching that condition, and ensuring the buyer is notified of the need for a new invoice.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objective: adding a DB helper to find failed-payment orders and modifying restore_session to re-queue AddInvoice for matching orders.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 26 minutes and 55 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app/restore_session.rs (1)

111-113: Scope the failed-payment lookup to the restored orders.

find_failed_payment pulls the full global set of failed_payment == true / settled-hold-invoice orders and this branch filters it in memory with restored_ids. That makes every restore-session cost proportional to the system backlog instead of the restored order set. A targeted helper keyed by restored IDs or the restoring master key would keep this path bounded and reduce the stale window before AddInvoice is re-sent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/restore_session.rs` around lines 111 - 113, The lookup currently
calling find_failed_payment(&pool).await returns all failed_payment orders and
is then filtered in-memory by restored_ids; change this to a targeted DB query
that is bounded to the restored set — e.g., add a helper like
find_failed_payment_for_ids(pool: &DbPool, restored_ids: &[Uuid]) or
find_failed_payment_for_master_key(pool, master_key: &str) and replace the call
in restore_session.rs to call that helper with the restored_ids (or restoring
master key) so only matching failed/settled-hold-invoice orders are returned,
keeping the operation proportional to the restored set and enabling prompt
AddInvoice re-send.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/restore_session.rs`:
- Around line 116-123: The resend is using the stale order.get_buyer_pubkey()
instead of the restored session key, so change the enqueue logic in
restore_session to target the current session key: pass trade_key (the incoming
event.sender key used for the restore payload) to enqueue_order_msg for the
AddInvoice action, or first set order.buyer_pubkey = trade_key (and persist if
required) and then call enqueue_order_msg; update references around
enqueue_order_msg, order.get_buyer_pubkey(), trade_key, and order.buyer_pubkey
accordingly so the follow-up message is delivered to the restored session key.

---

Nitpick comments:
In `@src/app/restore_session.rs`:
- Around line 111-113: The lookup currently calling
find_failed_payment(&pool).await returns all failed_payment orders and is then
filtered in-memory by restored_ids; change this to a targeted DB query that is
bounded to the restored set — e.g., add a helper like
find_failed_payment_for_ids(pool: &DbPool, restored_ids: &[Uuid]) or
find_failed_payment_for_master_key(pool, master_key: &str) and replace the call
in restore_session.rs to call that helper with the restored_ids (or restoring
master key) so only matching failed/settled-hold-invoice orders are returned,
keeping the operation proportional to the restored set and enabling prompt
AddInvoice re-send.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6979aef3-ddf4-4fad-9fc7-f2475ed810cb

📥 Commits

Reviewing files that changed from the base of the PR and between fb4d229 and befb508.

📒 Files selected for processing (1)
  • src/app/restore_session.rs

Comment thread src/app/restore_session.rs Outdated
@codaMW codaMW force-pushed the fix/restore-session-failed-payment-601 branch from befb508 to ae264b5 Compare April 29, 2026 20:22
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/app/restore_session.rs (1)

100-144: Please add a regression test for the new resend path.

This path now has important behavior (ID intersection + failed-payment lookup + Action::AddInvoice enqueue). Add a co-located test that asserts only restored failed-payment orders are re-queued.

As per coding guidelines: "**/*.rs: Co-locate tests in their modules under mod tests blocks with descriptive names" and "src/app/**: Mirror test fixtures under src/app/ where applicable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/restore_session.rs` around lines 100 - 144, Add a co-located
regression test in restore_session.rs (under mod tests) that exercises the new
resend path: simulate a restore session that returns a set of restored orders,
stub/mock find_failed_payment_for_master_key to return a mix of failed-order
records (some whose id is in restored_ids and some not), then call the
restore-session handler and assert that enqueue_order_msg is invoked only for
the intersection IDs with Action::AddInvoice and with
Payload::Order(SmallOrder::from(...)) while non-restored failed orders are not
enqueued; use test fixtures under src/app/ to mirror any required order/dispute
setup and reference the functions/enums enqueue_restore_session_msg,
find_failed_payment_for_master_key, enqueue_order_msg, Action::AddInvoice, and
the restored_ids behavior in your assertions.
src/db.rs (1)

571-589: Add docs and a focused test for this new public query helper.

find_failed_payment_for_master_key is a non-obvious public API (buyer master key only + failed_payment + settled-hold-invoice). Please add a /// doc comment and a dedicated test in src/db.rs mod tests that verifies filtering by master_buyer_pubkey and status/flag combination.

As per coding guidelines: "**/*.rs: Document non-obvious public APIs with /// doc comments" and "Co-locate tests in their modules under mod tests blocks with descriptive names".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db.rs` around lines 571 - 589, Add a /// doc comment above the public
function find_failed_payment_for_master_key explaining that it returns orders
for a given master_buyer_pubkey where failed_payment = true and status =
"settled-hold-invoice", then add a focused async unit test in src/db.rs's mod
tests that inserts several test orders (varying master_buyer_pubkey, status, and
failed_payment), calls find_failed_payment_for_master_key with the target master
key, and asserts only the orders with the matching master_buyer_pubkey AND
failed_payment = true AND status = "settled-hold-invoice" are returned; make the
test use the module's test DB setup (tokio::test), descriptive name (e.g.,
test_find_failed_payment_for_master_key_filters_correctly), and cleanup any test
data as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/restore_session.rs`:
- Around line 117-118: The spawned background tasks must use the cloned pool
from ctx (the Arc<Pool<Sqlite>> created around line 38) instead of calling the
global get_db_pool(); update the code to pass that cloned pool into
handle_restore_session_results and send_restore_session_response (and any other
helpers that currently call get_db_pool(), e.g., the code paths touching
find_failed_payment_for_master_key) by adding an extra parameter for the pool to
their signatures and all call sites (also update callers for the occurrences
noted around lines 54–58 and 90–95), remove internal get_db_pool() usages inside
those functions, and use the passed-in pool when awaiting DB helpers so tests
using TestContextBuilder no longer rely on the global DB_POOL.

---

Nitpick comments:
In `@src/app/restore_session.rs`:
- Around line 100-144: Add a co-located regression test in restore_session.rs
(under mod tests) that exercises the new resend path: simulate a restore session
that returns a set of restored orders, stub/mock
find_failed_payment_for_master_key to return a mix of failed-order records (some
whose id is in restored_ids and some not), then call the restore-session handler
and assert that enqueue_order_msg is invoked only for the intersection IDs with
Action::AddInvoice and with Payload::Order(SmallOrder::from(...)) while
non-restored failed orders are not enqueued; use test fixtures under src/app/ to
mirror any required order/dispute setup and reference the functions/enums
enqueue_restore_session_msg, find_failed_payment_for_master_key,
enqueue_order_msg, Action::AddInvoice, and the restored_ids behavior in your
assertions.

In `@src/db.rs`:
- Around line 571-589: Add a /// doc comment above the public function
find_failed_payment_for_master_key explaining that it returns orders for a given
master_buyer_pubkey where failed_payment = true and status =
"settled-hold-invoice", then add a focused async unit test in src/db.rs's mod
tests that inserts several test orders (varying master_buyer_pubkey, status, and
failed_payment), calls find_failed_payment_for_master_key with the target master
key, and asserts only the orders with the matching master_buyer_pubkey AND
failed_payment = true AND status = "settled-hold-invoice" are returned; make the
test use the module's test DB setup (tokio::test), descriptive name (e.g.,
test_find_failed_payment_for_master_key_filters_correctly), and cleanup any test
data as needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be9474a8-6bfc-4c2b-b451-862f4b36a8eb

📥 Commits

Reviewing files that changed from the base of the PR and between befb508 and ae264b5.

📒 Files selected for processing (2)
  • src/app/restore_session.rs
  • src/db.rs

Comment thread src/app/restore_session.rs Outdated
@codaMW codaMW force-pushed the fix/restore-session-failed-payment-601 branch from ae264b5 to c04d0f8 Compare April 29, 2026 20:53
…on restore

When a buyer performs restore-session after a Lightning payment fails
in settled-hold-invoice state, Mostro was returning the order without
re-sending the AddInvoice action. The buyer had no way to know the
payment failed and that a new invoice was required.

Fix: after sending the restore-session response, query for orders with
failed_payment == true and status == settled-hold-invoice. For any
that match the restored order IDs, re-queue Action::AddInvoice to the
buyer so they receive the prompt to submit a new invoice.

The existing payment safety model is unchanged - atomic state
transitions and serial scheduler execution already prevent double
payments.

Closes MostroP2P#601
@codaMW codaMW force-pushed the fix/restore-session-failed-payment-601 branch from c04d0f8 to e9c1f29 Compare April 29, 2026 20:54
@grunch grunch merged commit 87ff37b into MostroP2P:main Apr 29, 2026
3 checks passed
@codaMW
Copy link
Copy Markdown
Contributor Author

codaMW commented Apr 29, 2026

Updated based on CodeRabbit's review:

  1. Pool threading removed get_db_pool() from spawned tasks. The pool is now passed through handle_restore_session_results and send_restore_session_response using ctx.pool_arc(), consistent with how other handlers use the context pool

  2. Targeted DB query find_failed_payment_for_master_key() queries only orders belonging to the restoring user's master_buyer_pubkey, keeping the operation bounded to the restored set

  3. Stale pubkey AddInvoice is sent to trade_pubkey (the current session key), not the stored buyer_pubkey

  4. Doc comment added /// doc comment to find_failed_payment_for_master_key explaining its purpose

  5. Tests added 3 tests for find_failed_payment_for_master_key in db.rs covering: matching order returned, different master key ignored, non-failed order ignored

@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 29, 2026

@codaMW sorry this was merged by mistake, would you mind to create the PR again? sorry again

@Catrya
Copy link
Copy Markdown
Member

Catrya commented Apr 29, 2026

@codaMW It doesn't work for me: when I do the restore-session, the order with failed payment doesn't even appear in the app.
Before restore
image

After restore
image

@codaMW
Copy link
Copy Markdown
Contributor Author

codaMW commented Apr 29, 2026

@grunch i noticed, i was about to ask you on the same because all the checks had not passed yet. @Catrya let me try to fix it, i run it locally through my terminal and the test passed, let me fix that

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.

Lost recovery after restore-session when invoice payment failed in settled-hold-invoice state

3 participants