# Brightloop

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

- Status: complete
- Started: 2026-10
- Stack: Next.js, React, TypeScript, Prisma, PostgreSQL, NextAuth, Tailwind CSS, shadcn/ui, Zod, Nodemailer, Recharts, Docker Compose, Neon, Vercel
- Page: https://www.architbhuta.space/projects/brightloop
- Repository: https://github.com/ArtNayak/Brightloop

## 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.

*[Image: Run billing dialog showing how many subscriptions are due]*

*[Image: Invoice list with status tabs, filters and draft, confirmed, paid and overdue invoices]*

```text filename="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

| Theme | Risk | Check |
| --- | --- | --- |
| data integrity | The same billing period is billed twice | A unique constraint on (subscriptionId, periodStart), a compare-and-set on the next billing date, and one database transaction per subscription. |
| concurrency | A cancel races a payment on the same invoice | Guarded updates and a FOR UPDATE row lock in the invoice workflow. |
| access | A billing or invoice route is added without a role check | A static check that every billing and invoice route handler calls requireRole("ADMIN", "STAFF") as its first await. |
| data integrity | Money rounding errors, or old invoices changing when prices change | Prisma.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.

*[Image: Draft invoice detail page with confirm, cancel and print actions]*

*[Image: 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
