# Aerosoft POS — Multi-Tenant SaaS: Architecture Plan

> Working document. The 3 live apps (Curry in a Hurry `11.aerosoft.app`, Desi Kitchen
> `desikitchen.aerosoft.app`, Dumplings Delights `dumplings.aerosoft.app`) stay running
> **as-is** as demos. This plan is for the **new greenfield multi-tenant SaaS**.

## Decision summary
- **Stack: all-TypeScript** — Next.js (frontend) + dedicated TS API (NestJS) + PostgreSQL + Redis.
- **Not** Python/Django or PHP/Laravel: your team is TS-native, your frontend is already Next.js, and one language gives shared types end-to-end. Language is not the scaling bottleneck — architecture is.
- **Greenfield build**, but port ~60-70% of proven domain logic from FoodExpress (19 backend modules already exist, incl. a **PlugnPay** payments integration).
- **Multi-tenant from commit #1** — never repeat the one-container-per-restaurant + SQLite model.

## 1. Product
Any restaurant self-signs-up and gets a branded ordering site, POS/till, kitchen display,
delivery/rider flow, loyalty, WhatsApp, and admin — multi-tenant on shared infra, billed by subscription.

## 2. Stack
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js (App Router) + React + TS + Tailwind + TanStack Query | SSR + PWA for customer & POS |
| Backend API | NestJS (TS) + Prisma | Structure/DI/guards; FoodExpress services port in as functions. Lighter alt: Fastify-modular |
| Database | PostgreSQL 16 + PgBouncer | Multi-tenant, indexed, pooled |
| Cache/Queue/Bus | Redis (BullMQ + pub/sub) | Async jobs + horizontal SSE fan-out |
| Real-time | SSE over Redis pub/sub (WS where 2-way) | Live orders/tracking across N replicas |
| Auth | JWT access+refresh, RBAC, per-tenant | |
| Payments | Gateway abstraction: PlugnPay, FAC, WiPay, Fygaro, COD | Cayman/Caribbean-first (Stripe not available in KY) |
| Infra | Docker -> managed Postgres/Redis + 2+ API replicas behind LB | Scale-ready from day 1 |
| Observability | pino + OpenTelemetry + Grafana/Prometheus + Sentry | |
| CI/CD | GitHub Actions (lint, typecheck, test, migrate, deploy) | |

## 3. Architecture
```
 [ Customer PWA ] [ POS/Till ] [ Kitchen ] [ Rider ] [ Tenant Admin ] [ Super-admin ]
        \______________\__________\_________/________________/______________/
                                   |
                         [ Load Balancer + TLS + custom domains ]
                                   |
                    [ API replicas x N  (NestJS, stateless) ]
                       |                 |               \
                  [ PgBouncer ]      [ Redis ]        [ BullMQ Worker ]
                       |          (pub/sub+cache+queue)   |  webhooks, WhatsApp/SMS,
              [ PostgreSQL ]                              |  print dispatch, payouts
             (tenant_id + RLS)                    [ PlugnPay/FAC/WiPay | WhatsApp | Cloud print ]
```

## 4. Multi-tenancy (the core new thing)
Shared DB + shared schema + `tenant_id` on every row (row-level). Cheapest for thousands of
small tenants; single migration set; easy cross-tenant analytics.
- **Isolation, defense-in-depth:** (1) Prisma middleware auto-injects `tenant_id` from auth
  context on every query; (2) Postgres **Row-Level Security (RLS)** as a hard backstop.
- Hierarchy: `Tenant` (brand) -> `Location` (venue, multi-branch) -> Users/Menu/Orders scoped by both.
- Escalation: promote a whale tenant to its own schema/DB later, no app rewrite.

Core tables:
```
Tenant(id, name, slug, plan, status, createdAt)
Domain(id, tenantId, host, type[subdomain|custom], tlsStatus)
Subscription(id, tenantId, plan, status, currentPeriodEnd, gateway, gatewayRef)
User(id, tenantId, role[OWNER|MANAGER|STAFF|KITCHEN|RIDER], ...)
Location(id, tenantId, name, hours, address, printerConfig)
-- every domain table (Order, MenuItem, Customer, Reward...) carries tenantId (+ locationId)
```

