Fooodo / Docs

Architecture

The two-service shape (menu + payments), the multi-tenancy model, and where UVS sits.

Fooodo runs as two cooperating services plus an external POS. This page is the conceptual map.

The two services

┌─────────────────┐        ┌─────────────────┐        ┌─────────────────┐
│   Menu app      │ ─────▶ │ Payment service │ ─────▶ │     Mollie      │
│   (Laravel)     │ ◀───── │   (Laravel)     │ ◀───── │                 │
└─────────────────┘        └─────────────────┘        └─────────────────┘

        │ ▼
┌─────────────────┐
│    R-Keeper     │
│      (POS)      │
└─────────────────┘
  • Menu app. Laravel + React (Inertia) + Filament admin. Handles the customer-facing flow (QR → menu → cart → order), the operator admin panel, and all R-Keeper traffic.
  • Payment service. Separate Laravel app behind a token-authenticated API. Owns Mollie integration, webhook routing, donation routing, and tip routing. Has its own admin (Nova).
  • R-Keeper is the POS. Fooodo treats it as the system of record for the menu and as the kitchen's source of truth for orders.

The two Fooodo services talk over an authenticated HTTP API; there is no shared database, no shared queue, and no shared deployment. They can be deployed independently.

Why split the payment service

Three reasons:

  1. Compliance scope. PCI scope is bounded to the payment service. The menu app never sees card data; it gets back a checkout URL and a webhook callback.
  2. Reusability. The same payment service can serve other Fooodo surfaces (and historically has — the architecture predates Fooodo's current shape).
  3. Failure isolation. If Mollie has a regional outage, the menu app stays up; orders queue in ReadyToBePaid state and reconcile when the payment service recovers.

Multi-tenancy

Every record in the menu app is scoped through this hierarchy:

Company
  └── Restaurant (POS config, working hours, default flow)
        └── Table (QR code, per-table flow override)
              └── Order
                    └── Order items, payments, tips, donations
  • Company is the tenant boundary. Currently one active company in production (Čili Pizza), but tenant separation is enforced everywhere.
  • Restaurant admins can only manage their own restaurant — the role system enforces this at the policy layer.
  • Tables are addressable: a QR code resolves to a specific table at a specific restaurant. There is no anonymous "scan to order" — Fooodo always knows which seat the order is for.

Where UVS sits

UVS — the order/operations management system — is the conceptual core. In practice, it is the menu app's Order aggregate plus the event stream around it. Everything written by the platform — orders, payments, table state, kitchen tickets — is written through UVS first, then propagated to consumers (R-Keeper, the payment service, future modules).

The reason this matters for integrators: modules don't talk to each other directly. A connector for a new POS doesn't touch the kitchen flow or the payment service. It speaks one language — the UVS contract — and Fooodo handles the rest.

Stack at a glance

ComponentTechnology
Menu appLaravel 10+, PHP 8.2+, PostgreSQL
Menu adminFilament 5, Livewire 4
Menu frontendReact 18 + Inertia.js + Tailwind
Menu queuesRedis + Laravel Horizon
Payment serviceLaravel 12, PHP 8.2+, Nova admin
Payment providerMollie (Card, Apple Pay, Google Pay, Trustly)
POS connectorR-Keeper (live), Toast / iiko / Square (roadmap)
Marketing siteNext.js 16 + next-intl + Fumadocs (this site)

What lives where

  • Menu management, orders, tables, customer-facing flow → menu app.
  • Card data, refunds, payment methods, tips, donations → payment service.
  • Menu source of truth, kitchen tickets, reports → R-Keeper.
  • Marketing copy, public docs, AI assistant, MCP server → this site.

If you are integrating with Fooodo, the API surface you care about is the menu app's external API. The payment service is internal — the menu app proxies to it.

On this page