ZionPlace
Multi-vendor marketplace with tiered subscriptions, atomic checkout, and automated vendor payouts across 18 database tables
54K+ lines of code
97 pages / 3 dashboards
18 DB tables (RLS)
30 server actions
Overview
Production multi-vendor marketplace where vendors manage their own stores under subscription plans, customers browse products and request custom services, and admins moderate the entire ecosystem from a single panel. Solves a real problem for Nigerian small vendors: affordable marketplace infrastructure that handles both products AND custom service requests, with built-in subscription tiers, automated payouts, and dispute resolution. Atomic multi-vendor checkout uses stock-safe RPC functions that prevent overselling under concurrent load. Paystack split payments route money to per-vendor subaccounts with platform commission deducted at transaction time. Tiered subscriptions (Starter 20 products / Growth 100 / Pro unlimited) are enforced with Drizzle ORM guards. Background automation via Vercel Cron handles abandoned-order cancellation, auto-delivery confirmation, and dispute escalation. 54,000+ lines across 425 TypeScript files, 97 pages, and 3 role-based dashboards.
Architecture Diagram
Design Decisions
- →Drizzle ORM over Prisma for type-safe SQL, a thinner bundle, and better edge compatibility with Next.js 16. Shared Zod schemas between client forms and server actions give a single source of truth for validation.
- →Supabase Row-Level Security over app-level auth checks. Access control lives at the database layer across all 18 tables, so even raw SQL access is role-scoped rather than trusting the application to filter correctly.
- →Server Actions over API routes for mutations. Colocating writes with React 19 form integration gives automatic revalidation and keeps the 30 mutation paths close to the components that use them.
- →Paystack subaccounts with payment-time splits. Each vendor gets a bank subaccount created during onboarding; the platform commission is deducted at the moment of payment rather than reconciled later at settlement.
- →Atomic checkout through stock-safe RPC functions. Inventory is decremented inside a database transaction so concurrent orders cannot oversell the same item under load.
- →Vercel Cron over an external scheduler for background automation. Abandoned-order cancellation (15 min), auto-delivery confirmation (7 days), and weekly payout batches run with zero additional infrastructure.
Deployment
Runs on Vercel (Edge + Serverless) with Supabase as the managed backend PostgreSQL with Row-Level Security, Auth, and S3-compatible storage. The Next.js 16 app (App Router, Turbopack) uses React 19 Server Actions for mutations with automatic revalidation. Payments flow through Paystack (subaccounts, splits, transfers, and webhooks), and transactional email is sent through Resend across four dedicated addresses (noreply@, orders@, payments@, billing@). Background automation runs on Vercel Cron: abandoned orders are cancelled every 15 minutes with stock restored atomically, orders shipped over 7 days ago auto-deliver on a daily 2 AM job that also escalates disputes, and vendor payouts are batch-processed weekly. Monitoring comes from Sentry plus Vercel Analytics and Speed Insights. Testing spans Vitest unit tests and Playwright end-to-end suites. Six architecture diagrams are generated as code (diagram-as-code with Python) and regenerable in a single command.
Lessons Learned
The hardest problem was atomic multi-vendor checkout. A cart can contain items from several vendors, and each line has to decrement stock without overselling when two customers check out the same item at once. Doing the stock check and decrement in application code left a race window; moving it into stock-safe Postgres RPC functions inside a transaction closed it. Paystack split payments taught me to deduct the platform commission at payment time rather than reconciling at settlement each vendor's subaccount is created during a 4-step onboarding wizard with bank verification, so the money is routed correctly the moment a charge succeeds. Enforcing subscription tiers (Starter 20 products / Growth 100 / Pro unlimited) at the Drizzle ORM layer rather than the UI meant the limit held even against direct API calls. And leaning on Supabase RLS across all 18 tables shifted authorization into the database, so a missed check in a server action couldn't leak another vendor's data. The biggest operational lesson was that background jobs need dead-simple, idempotent logic abandoned-order cancellation and auto-delivery both run on cron and must never double-process an order that changed state between runs.