## 5. Modules  (PORT = lift from FoodExpress, NEW = SaaS layer)
| Module | Status |
|---|---|
| auth, users, RBAC | PORT (add tenant scoping) |
| menu/catalog, cart, orders (+ fixed state machine) | PORT |
| pos (till), kitchen display | PORT |
| restaurant/venue settings, hours, floor | PORT |
| rider / delivery flow | PORT |
| payments (PlugnPay started) + payouts | PORT (extend abstraction) |
| rewards/loyalty, offers, reviews | PORT |
| notifications (WhatsApp/SMS), printing (SDP) | PORT |
| admin, diagnostics, uploads, sales/reports | PORT |
| Tenant provisioning + self-serve onboarding | NEW |
| SaaS subscription billing + plans + feature flags | NEW |
| Custom domains + automated TLS | NEW |
| Super-admin (provision, suspend, usage, support) | NEW |
| Usage metering (orders/mo, SMS) for plan limits | NEW |

## 6. Payments — gateway abstraction (Cayman-first)
One interface, many providers; each tenant configures theirs. COD always available.
```ts
interface PaymentGateway {
  id: 'plugnpay' | 'fac' | 'wipay' | 'fygaro' | 'cod';
  createCharge(input: ChargeInput): Promise<ChargeResult>;   // hosted page or token
  verifyWebhook(req: RawRequest): WebhookEvent;              // signature check
  refund(chargeRef: string, amount: Cents): Promise<RefundResult>;
  capabilities: { hostedPage: boolean; tokenization: boolean; refunds: boolean; currencies: Currency[] };
}
```
- Idempotency on charge-create (order-scoped key); webhook signature verify + replay protection.
- Minimize PCI scope: prefer hosted-payment-page/redirect (PlugnPay, FAC) — card data never hits our servers.
- Per-tenant credentials encrypted at rest (KMS / pgcrypto).
- Reuse the PlugnPay work already in FoodExpress; add FAC/WiPay/Fygaro behind the same interface.

## 7. Real-time & jobs
- SSE endpoints (`/orders/stream`, `/order/:id/stream`) fed by Redis pub/sub -> works across all replicas.
- BullMQ workers: payment webhooks, WhatsApp/SMS, kitchen print, payouts, partner/POS webhooks, marketing.

## 8. Scaling (built in)
- Stateless API -> N replicas behind LB.
- PgBouncer (transaction pooling) so replicas don't exhaust Postgres connections.
- Redis for shared state/pub-sub/cache/rate-limits.
- Partition hot `orders` by month if needed; read replicas for analytics.
- Validate with k6 load tests on order-create + tracking before launch.
- Infra path: single dedicated box (Compose) -> managed Postgres/Redis + container platform (Fly/Render/ECS).

## 9. Security & compliance
- Tenant isolation (Prisma filter + RLS), RBAC, per-tenant rate limits.
- Secrets encrypted; audit log of admin/payment actions; HTTPS everywhere; automated per-domain certs.
- PCI: hosted-page gateways to stay out of SAQ-D. Daily Postgres backups + restore drills.

## 10. Roadmap
| Phase | Deliverable | Effort |
|---|---|---|
| 0 Foundation | Nest+Prisma+PG+Redis skeleton; tenant model+RLS; auth/RBAC; CI/CD; observability | 2-3 wks |
| 1 Core commerce | Port menu/cart/orders/POS/KDS tenant-scoped; SSE via Redis | 3-4 wks |
| 2 Payments + delivery | Gateway abstraction (PlugnPay live, COD, +1), rider flow, notifications/print | 3-4 wks |
| 3 SaaS layer | Onboarding, subscription billing, plans/flags, custom domains, super-admin | 3-4 wks |
| 4 Loyalty/marketing | Rewards, offers, WhatsApp campaigns, reports | 2 wks |
| 5 Harden + launch | Load tests, security review, backup/restore drills, pilot 3-5 venues | 2-3 wks |

~4 months to a scalable, sellable v1 with one focused TS team (because ~60-70% of domain logic is ported).

## 11. Open decisions (fill in to finalize)
1. Team size + TS comfort (NestJS vs lighter Fastify).
2. Payment gateways at launch (PlugnPay + which of FAC/WiPay/Fygaro?).
3. Domains: subdomains only (`venue.yourpos.app`) or custom domains at launch?
4. Pricing/plans shape (flat monthly? per-order? tiers?) -> drives billing module.
5. Host target: dedicated VPS initially, or managed (Fly/Render/AWS) now?

## Reference: what exists today
- FoodExpress: Next.js 16 + Express/TS + Prisma + PostgreSQL 16 + Redis + BullMQ + Zod; PlugnPay+COD payments; 19 modules; daily DB backups.
- 3 POS demos: Node/Express (JS) + SQLite, one container per restaurant (the model we are replacing).
- All on one 8 vCPU / 31 GB VPS today (fine for demos, not for a 1000-tenant launch).
