Full-Stack Engineer (Personal project)
Fleet Billing (lite)
Subscriptions and billing for a school-bus operator
- Next.js 16
- NestJS
- Prisma
- Supabase RLS
- Stripe
- Vitest
- Playwright
- Arabic/English

The problem
A school-bus operator needs to manage buses and trips, see revenue and seat utilization, and collect subscription payments from parents without handling card data. Parents need to pick a plan for one or more children, pay online, and see when their subscription is active.
This is a portfolio project: the data is generated by a seed script, Stripe runs in test mode, and the demo signs in with one click as Operator or Parent (test card 4242 4242 4242 4242).
How it is built
Two apps in a pnpm workspace. The web app is Next.js 16 App Router on Vercel: a proxy handles locale and session refresh, Server Components render the operator stats, tables, and the parent's subscriptions, a Server Action starts checkout, and client components cover the fleet editor, the plan picker with its price preview, and login. The API is NestJS 12 deployed as a Vercel function, with an auth guard that verifies Supabase access tokens against the project's JWKS, a roles guard, class-validator DTOs, and modules for buses, trips, children, subscriptions, stats, plans, and the Stripe webhook. Data lives in Supabase Postgres through Prisma 7, with Row Level Security on subscriptions.
Checkout: the Server Action asks the API to check that the children belong to the caller, price the order, store a PENDING subscription, and create a Checkout Session with an idempotency key; the browser is redirected to Stripe. On checkout.session.completed the webhook verifies the signature against the raw body, checks that the charged amount and currency match, and marks the subscription ACTIVE for one month or one term, storing the event id in the same transaction so retries do nothing.
Browser (client components: login · fleet editor · plan picker)
│
▼
Web app (Next.js 16 App Router on Vercel)
├── proxy.ts: locale + session refresh
├── Server Components: stats · tables · my subscription
└── Server Action: startCheckout ──► Stripe Checkout
│ │
▼ Bearer JWT │ signed event
API (NestJS 12 as a Vercel function) │
├── AuthGuard (JWKS) → RolesGuard │
├── buses · trips · children · stats · plans│
├── pricing.ts (pure, unit-tested) │
└── POST /stripe/webhook ◄──────────────────┘
│
▼
Supabase (Auth + Postgres + RLS via Prisma 7)Key decisions
Prepaid periods instead of Stripe Subscriptions
Checkout runs in payment mode for one month or one term, which keeps all pricing, including sibling discounts, in tested code. Auto-renewal and the Customer Portal are left out on purpose.
Roles from a profiles table, not user metadata
The JWT proves who the caller is; the role is read from the database, new sign-ups always get Parent, and operators exist only through the seed.
API as a serverless function
Free and simple to host, at the cost of a cold start on the first request after idle, which the Suspense skeletons on the operator page cover.
What it changed for the product
- Gives an operator a live view of revenue, active and pending subscriptions, and seat utilization per trip.
- Lets parents subscribe one or more children and pay online without the product handling card data.
- Serves Arabic- and English-speaking families with a right-to-left layout.
Hard parts and how I solved them
Payments without trusting the browser
Pricing that is easy to verify
Keeping parents inside their own data
Server or client for each piece
Stack and what each part does
- Next.js 16 App Router, React 19Web app with Server Components, a Server Action, and client components
- next-intlArabic and English with right-to-left layout
- NestJS 12API with global auth and role guards and class-validator DTOs
- Prisma 7 on Supabase PostgresData layer with Row Level Security on subscriptions
- Supabase AuthSign-in; the API verifies tokens against the project's JWKS
- Stripe Checkout and webhookPayments in test mode with signed, idempotent activation
- Vitest and PlaywrightPricing, webhook, and HTTP e2e tests plus web smoke tests
- GitHub Actions, Docker, VercelCI with migrations and seed on Postgres, docker compose build, and deploys
Highlights
Screens



