One booking engine, every service business.
A multi-tenant booking and business-management platform, originally built for massage therapy and generalized to serve spas, salons, legal and consulting, fitness, and home-service businesses — with a companion internal Sales & Commission CRM for the platform's own hired sales reps.
Two problems, one platform — and only one of them was the product.
Service businesses need booking, staff and resource scheduling, payments, and retention tools in one system rather than four subscriptions that don't talk to each other.
Separately, the platform operator needed to pay commission to outside sales reps for new tenant signups — accurately, without manual reconciliation, and without trusting self-reported deal numbers.
Solo full-stack,
end to end
Architecture, backend, both frontend apps, the data model, and the delivery process — owned start to finish.
Express 5 · clean architecture · manual DI · 25+ modules
PostgreSQL · Prisma · 95 models · Decimal-typed money
React · Vite · Chakra UI v3 · two SPAs
Stripe — plans, subscriptions, invoices, payments
Nx monorepo · per-app end-to-end suites
A platform, a portal,
and a CRM
Platform-versus-tenant RBAC scoping underneath every module, enforced independently at the API and the UI.
Conflict detection, recurring rules, waitlists, and staff and resource availability.
Tenant onboarding with document verification, reviewed by platform admins before activation.
Stripe-backed plans, subscriptions, invoices and payments.
Loyalty accounts, gift cards, promotions, campaigns, reviews, reminders and notifications.
An internal pipeline and payout ledger for the platform's own hired sales reps.
One engine, seven verticals
A generalized Industry model lets the same booking engine run a massage studio and a home-cleaning business without forking code per vertical.
- Conflict detection across staff, resources and rooms.
- Recurring appointment rules and waitlists.
- Availability modeled per staff member and per resource.
- Industry-specific behaviour configured, not branched.
Commission you
can't argue with
A Won lead converts into a real tenant signup — no fabricated deal data. Commission is then calculated automatically from that tenant's first real subscription payment.
Every commission record carries a Pending → Confirmed → Clawed-back lifecycle with a 30-day clawback window. Rep dashboards are locked down so a rep only ever sees their own numbers — never the underlying rate or basis amount.
Where the money
meets the code
All commission math runs on Prisma's Decimal type end to end, never a plain JS number.
Why — It closes the exact floating-point rounding gap that would undermine trust in every number a rep sees.
Tracing the obvious data path for payment amounts turned up dead code — nothing had ever written to that table in production.
Why — Re-routed the commission trigger onto the signal that was actually reliable, instead of building a feature on a broken assumption.
Commission carries a Pending / Confirmed / Clawed-back lifecycle with a 30-day window, even though payout execution itself was out of scope.
Why — Cheap to add before reps see numbers, expensive to retrofit after.
Permissions are enforced independently at the API and the UI layer, verified with live evidence rather than narrated test claims.
Why — Reps only ever see their own numbers — never the underlying rate or the basis amount.
A generalized Industry model lets the same booking engine run a massage studio and a home-cleaning business.
Why — Adding a vertical is configuration, not a code branch to maintain forever.