Food For Less
One backend behind a restaurant's website, Telegram Mini App, kitchen, drivers and till, so every channel agrees on the order and the money.
- Status
- Live
- Role
- Co-developer
- Year
- 2026
- Type
- Commerce and restaurant operations platform




Where it stands
Running at food-for-less.com. Its health endpoint reported release V777-045 when checked on 13 October 2026.
- Channels
- Website, Telegram bot, Mini App, WhatsApp, phone orders
- Database
- 44 additive migrations
- Tests
- 86 test modules plus browser tests
The problem
A restaurant that takes orders from a website, a Telegram bot, a Mini App and the phone ends up with several versions of the truth. Who is this customer? What do they owe? Has the food left the building?
Each channel had grown its own answer. The work was to give all of them one order lifecycle, one ledger and one set of rules, without stopping service to do it.
Goals and constraints
It had to
- One order lifecycle that behaves the same on every channel.
- Money recorded once, from confirmed transactions only, and never edited afterwards.
- Kitchen staff and drivers can only take the action that is legal right now.
- Updates the owner can run by double-clicking, with a way back if anything fails.
Within these limits
- It runs as a single process on one Windows machine at the restaurant, with SQLite as the database.
- The receipt printer sits on the local network and must never be exposed to the internet.
- The people running it are restaurant staff, not engineers.
- It was already taking real orders, so every release had to migrate live data in place.
My part
The repository has two contributors. My commits cover the releases from V777-015 to V777-045: the storefront rebuild, payments, the delivery engine, finance, roles, printing and the installer.
- Rebuilt the web storefront and checkout against the live catalogue, with SMS one-time-code sign-in.
- Built the payment model: append-only transactions, Stripe Checkout confirmed by signed webhooks, partial payments, refunds and corrections.
- Built the delivery engine as a single state machine shared by the Telegram bot, the driver web app and the admin panel.
- Added admin roles and permissions, the finance and cash ledgers, scheduled orders, customer accounts and delivery pricing rules.
- Added thermal receipt printing with a job queue and an outbound-only print agent.
- Wrote the installer that tests a release before it stops the running one, and set up the GitHub Actions deployment workflow.
What it is
Customers order from the website, the Telegram bot or the Mini App and pick delivery or pickup, now or at a scheduled time. The restaurant approves the order, the kitchen prepares it, a driver accepts it, and the customer confirms delivery with a PIN.
Behind that sits an admin panel for live orders, menu editing, finance, customers, drivers, printers and support. Every surface calls the same server-side services, which is the point of the project.
- Ordering on every channel
- Website, Telegram bot and Mini App offer the same enabled payment methods and the same server-side rules. The Mini App verifies Telegram's signed init data instead of relying on an SMS provider.
- Delivery engine
- Assigned, accepted, picked up, out for delivery, PIN verified, delivered. Batches of two or three orders, driver handovers and structured delivery problems are all part of the same machine.
- Finance ledger
- Selected and actual payment methods are tracked separately. Payments are separate records, so an order is unpaid, partially paid or paid by arithmetic rather than by a flag.
- Invoice revisions
- Changing an order writes a new invoice revision. A delivered order is financially locked: only a refund or an explicit, reasoned correction can touch it.
- Roles and audit
- Seven default roles with per-admin grant and deny overrides. Permissions are checked on the route and again in the service layer, and sensitive actions need a reason.
- Receipt printing
- Orders insert print jobs and move on. A worker or the restaurant's print agent talks to the printer afterwards, with retries and a visible failure reason.
- One customer across channels
- Telegram chats, WhatsApp numbers and website sign-ins are channel identities that resolve to a single customer record.
- Driver payroll
- Effective-dated salary and commission rules, one immutable commission entry per delivered order, and payroll runs that a second admin has to approve.
Decisions and trade-offs
01Money is append-only
A wrong payment is never edited. The original stays, a reversal is added, and the corrected payment is recorded next to it with a reason. Cash 100 becomes cash 80 plus card 20 without losing the fact that someone first entered 100.
The cost. More rows and more interface to explain them. In exchange, the question "who changed this and why" always has an answer.
02Order status and payment status are separate
An accepted online order is not a confirmed order until the ledger holds the money. The kitchen cannot start it, and the customer is never shown "payment received" early.
The cost. Two status fields to reason about in every screen, instead of one combined state that would have been simpler and wrong.
03One state machine, written as compare-and-set
Every delivery step checks the actor, the current state, the payment and the PIN on the server, and writes only if the state is still what the caller saw. A stale Telegram button or a crafted form request is refused.
The cost. Admin overrides lost the ability to force a driver stage. That was the intent, but it meant building explicit, audited exception paths.
04Printing never blocks an order
The print agent on the restaurant PC connects outwards over HTTPS with a per-printer token and prints on the local network. No inbound port, and a dead printer cannot fail a checkout.
The cost. A receipt can arrive a few seconds late, and failed jobs need their own screen and retry button.
05Tests run before the old version stops
The installer checks dependencies, rehearses the migration on a copy of the database and runs the full suite while the current release keeps serving. Only then does it stop, back up, deploy, migrate and wait for the health check.
The cost. Updates take longer end to end, but the window where the site is down stays short.
Try it
A small model of the order and payment rules, written for this site. It runs in your browser and is not connected to the restaurant. Every button is live, including the ones the server would refuse.
- New
- Accepted
- Preparing
- Ready
- Assigned
- Out for delivery
- Delivered
- Order
- New
- Payment
- Not payable yet
- Method
- Online
- Customer PIN
- Not entered
Restaurant
Customer
Driver
Solid buttons are legal now. Dashed ones are not, and you can press them to see the refusal.
Event timeline
Append only · 0A new order is waiting for the restaurant. Try to pay for it before it is accepted.
How it is built
- A FastAPI application with server-rendered pages for the storefront, admin and driver app, and an aiogram bot in the same process.
- SQLite with numbered, additive migrations. Append-only tables are protected by database triggers that refuse UPDATE and DELETE, and time-slot capacity is enforced by a trigger inside the write that takes the slot.
- A single finance module is the only place totals, VAT and breakdowns are computed. Checkout, order pages, the Mini App and invoices all call it.
- Stripe Checkout in test or live mode, with keys, objects and webhook events required to match the mode.
- Messaging goes through one notification service with channel adapters for Telegram and WhatsApp.
Python · FastAPI · SQLite · aiogram · Telegram Mini Apps · Stripe · Twilio · ESC/POS · pytest · GitHub Actions · Cloudflare Tunnel
Hard problems
A Stripe webhook and the customer's return page can both report the same payment at the same moment.
Payment recording is idempotent and keyed to the Stripe objects, so whichever arrives first writes the transaction and the second is a no-op. Amount mismatches are flagged for review instead of being trusted.
The same person appears as a Telegram account, a WhatsApp number and a phone order. Merging them by name or address would eventually merge two different people.
The only automatic join is a phone number the person has just proved they own, and only when exactly one customer holds it. Everything else raises a possible-duplicate alert for a human, and merges are side-by-side, reasoned and recorded.
Two customers can try to book the last place in the same delivery slot.
Capacity is checked by a database trigger inside the transaction that takes the place. A rolled-back or cancelled order holds nothing.
An update that fails halfway on the restaurant's only machine means no orders.
Preflight on a copy of the data, a single-instance lock, a verified backup, and a rollback that removes what the new version deployed and restores the database by checksum.
What can be verified
- automated tests passing, 0 failingRelease notes for V777-038, recorded 8 October 2026.
- 1,419
- database migrations, all additiveMigration history in the repository at V777-045.
- 44
- release serving the public sitefood-for-less.com health endpoint, checked 13 October 2026.
- V777-045
Limits and open work
- It is designed for one restaurant on one machine. Multi-branch or multi-tenant use would need a different database and deployment story.
- The WhatsApp channel is implemented against Twilio's sandbox and has not been proven end to end.
- Thermal receipts were validated by tests of the ESC/POS command stream; real-printer behaviour is tuned on site.
- The repository is private, so there is no public code to link.
Need something like this built?
Tell me what you are making. I will tell you how I would approach it, and whether I am the right person.
Start a conversation