/PROJECT · complete

Brightloop

Subscription management where a billing run turns active subscriptions into invoices.

On this page

Overview

A business that sells recurring plans has to create the right invoice for each customer every billing period, apply discounts and tax correctly, and never bill the same period twice. Brightloop covers that loop: subscription, invoice, payment.

How it works

The app is a Next.js App Router project with a PostgreSQL database accessed through Prisma. NextAuth handles login, and three roles (admin, staff, customer) are enforced with a requireRole() helper on API routes and a route guard in src/proxy.ts.

An active subscription moves through billing run -> DRAFT invoice -> CONFIRMED -> PAID. Staff can cancel an invoice while it is a draft, or confirmed with no payments. Payments are recorded by a separate part of the app.

Run billing dialog showing how many subscriptions are due

Invoice list with status tabs, filters and draft, confirmed, paid and overdue invoices

project.config
flow: billing run -> DRAFT -> CONFIRMED -> PAID
database: PostgreSQL via Prisma
invoice_numbers: gapless (INV-0001, INV-0002, ...)
auth: NextAuth

Where it could break

ThemeRiskCheck
data integrityThe same billing period is billed twiceA unique constraint on (subscriptionId, periodStart), a compare-and-set on the next billing date, and one database transaction per subscription.
concurrencyA cancel races a payment on the same invoiceGuarded updates and a FOR UPDATE row lock in the invoice workflow.
accessA billing or invoice route is added without a role checkA static check that every billing and invoice route handler calls requireRole(“ADMIN”, “STAFF”) as its first await.
data integrityMoney rounding errors, or old invoices changing when prices changePrisma.Decimal maths with half-up rounding, and invoice lines that snapshot prices at billing time. Seven check scripts cover unit, database, rendering and HTTP role-matrix cases.

Key features

  • Catalog management: products, variants, taxes, plans and discount rules (admin CRUD).
  • Quotation templates, subscriptions and a subscription state machine with a lifecycle job.
  • Billing run with catch-up for missed periods, discounts, tax and gapless invoice numbers (INV-0001, INV-0002, …).
  • Invoice list and detail pages, a print sheet, and an emailed invoice link.
  • Payments with overdue tracking, a finance dashboard, a customer portal, and password reset by email.
  • A health endpoint and a deployment runbook for Neon and Vercel.

Draft invoice detail page with confirm, cancel and print actions

Printable invoice sheet

Highlights

  • Built a billing run that bills each due period at most once, using a unique constraint on (subscriptionId, periodStart) and a compare-and-set on the next billing date, with one database transaction per subscription.
  • Implemented decimal money math with Prisma.Decimal (half-up rounding, per-line discounts, tax) and invoice lines that snapshot prices at billing time.
  • Wrote the invoice workflow (confirm, bulk confirm, cancel, send) with guarded updates and a FOR UPDATE row lock so a cancel cannot race a payment.
  • Added a static check that every billing and invoice route handler calls requireRole("ADMIN", "STAFF") as its first await.
  • Wrote seven check scripts covering unit, database, rendering and HTTP role-matrix cases for the billing module.

Tech stack

  • Language: TypeScript
  • Framework: Next.js 16 (App Router), React 19
  • Database: PostgreSQL via Prisma 6; Docker Compose for local Postgres
  • Auth and validation: NextAuth, bcryptjs, Zod
  • UI: Tailwind CSS 4, shadcn/ui, Recharts
  • Email: Nodemailer
  • Deployment: Neon and Vercel (documented in DEPLOYMENT.md)
  • Tooling: tsx for the check scripts, prisma db push for schema changes

/Stack

  • Next.js
  • React
  • TypeScript
  • Prisma
  • PostgreSQL
  • NextAuth
  • Tailwind CSS
  • shadcn/ui
  • Zod
  • Nodemailer
  • Recharts
  • Docker Compose
  • Neon
  • Vercel