Case Study · Independent Build — Open Source
First-party restaurant ordering, built in the open: one POS, two courier networks, five published gems
Restaurants pay marketplaces 15–30% to rent access to their own customers. I built the alternative end to end — a branded ordering storefront that runs on the Square POS a restaurant already uses, with DoorDash and Uber Direct quoting head-to-head for every delivery — and unlike the engagements elsewhere on this page, every layer of this one is public: five open-source gems, a live deployment you can click, and a written record of what broke along the way.
See it yourself: live demo · spree_square · spree_doordash · spree_uber_direct · spree_menu_chat · spree_loyalty
Context
A decade of restaurant-integration work — the other case studies on this page — lives in private repositories. This build exists to put the same class of work where anyone can inspect it: a complete first-party ordering platform for a single restaurant, of the kind Toast and ChowNow sell as products, built solo as a reference implementation. The demo restaurant ("The Local Table") is fictional; the integrations, payments, webhooks, and bugs are not.
Challenge
The hard constraint is that a restaurant already runs its business on a POS, and staff will not maintain a second menu in a second admin. So the POS had to stay the single source of truth — menu, prices, photos, sold-out state, even tax configuration — while the platform adds only what the POS has no answer for: a branded ordering channel and on-demand courier dispatch, handed back to the POS as ordinary paid tickets. And because integration work is only as credible as its verification, every phase had to be proven against real infrastructure — real sandbox APIs, real webhooks, a real deployed storefront — not mocked test suites alone.
Approach
- Seven applications, deployed independently: a Rails backend (Spree Commerce), a Next.js storefront, and five integration engines — each engine its own gem, repo, test suite, and changelog, consumed by the host app via pinned versions like any third-party dependency.
- Square as menu master: one-way catalog sync (items, modifiers, photos, 86'd state, and catalog tax objects) driven by webhooks with idempotency tables and optimistic-concurrency version checks, so out-of-order delivery can't clobber newer data.
- Event-driven order flow: order completion fires three independent subscribers — POS push (orders land in Square as already-paid tickets), courier dispatch, and loyalty accrual — none waiting on, or aware of, the others.
- Delivery as ordinary shipping rates: DoorDash and Uber Direct each implement Spree's shipping-calculator interface, so both networks' live quotes appear side by side at checkout with zero storefront changes — the customer picks, dispatch is automatic.
- Two AI layers, both grounded in real store data: a RAG menu assistant (pgvector on the existing Postgres — no new infrastructure), and a remote MCP server with OAuth 2.1 and dynamic client registration, so the admin can ask ChatGPT or Claude "what was last week's revenue?" and get answers computed live from the production database.
Key decisions
- The POS stays the source of truth — staff edit Square exactly as they already do; the storefront follows. No second menu, no second admin, and tax configuration syncs from Square's own catalog rather than being maintained twice.
- Modifiers are not variants — Square's "choose your sauce" lists map to per-line-item customizations with price snapshots at add-to-cart, not Spree's combinatorial variant matrix. The kitchen ticket always matches what the customer paid.
- Two courier networks, priced against each other — one integration proves you can dispatch; the second proves the architecture is provider-agnostic, and gives the restaurant leverage and failover a single network can't.
- Verify live, or it doesn't count — every milestone ended against real infrastructure: real Square sandbox catalogs, DoorDash's delivery simulator, Uber's Robo Courier walking a delivery through its full lifecycle on real webhooks, a real claude.ai connector answering revenue questions checked against the database behind it.
- Ship the reusable parts as gems — each integration is generically useful to any Spree store, so each is published, versioned, and documented as if a stranger will install it. Five strangers can.
What broke along the way
The build record keeps its bugs on purpose — they're where the real lessons are. A
sample: a \r\n-vs-\n SSE parsing bug that silently broke every
streamed chat answer under a fully green test suite. CanCan authorizing an STI base class
before Rails reclassifies the record, making the "correctly scoped" permission grant
impossible by design. Doorkeeper's admin_authenticator defaulting to a silent
no-op — omitting it doesn't lock the OAuth admin UI, it publishes it. And a production 500
caught by Sentry within the hour: the MCP transport hands back a streaming body for one
method even when configured stateless, and a controller that assumes an array crashes on
it. Each of these is written up properly in the repo record and
the article series that accompanies this build.
Outcomes
A deployed, working platform — figures measured from the live demo on 2026-08-26, not projected: 14 of 14 completed orders pushed to Square as paid tickets with zero errors, 32 real courier quotes returned at checkout, 158 order- and delivery-status webhooks handled across Square and both courier networks, and a full Uber Direct delivery lifecycle (assigned → picked up → delivered → order closed) verified end to end. The five gems install into any Spree 5 store. And the honest caveats are documented rather than hidden: both delivery networks run against their sandboxes — production access is an application step on their timelines, not remaining engineering — and payments run Stripe test-mode by design, this being a demo.
Facing a build like this?