Repository navigation
feat(045): admin UI for credit top-ups and the opening balance - #129
Conversation
Spec 045 shipped recordCreditPurchase, deleteCreditPurchase and setCreditOpeningBalance, but no surface called them, so a Console top-up could not be recorded and the derived credit balance stayed unavailable. Adds both to the Monthly Billing Budget card on /claude: - Opening balance: amount + "as of end of" date, edit and clear. The dialog spells out that only events dated after that day move the balance (C4). - Credit top-ups: record amount, date, optional invoice link and note; list with confirm-before-delete. Rows on or before the opening date are flagged as already included in it. - getCreditPurchaseOverview loads the purchases and the recent vendor invoices not yet recorded as a purchase, offered for linking, never picked automatically (D9). - parseCreditAmountCents / parseOpeningBalanceCents turn typed dollars into integer cents; unit-tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Invoice relinking can duplicate balances, server validation is incomplete, and the controls remain inaccessible before the first usage sync.
Review effort: Balanced
Findings: 3
Open (5)
Missing unique invoice constraint allows duplicate credit purchases · New Server action accepts negative and future opening balances · New Re-recording invoices creates duplicate purchases and doubles balance · New Empty state prevents opening balance and first top-up setup · New Deletion confirmation is misleading for pre-opening top-ups · New
What changed in this PR
Adds admin controls for managing Claude prepaid-credit opening balances and top-ups.
Changes:
- Adds opening-balance and top-up dialogs.
- Loads purchases and eligible invoices for linking.
- Adds dollar-to-cents parsers and unit tests.
| File | Description |
|---|---|
src/actions/credits.ts |
Loads purchases and linkable invoices. |
src/app/claude/page.tsx |
Fetches credit-purchase data. |
src/components/claude/credit-opening-balance.tsx |
Adds opening-balance controls. |
src/components/claude/credit-purchases.tsx |
Adds top-up recording and deletion UI. |
src/components/claude/org-credits-panel.tsx |
Integrates the new controls. |
src/lib/credits.ts |
Adds amount parsers. |
tests/unit/credits.test.ts |
Tests amount parsing. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const creditPurchasesResult = creditBalance | ||
| ? await getCreditPurchaseOverview(creditBalance.toolId) | ||
| : null; |
There was a problem hiding this comment.
Not changed in this PR. The /claude empty state shows before any Anthropic cost data has been synced, and the whole dashboard, including the existing billing-budget card, was already hidden in that case. Production has synced data. Showing the billing section alongside the empty state would be a separate change to the page; leaving that to the reviewer.
🤖 Addressed by Claude Code
… checks Addresses review on #129: - Unique index on credit_purchases.invoice_id (migration 0033) so two admins can't link the same invoice and double-count the balance; the violation maps to the same friendly error as the pre-check. - linkCreditPurchaseInvoice attaches an invoice to an existing top-up. The old advice to re-record once the invoice arrived would have left two rows and doubled the cash. The list now offers "Link invoice" on unlinked rows. - setCreditOpeningBalance rejects negative balances and future as-of dates on the server, not only in the form (one day of slack for the admin's timezone running ahead of UTC). - The delete confirmation for a top-up on or before the opening date says the balance won't change, instead of claiming it will. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Why
Spec 045 added the server actions for prepaid Claude Console credits (
recordCreditPurchase,deleteCreditPurchase,setCreditOpeningBalance), but nothing in the UI called them. A Console top-up could not be recorded, and with no opening balance on file the derived credit balance always read "unavailable".What
Both are now on the Monthly Billing Budget card on
/claude(the page is already admin-only):Opening balance: the amount read in the Console and an "as of end of" date (not in the future), with Edit and Clear. The dialog explains contract C4: only top-ups and usage dated after that day move the balance.
Credit top-ups: record an amount, a date (defaults to today), an optional invoice and a note. The list asks for confirmation before deleting. Top-ups dated on or before the opening date are flagged as already included in it, so they don't silently fail to count.
getCreditPurchaseOverview(insrc/actions/credits.ts) loads the recorded purchases and the vendor invoices from the last 180 days that aren't already linked. Invoices are offered for linking but never chosen automatically (D9), because seat invoices and top-ups both come from "Anthropic, PBC".parseCreditAmountCentsandparseOpeningBalanceCentsturn typed dollars into integer cents. A top-up must be more than 0; an opening balance may be 0.Link invoice on a top-up recorded before its invoice arrived (
linkCreditPurchaseInvoice). This updates the existing row, so the cash is never counted twice.Migration 0033:
credit_purchases.invoice_idis now a unique index (credit_purchases_invoice_id_unique), so two admins can't link one invoice to two top-ups.setCreditOpeningBalancealso rejects negative balances and future dates on the server.No new packages.
Verification
pnpm typecheckpasses, and ESLint is clean on the changed files.tests/unit/credits.test.tspasses (13 tests, including the new parser cases).preview/045-usage-based-cost-modelNeon branch, never production:After merge
pnpm db:migrate; the build doesn't run migrations).credit_purchasesis empty there, so the unique index can't fail.🤖 Generated with Claude Code