Skip to content

Let invoice senders revoke and resend#490

Merged
ralyodio merged 1 commit into
masterfrom
worktree-invoice-revoke-resend
Jul 5, 2026
Merged

Let invoice senders revoke and resend#490
ralyodio merged 1 commit into
masterfrom
worktree-invoice-revoke-resend

Conversation

@ralyodio

@ralyodio ralyodio commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Problem

A worker who sent an invoice had no way to take it back. The only mechanism to get a mistaken invoice off the table was for the payer to reject it — and after that there was no clear way for the sender to resend a corrected one.

What this adds

  • POST /api/gigs/[id]/invoice/[invoiceId]/revoke — sender-only (the worker who billed). Cancels a draft/sent/expired invoice (status='cancelled') and notifies the payer. Idempotent for already-cancelled/rejected; refuses paid.
  • Gig-page InvoiceButton — a Revoke invoice control on the worker's active invoices, plus a Resend invoice button on revoked/rejected ones that pre-fills the invoice form from the original (line items, notes, PR links, category). For sats-denominated gigs the stored amounts are USD, so prices are left blank on resend and re-entered.
  • Dashboard "Invoices Sent" tab — Revoke on active invoices and a Resend hand-off to the gig page (where the pre-filled form and wallet context live).

Resend just creates a fresh invoice through the existing create route, which already allows it — a cancelled/rejected invoice no longer counts as an open (draft/sent) one, so the retry guard doesn't block it.

Notes

  • No migrationcancelled is already an allowed status and workers already have an UPDATE RLS policy.
  • New route unit tests (4) cover: sender cancels + payer notified, non-sender blocked (403), paid refused (409), idempotent when already cancelled.

Testing

  • vitest run on the gigs API suite — 93 passed (incl. 4 new revoke tests).
  • tsc --noEmit clean; eslint clean on changed files.

🤖 Generated with Claude Code

Previously only the payer could take an invoice off the table (reject),
and the sender (worker) had no way to pull back an invoice they'd sent —
so a mistaken invoice was stuck until the payer rejected it, with no
clear resend path.

Adds:
- POST /api/gigs/[id]/invoice/[invoiceId]/revoke — sender-only; cancels a
  draft/sent/expired invoice (status='cancelled') and notifies the payer.
  Idempotent for already-cancelled/rejected; refuses paid invoices.
- Gig-page InvoiceButton: a "Revoke invoice" control on the worker's
  active invoices, and a "Resend invoice" button on revoked/rejected ones
  that pre-fills the invoice form from the original (line items, notes,
  PR links, category) so they can fix and send a fresh one.
- Dashboard "Invoices Sent" tab: Revoke on active invoices and a Resend
  hand-off to the gig page.

Resend creates a brand-new invoice via the existing create route, which
already permits it because a cancelled/rejected invoice no longer counts
as an open (draft/sent) one. No migration needed — 'cancelled' is already
an allowed status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio ralyodio marked this pull request as ready for review July 5, 2026 09:55
@ralyodio ralyodio merged commit 806c9e9 into master Jul 5, 2026
5 checks passed
@ralyodio ralyodio deleted the worktree-invoice-revoke-resend branch July 5, 2026 10:57
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.

1 participant