In plain terms

Wufud (وفود — delegations) is software for Hajj and Umrah travel agencies. Each agency gets a branded storefront to publish packages, pilgrims browse and book online, staff run branches from a dashboard, and the platform operator sells subscriptions — not a cut of every seat sold.

Agencies worldwide can start on a unique subdomain (for example demo.wufud.musfiqdehan.com) and graduate to a custom domain with automated TLS at the edge. Pilgrims pay in full or on installments; finance tracks BDT and SAR vendor costs; branch managers see only their office’s bookings when RBAC demands it.

Why it matters commercially: pilgrimage travel is high-trust and high-stakes — overselling a tier, losing a payment webhook, or showing one branch another’s pilgrims destroys reputation fast. Wufud is built around those stakes, not around a generic CRUD booking form.

The problem

An agency selling sacred travel is really running:

  • A catalog of dated packages with tiers, room types and finite seats.
  • A sales network of branches that need isolated visibility but shared inventory rules.
  • A treasury that mixes online gateways, cash at a branch desk, installments, refunds and vendor payouts in more than one currency.
  • A compliance story where seat counts, passport data and payment state must stay consistent even when three pilgrims click “Book” on the last seat at once.

Spreadsheets and WhatsApp threads do not survive the first busy season. Wufud is a multi-tenant platform where each agency is a first-class tenant with isolated data, not a row filtered by company_id that someone might forget in a report.

Isolation by schema, not by hope

The platform schema (public) holds tenants, domains, plans, subscriptions, platform roles and signup flows. Each agency’s operational data lives in t_<slug> — a dedicated PostgreSQL schema selected per request from the hostname (platform marketing host, tenant subdomain, or verified custom domain) via async request context before MikroORM runs queries.

For the business, that is the difference between “we are careful with WHERE clauses” and “the other agency’s tables are not addressable from this connection”. Custom domains and white-label storefronts follow naturally: the edge routes by host, the API resolves tenant, the ORM sets em.schema.

Seat integrity under real concurrency

Package tiers track total, confirmed and held seats. Creating a booking locks the tier row, verifies availability, creates the booking and hold, and increments held seats in one transaction. PostgreSQL also enforces seats_confirmed + seats_held <= seats_total. Confirmation, cancellation and hold expiry take row locks so late webhooks cannot resurrect seats that were already released.

Integration tests deliberately race bookings for the last seat and concurrent hold expiry — the kind of failures that only appear during Hajj registration windows.

Payments that survive the real world

Agencies enable SSLCommerz and Stripe (plus stub/manual paths in development). Webhook handling locks the payment attempt, treats a successful attempt as immutable, validates amount and currency against the booking, and commits receipt updates in the same transaction. Duplicate or out-of-order events are absorbed by attempt state rather than by hoping the provider sends perfect ordering.

Branch manual payments separate who recorded cash from who approved it, with request keys to avoid double submission. POS supports product and service sales and refunds for ancillary items (Ihram, bags, tags) without a separate till system.

Who uses which surface

Actor What they get
Platform operator Tenant lifecycle, feature flags, subscription billing, platform admin at /admin on the marketing host
Agency owner Packages, staff, branches, gateways, accounts and reports on the tenant host /dashboard
Branch staff Branch-scoped RBAC — bookings and collections visible only where their role allows
Pilgrim Storefront browsing, family grouping, online or installment pay, journey tracking

Host-based routing is part of the product: wufud.musfiqdehan.com is platform marketing; demo.wufud.musfiqdehan.com is a fully seeded agency (Nur Travels) with storefront and operations. Tenant pages do not silently fall back to platform marketing when the API is unavailable — failures are explicit rather than brand-confusing.

Edge, workers and operations

Traefik terminates TLS and splits Next.js from NestJS (/api to the API). BullMQ workers handle asynchronous jobs against the same Postgres and Redis. Compose files cover local development, CI test stacks, staging and production behind GHCR images — with staging/production startup rejecting weak or identical JWT secrets.

Reporting aggregates in PostgreSQL with grouped joins; the README documents honest limits at very large scale and the incremental rollups, caching and read-replica path forward — architecture writing that treats “5 million users” as a design review, not a marketing bullet.

The result

  • Agencies launch in minutes on a subdomain, with optional custom domain and no per-booking commission.
  • Seats and payments stay consistent under concurrency and messy webhook delivery.
  • Branches and finance fit how Hajj operators actually collect and disburse money in Bangladesh and Saudi contexts.
  • One monorepo (pnpm workspaces, shared contracts package) keeps API and frontend types aligned.

What it demonstrates

Wufud is a greenfield TypeScript SaaS: NestJS 11, MikroORM 6, Next.js with React 19, schema-per-tenant isolation, Argon2 and JWT auth, OpenAPI-documented /api/v1, Playwright tenancy acceptance tests, and business rules captured in dedicated docs — seat locking, webhook idempotency and currency rules — so the implementation stays accountable to operators who cannot afford silent data leaks or oversold packages.