System design
Designing Zube's Grub
How one system coordinates a public storefront, authoritative commerce, kitchen operations, customer tracking, and recovery when providers disagree.
The complete system
The public website is only one interface. The product also has a protected staff application, provider-backed checkout, operational projections, email delivery, and maintenance workers.
Select a node to jump to its explanation.
- Customer browserrenders the React storefront and sends cart intent.
- Cloudflare Workerserves assets, applies edge policy, and routes backend work.
- Next.js applicationvalidates requests and owns API, authentication, and domain logic.
- Square + Supabase + Resendhold commercial truth, durable application state, and email delivery.
- Staff application + workersoperate orders and repair delayed or failed provider synchronization.
The website is the public face of an operating system
A customer experiences Zube's Grub as a menu and an order form. The business experiences the same action as a catalog lookup, an availability decision, a payment, a kitchen task, a fulfillment transition, a notification, and a record that must remain correct when any one provider is late.
That makes the hard problem coordination. The implementation cannot allow the browser, the local database, and Square to each tell a different story about an item's price or an order's state. It also cannot turn every temporary provider outage into a duplicate charge or a customer-visible promise that the kitchen has not actually confirmed.
Start with operating constraints
The useful requirements are not a list of pages. They are the conditions that determine whether an order can be accepted and completed safely.
- Changing service
- Pickup availability depends on configured days, windows, lead time, and location rather than a permanent storefront schedule.
- Configurable food
- Variations and modifier groups need minimums, maximums, price deltas, and sold-out behavior that match the operational catalog.
- Two ordering channels
- The website owns direct pickup. DoorDash and Grubhub own their delivery transactions and send resulting orders through their Square integrations.
- Kitchen progression
- Paid orders need a controlled path from received to preparing, ready, and completed, with cancellation and refund represented separately.
- Provider latency
- Webhooks can repeat, arrive late, or be missed. A process can fail after a provider accepts a request but before the application stores the response.
- Different audiences
- Customers need a small, confirmed view. Staff need pending side effects, failed synchronization, and repair controls.
This framing immediately eliminates a tempting but fragile design: a browser-only cart that trusts static prices and writes a final order directly to a database. The server must re-establish every commercial fact before creating provider state.
One deployment, deliberately separated sources
The repository has two UI source areas. web/ is the
public Vite/React storefront. The Next.js application contains
API routes, authentication, domain services, and the protected
staff interface. A Cloudflare Worker assembles both into one
deployment without pretending they are one codebase.
/React storefront assets/api/*Next.js route handlers
/admin*Protected staff application
/auth/*Authentication callbacks
scheduled / queueReconciliation and retry work
This boundary keeps public presentation independent from the staff application's release structure while still giving the customer one origin. The Worker can apply same-origin mutation checks, request-size ceilings, durable rate limits, security headers, and explicit asset caching before a request reaches application code.
The storefront owns intent, not truth
The browser helps a customer compose an order. It can display a cached menu projection, collect a pickup choice, hold a cart, and ask Square's browser SDK to tokenize a payment method. None of those actions authorize a final price or payment.
When the customer submits, the API reloads the referenced catalog rows from the server-side menu projection. It rejects missing or sold-out items, verifies the selected variation and every modifier, enforces selection limits, checks the configured ordering window, applies pickup lead time, reserves an eligible promotion, and calculates tax and totals in integer cents.
The cart is therefore a proposal. The server is the verifier. Square is the commercial authority.
Give every important fact one owner
Many commerce bugs are ownership bugs: two systems both believe they are allowed to decide the same fact. Zube's Grub instead distinguishes provider truth, application projection, and temporary intent.
- Square
- Operational catalog, orders, payments, refunds, and fulfillment.
- Supabase
- Authentication, schedules, application projections, events, content, and retry state.
- Resend
- Email delivery and recipient-level contact preferences.
- Browser
- Temporary cart and form intent; never authoritative commercial state.
The Supabase menu cache is a projection of Square's operational catalog, not a competing product catalog. Likewise, an order status row can record a staff intent before Square confirms it, but public tracking shows only the latest provider-confirmed event.
Ordering is a two-stage protocol
Draft creation and payment confirmation are separate. This makes the Square order, server-calculated total, and payment idempotency key stable before the payment request begins.
- Submit intent.The browser sends item IDs, modifier IDs, customer details, and pickup preference.
- Revalidate.The server reloads menu and schedule state, checks constraints, and calculates integer-cent totals.
- Create the draft.Square receives an idempotent order; Supabase records a non-operational pending projection and payment key.
- Tokenize payment.The Square browser SDK returns a source token; raw card details do not become application state.
- Claim and compare.The API claims the payment attempt, retrieves the Square draft, and rejects a total mismatch.
- Capture once.Square receives the stable idempotency key. Ambiguous failures retain the claim for a same-key retry.
- Activate operations.Only captured payment makes the order visible to the kitchen; confirmation delivery becomes retryable work.
Why ambiguous failures matter
A timeout does not prove that Square rejected a payment. The request might have reached the provider while the response was lost. Releasing the claim and inventing a new key could charge again. The implementation releases a payment claim only before the provider request starts or after a definitive rejection. Timeouts, conflicts, rate limits, and malformed responses retain the original claim and key.
The kitchen sees a controlled state machine
Once payment is captured, staff move the order through an ordered workflow. The local event records the operator's intent. A corresponding Square fulfillment update then confirms that the provider reached the same state.
- Receivedcaptured payment makes the order operational
-
Preparingmaps to Square fulfillment
RESERVED -
Readymaps to
PREPARED; customer notification follows confirmation -
Completedmaps to
COMPLETED
Forward-only transitions are enforced in the database, not only hidden in the interface. An update uses the current Square order version and retries once after a version conflict. If Square already has the requested state, the retry is treated as converged rather than issuing another update.
Notifications are durable side effects
Order and catering emails do not disappear because Resend is temporarily unavailable, and email failure does not make an otherwise valid Square webhook fail. The system persists a delivery row with its workflow, payload, provider transaction ID, retry count, bounded error state, and stable idempotency key.
A tokenized claim prevents concurrent workers from sending the same delivery. Stale claims can be reclaimed. The same key is passed to Resend, so application-level and provider-level deduplication reinforce each other.
Webhooks are the fast path; reconciliation is the correctness path
Square webhooks update the local projection quickly, but no durable system should assume every webhook arrives exactly once. Signature verification establishes authenticity; a reclaimable event claim establishes single processing; mirroring is idempotent; scheduled reconciliation repairs anything the event path missed.
- Authenticate the event.Verify the Square signature against the exact raw request body and notification URL.
- Claim the event ID.A duplicate exits without running provider side effects again; stale work can be reclaimed.
- Mirror monotonically.Later pending or failed snapshots cannot erase captured or refunded payment state.
- Persist delivery failures.Customer email remains retryable without rejecting a correct commerce update.
- Reconcile independently.Scheduled searches replay updated orders and completed refunds idempotently.
- Refuse a false checkpoint.The watermark advances only after the whole batch succeeds, so failed records return next run.
| Failure | Unsafe reaction | Implemented response |
|---|---|---|
| Duplicate webhook | Process twice | Event-ID claim returns duplicate and stops. |
| Payment response lost | Create another charge | Retain claim and retry the same Square idempotency key. |
| Late pending snapshot | Downgrade captured payment | Monotonic mirror preserves terminal progress. |
| Square version conflict | Overwrite newer state | Retrieve the latest version once and converge. |
| Notification outage | Lose email or fail commerce event | Persist failed delivery and acknowledge correct mirror work. |
| One malformed reconcile record | Skip it forever | Process healthy records, fail the batch checkpoint, replay safely. |
A projection-oriented data model
The database does not attempt to duplicate every Square object. It stores application-facing records, the provider identifiers required to correlate them, and the event state needed for safe work.
- orders
- Core operational projection with Square IDs, money, payment state, workflow state, fulfillment, and a public tracking token.
- order_items + order_status_events
- Immutable purchase detail and versioned workflow history, including provider synchronization state.
- menu_cache
- Application-safe projection of Square items, variations, modifiers, price cents, and sold-out state.
- locations + schedule_slots
- Reusable places and dated public or staff-only service entries.
- refund + notification records
- Durable idempotency and retry boundaries for external side effects.
- webhook_events + job_state
- Claims, attempt state, and incremental reconciliation checkpoint.
Trust boundaries are product behavior
Security is most useful here when it is expressed as explicit ownership and failure behavior rather than a list of tools.
- Untrusted input is parsed at the server.Checkout, catering, admin, and webhook contracts validate before privileged work.
- The client cannot authorize money.Server code reconstructs the cart and totals; money is stored and compared in integer cents.
- Secrets stay behind the API.Service-role credentials and provider secrets are not browser configuration.
- Provider events prove origin.Square webhooks use the raw body and configured notification URL for signature verification.
- Staff identity and staff authority are separate.Supabase authentication establishes identity; an allowed role establishes access. Cloudflare Access adds defense in depth.
- Public projections are smaller than internal records.Customer tracking omits provider-sync internals and only exposes confirmed workflow events.
- The edge rejects obvious abuse early.Same-origin mutation checks, request-size ceilings, security headers, and durable rate limits run before application logic.
Testing follows the boundaries
The repository separates fast domain checks from tests that need a real database or provider sandbox. That matters because the most consequential behavior crosses systems.
Contract and domain tests
Checkout validation, ordering windows, menu mapping, status transitions, public projections, refunds, authentication, and notification claims.
Database tests
Row-level security, checkout functions, monotonic payment state, forward-only workflows, atomic claims, and refund application.
Provider verification
Square sandbox checkout, catalog parity, webhook behavior, production identity gates, and Resend readiness.
Deployment checks
Backend and storefront lint/typecheck/build, Cloudflare dry run, environment and secret checks, live route verification, and focused browser coverage.
The important point is not the number of tests. It is that every ownership boundary has a verification strategy: what the browser may send, what the server recalculates, what the database enforces, what the provider confirms, and what the deployment must expose.
The decisions that keep the system coherent
Square owns commerce
Using the provider as authority avoids a second catalog and payment ledger. The cost is projection logic, provider version handling, and reconciliation.
Supabase stores operational projections
Staff can search, operate, audit, and recover work without forcing every UI read through Square. The projection must never silently outrank provider truth.
Public and staff interfaces are separate
The storefront stays customer-focused while staff get repair state and operational controls. A shared deployment still prevents origin and release fragmentation.
Pickup is native; delivery remains external
The website specializes around a workflow it can operate reliably. DoorDash and Grubhub retain their own customer transactions and delivery logistics.
Reconciliation is a first-class feature
Webhooks optimize latency. Scheduled comparison provides eventual convergence when events are delayed, duplicated, or missed.
Remove systems that create false complexity
The current migration history removes the internal delivery path and rewards system. Fewer authorities and fewer half-owned workflows make the remaining product easier to operate correctly.
Implementation evidence and limits
This article was reconstructed from the current private application repository. The repository is not presented as public source, so the paths below identify the implementation concepts without creating misleading external links.
- Runtime and routing
-
ARCHITECTURE.md,worker.js,wrangler.jsonc - Checkout protocol
-
lib/checkout/service.ts,app/api/checkout/* - Kitchen synchronization
-
lib/orders/status.ts,lib/orders/public-view.ts - Provider event handling
-
lib/webhooks/square/*,lib/jobs/reconcile.ts - Durable notifications
-
lib/notifications/*, notification migrations - Schema and access
-
supabase/migrations/,SCHEMA.md
Current status: the repository targets the production domains through Cloudflare and configures production Square checkout. This article explains implemented architecture, not independently audited uptime or business-performance results.