Skip to content

feat(billing): add credit top-ups + billing UI cleanup#926

Merged
harshithmullapudi merged 1 commit into
mainfrom
feat/credit-topups
Jul 3, 2026
Merged

feat(billing): add credit top-ups + billing UI cleanup#926
harshithmullapudi merged 1 commit into
mainfrom
feat/credit-topups

Conversation

@harshithmullapudi

Copy link
Copy Markdown
Member

Summary

  • Adds a persistent, non-expiring top-up credit bucket: users on any plan can buy credits in $10 increments (min $10, $10 → 1000 credits) via Stripe Checkout in `payment` mode.
  • Adds new `UserUsage.topupCredits` column and `CreditTopup` audit table (migration included). Monthly resets never touch top-up credits.
  • Credit spend is now monthly first, then top-up, then overage — atomic via compound optimistic `updateMany`. Applied consistently across `reserveCredits`, `reconcileCredits`, `deductCredits`, `hasCredits`.
  • Webhook: new `checkout.session.completed` handler for top-up sessions, idempotent by `topupId` + status.
  • UI fixes on `/settings/billing`:
    • New "Add credits" card with $10/$20/$50/$100 preset chips + custom whole-dollar input.
    • Top-up balance surfaces on the Credits card; recent top-ups list.
    • Credits progress bar now fills with % used (was inverted before).
    • Plan card no longer has an empty right side — "View all plans" moved inside.
    • Kills the noisy $0.00 "pending" invoice rows on FREE plans (skip `BillingHistory` when `totalAmount = 0`; also filter legacy rows from the loader).

Files touched

  • `packages/database/prisma/schema.prisma` + `migrations/20260703120000_add_credit_topup/`
  • `apps/webapp/app/config/billing.server.ts` (topup config + `validateTopupAmount`)
  • `apps/webapp/app/services/stripe.server.ts` (`createTopupCheckoutSession`)
  • `apps/webapp/app/routes/api.webhooks.stripe.tsx` (checkout webhook + $0 skip)
  • `apps/webapp/app/jobs/credit_utils.ts` (reserve/reconcile — 2-bucket)
  • `apps/webapp/app/trigger/utils/utils.ts` (deduct/hasCredits/resetMonthly — 2-bucket)
  • `apps/webapp/app/services/billing.server.ts` (usage summary + reset)
  • `apps/webapp/app/routes/settings.billing.tsx` (UI)

Test plan

  • Apply migration (`20260703120000_add_credit_topup`) — verify `UserUsage.topupCredits` and `CreditTopup` table exist.
  • Add `checkout.session.completed` to the Stripe webhook endpoint if scoped explicitly.
  • On a FREE workspace: click a $10 chip in `/settings/billing`, complete Stripe test-card checkout, verify `CreditTopup.status = completed`, `UserUsage.topupCredits` incremented by 1000, and the "Recent top-ups" list shows the row.
  • Drain `availableCredits` to 0 via a few chat/search ops; next op should debit `topupCredits` (not overage). Check that PRO/MAX still overage after both buckets are dry.
  • Run the monthly reset trigger against the workspace — confirm `availableCredits` resets to plan quota but `topupCredits` is unchanged.
  • Custom-input UX: values below $10 or non-multiples of 10 should be blocked client-side and rejected server-side.
  • Verify Invoices list no longer shows $0.00 pending rows (legacy rows hidden, no new ones created).

🤖 Generated with Claude Code

Introduce a persistent, non-expiring "top-up" credit bucket users can
purchase from any plan in $10 increments (min $10, $10 → 1000 credits).
Spend order is monthly credits first, then top-up, then overage. Monthly
resets never touch the top-up bucket.

- schema: UserUsage.topupCredits + new CreditTopup audit table with
  Stripe checkout/payment_intent uniqueness and per-workspace/user
  indexes; migration included.
- Stripe: createTopupCheckoutSession opens a payment-mode Checkout with
  dynamic price_data and metadata; webhook handles
  checkout.session.completed idempotently by topupId + status.
- credit spend: reserveCredits/reconcileCredits/deductCredits/hasCredits
  updated to drain monthly first, then topup, using compound optimistic
  locking on updateMany.
- billing UI: new "Add credits" card with $10/$20/$50/$100 chips and a
  custom whole-dollar input; top-up balance surfaces next to monthly on
  the Credits card; recent top-ups list.
- fixes: skip BillingHistory rows when totalAmount = 0 (this was
  producing the noisy $0.00 "pending" invoice rows on FREE plans), and
  hide legacy $0 rows from the invoices loader; corrected the Credits
  progress bar so it fills with % used instead of % remaining; reworked
  the Plan card so the right side isn't empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@harshithmullapudi harshithmullapudi merged commit f71dfa2 into main Jul 3, 2026
1 check passed
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