System design

Designing Zube's Grub

How one system coordinates a public storefront, authoritative commerce, kitchen operations, customer tracking, and recovery when providers disagree.

Author
Mobolaji Ogunbiyi
System
Commerce + operations
Primary authority
Square
Runtime
Cloudflare Worker

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.

Zube's Grub complete system architecture Customers use the React storefront through a Cloudflare Worker. The Worker serves static assets and routes API, staff, authentication, scheduled, and queue work to a Next.js application. The application works with Square, Supabase, and Resend. Staff use the protected application, while marketplace orders enter through Square. Interfaces Application boundary Authoritative services + durable state Customer browser React storefront public UI · cart · Square tokenization Staff application orders · menu · schedule Marketplaces DoorDash · Grubhub Cloudflare Worker routing · assets · edge policy Next.js application API · auth · domain services Scheduled + queue workers reconcile · retry · analytics · hours Square catalog · orders · payments refunds · fulfillment Supabase auth · projections · schedules events · notifications · content Resend transactional email delivery preference The browser never receives service-role credentials and never decides price, availability, payment, or fulfillment truth.
  1. Customer browserrenders the React storefront and sends cart intent.
  2. Cloudflare Workerserves assets, applies edge policy, and routes backend work.
  3. Next.js applicationvalidates requests and owns API, authentication, and domain logic.
  4. Square + Supabase + Resendhold commercial truth, durable application state, and email delivery.
  5. Staff application + workersoperate orders and repair delayed or failed provider synchronization.
One Cloudflare Worker deployment combines a static public storefront with protected application routes and maintenance work. The implementation sources remain separated even though the runtime is unified.

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.

Source-of-truth ownership map Square owns catalog, orders, payments, refunds, and fulfillment. Supabase owns identity, schedules, application projections, workflow events, content, and retry state. Resend owns email transport and recipient preferences. The browser owns only temporary cart and form intent. Server boundary validates · coordinates Square catalog · money · orders refunds · fulfillment Supabase auth · projections · schedule events · retry state · content Browser cart + form intent only Resend email transport + preferences
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 server does not replace the providers. It validates the request, coordinates side effects, and stores enough durable state to explain and repair them.

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.

Order and payment sequence The browser submits cart intent. The API reloads menu and schedule state, validates and calculates totals, creates a Square draft, and records a pending local order. The browser tokenizes payment with Square and confirms using a stable key. The API claims the attempt, compares the Square total, captures payment, marks the projection paid, exposes it to staff, and queues confirmation email. Browser API Square Supabase 1 cart + customer + pickup intent Rebuild authority menu · sold-out · modifiers hours · promotion · cents 2 create idempotent draft order 3 persist pending order + line items + payment key 4 Square SDK tokenizes card or wallet 5 confirm with order, token, stable key Claim payment attempt retrieve draft · compare totals 6 capture with the same idempotency key 7 mark paid · operationally visible · queue email 8 return tracking token + confirmed totals
  1. Submit intent.The browser sends item IDs, modifier IDs, customer details, and pickup preference.
  2. Revalidate.The server reloads menu and schedule state, checks constraints, and calculates integer-cent totals.
  3. Create the draft.Square receives an idempotent order; Supabase records a non-operational pending projection and payment key.
  4. Tokenize payment.The Square browser SDK returns a source token; raw card details do not become application state.
  5. Claim and compare.The API claims the payment attempt, retrieves the Square draft, and rejects a total mismatch.
  6. Capture once.Square receives the stable idempotency key. Ambiguous failures retain the claim for a same-key retry.
  7. Activate operations.Only captured payment makes the order visible to the kitchen; confirmation delivery becomes retryable work.
Pending and failed drafts are intentionally excluded from staff order queues. Payment is the boundary between checkout state and operational 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.

  1. Receivedcaptured payment makes the order operational
  2. Preparingmaps to Square fulfillment RESERVED
  3. Readymaps to PREPARED; customer notification follows confirmation
  4. 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.

Failure and recovery loop Square sends a signed event. The API verifies it and claims its event ID. A new event mirrors into Supabase and marks the claim processed; a duplicate stops safely. If processing fails, the claim becomes reclaimable. Independently, a scheduled search retrieves updated orders and completed refunds, mirrors each idempotently, and advances its watermark only when the entire batch succeeds. Square event order · payment · refund Verify signature parse + validate payload Claim event ID token + attempt count new claim? duplicate stops safely Mirror provider state monotonic + idempotent Mark processed or release failed claim Independent repair path Scheduled worker search since watermark Orders + refunds continue past malformed records Replay safely advance watermark only when every record succeeds
  1. Authenticate the event.Verify the Square signature against the exact raw request body and notification URL.
  2. Claim the event ID.A duplicate exits without running provider side effects again; stale work can be reclaimed.
  3. Mirror monotonically.Later pending or failed snapshots cannot erase captured or refunded payment state.
  4. Persist delivery failures.Customer email remains retryable without rejecting a correct commerce update.
  5. Reconcile independently.Scheduled searches replay updated orders and completed refunds idempotently.
  6. Refuse a false checkpoint.The watermark advances only after the whole batch succeeds, so failed records return next run.
Fast event delivery and periodic state comparison are complementary. The first reduces latency; the second protects correctness.
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.

Simplified Zube's Grub data model Orders connect to order items, status events, promotions, locations, refund applications, and notification deliveries. Menu cache projects Square catalog data. Locations connect to schedule slots. Webhook events and job state support provider synchronization. Catering inquiries connect to quotes and notification delivery. orders square_order_id payment + status money in cents pickup + public token order_items catalog IDs variation + modifiers order_status_events status_version square_sync_status notification status truck_locations public location address + metadata promotions rules + limits redemption state refund applications Square refund ID amount + status idempotent result customer identity optional relation menu_cache Square catalog projection variations · modifiers · sold-out schedule_slots location · service type public vs staff detail recovery state webhook_events job_state · deliveries
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.
This is a conceptual map, not a complete schema. The canonical schema is the ordered migration history; generated types follow it.

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.