Korat
Feature

Restaurant POS, kitchen display and dine-in ordering by QR

A restaurant ordering system starts at the table: a diner scans the QR and is in the menu. A shop gets a kitchen display, an itemised bill the till agrees with, and a delivery queue — without a single price being trusted to the phone that sent it.

Restaurant ordering software usually fails in one of two places: the moment a stranger can order to a table that is not theirs, and the moment the total on the customer's screen disagrees with the total on the till. Korat's food module was designed backwards from both. The table session is minted by staff and carries a single-use token; the bill is the same set of rows the till reads; and place_dining_order prices every line on the server. The modifiers, the buffet timer and the split view are built on top of those two guarantees.

The table QR is a single-use token, not a table number

There is deliberately no "type your table number" path. It is the obvious feature to build, and the reason so many dine-in systems can be abused from the car park: if the only credential is a number printed on the table, anyone who has ever eaten in the shop can order to any table forever. Instead, staff open the table from the business console, and opening it mints a fresh single-use token embedded in the QR shown at that table. A photographed QR, a screenshot forwarded to a friend, or last Tuesday's printout carries a token that is no longer current, and does not work.

A diner walking up to a table is neither a member of the business nor the host of a session that does not exist yet, so under row-level security they cannot read the dining_sessions table at all — not their own row, not any row. RLS can judge who is calling; it cannot judge that a request carries a valid token. So the scan goes through join_session_by_token, a SECURITY DEFINER function that matches the token server-side, makes the first scanner the host, and returns null when it does not match — without saying why. An error that distinguishes "wrong token" from "expired token" is a guessing oracle.

Once inside, the host can invite the rest of the table with a link or an in-chat card, so the friend who arrives late joins the same session and their food lands on the same bill.

The table QR is an https link, so it opens in the app or on the web

The table QR is an https URL, not a custom app scheme. A phone with Korat installed opens it in the app through a domain-verified App Link; a phone without one — including every iPhone — opens the web ordering page instead. This fixes what used to be the deadest moment in the whole product: the customer sits down, raises the camera, and nothing happens at all. That page reads the menu, the buffet clock and the alcohol rules while signed out, and asks for a sign-in only when the order is actually sent, which is the one moment it cannot avoid. QR codes already printed carrying the old korat://dine scheme keep scanning forever.

Ordering: modifiers, notes, and a menu that knows it is closing

The menu is organised by category and every item can carry option groups — the extra shot, the level of spice, the choice of noodle — with price deltas, a required flag and a maximum number of selections. The delta is attached to the option, not typed into a note, which is why the kitchen ticket and the bill line agree about what was ordered. Items also take a free-text note and a quantity, and the cart stays editable until it is sent.

Opening hours, the last-order buffer, and the alcohol check

Shops set opening hours with a last-order buffer. Approaching it, the menu warns that the kitchen is closing soon; past it, ordering is blocked rather than accepted and quietly cancelled later. Items marked unavailable or out of stock say so on the card, before the tap. Alcohol carries its own flag: an age check against the diner's birthdate, and staff can age-verify a whole session at once.

The confirmation queue: nothing reaches the kitchen until a person says yes

Submitted orders do not go straight to the pass. They land in a waiting-for-confirmation queue on the kitchen display, and staff confirm or reject each one before it becomes work. A system that lets a phone put tickets straight into a kitchen has handed a stranger the ability to cost the shop food, and removes the moment where a human notices table six has ordered four identical set menus by accident.

From the kitchen display, staff advance orders through their states, and the console shows live badges refreshed every few seconds — active orders, tables calling staff, pending reservations, new deliveries — plus a red banner the instant a table presses call staff. Diners get the other half of that loop: call staff, request the bill, or leave the table. A shop that seats people in the order they arrive rather than at a numbered table runs queue tickets off the same board and the same console.

The live itemised bill, and splitting it with a PromptPay QR

The live bill shows exactly the rows the till reads, updated as food is confirmed — no end-of-meal reveal, no reconstruction from memory. When the table closes, the app produces a wrap-up sheet — including add-friend suggestions for the people who shared the session — and a review reminder afterwards. Receipts stay in a personal bill history.

Splitting the bill: what each person owes

The split view answers the question people actually argue about: what do I owe. Each person sees their own à-la-carte subtotal plus an equal share of any buffet package on the table. A PromptPay QR can be generated for the amount, in the Thai standard QR format with its CRC-16 checksum — the payments guide walks through where the money actually goes, and what Korat never touches.

Diner scans QR Order sent Confirmed by staff Served
The order only reaches the kitchen after a staff member confirms it in the waiting queue.

The split bill is a breakdown, not separate payments. It computes what each person owes; it does not charge four cards for four shares. Splitting the money is still done between the people at the table and the shop.

And there is no "type your table number" fallback, on purpose. If a diner's QR will not scan, staff reopen the table from the console. Adding a manual path would give back exactly the hole the token was minted to close.

Buffet: per-head packages, timers and leftover charges

Buffet service has its own model rather than being faked with a menu item priced per head. A package has a per-head price, a countdown timer with staff-granted extensions, and separate adult and child headcounts. Menu items carry a buffet role — included, à-la-carte only, or included with a surcharge — so one menu serves both kinds of customer. Leftover-food charging is supported with unit labels: the rule most Thai buffets already print on the table and most software pretends does not exist. The full detail — child pricing tiers, last orders and leftover charges in three units — is on the buffet POS page, and cafes have their own on the coffee shop POS page.

Delivery, takeaway and table reservations

The same menu backs delivery and takeaway, and a restaurant that also sells packaged goods over the counter runs those through the retail module, whose sales land in the same ledger. There is no monthly fee for the menu, the orders or the kitchen display; what a shop package and the add-on plugins each include is set out on the plans page. An order carries a delivery fee, a provider, a scheduled pickup time and an address, and moves through new, cooking, on the way and done, with both sides notified on each transition. Table reservations take a date, time, party size and a note, and wait for the shop to confirm — the pending count sits on the dashboard as a live badge.

Every price is computed on the server

The rule that makes the rest of it safe: the client never sets a price. place_dining_order and place_shop_order read item prices, option deltas and the delivery fee out of the database and ignore whatever the phone sent. A trusted client field is a self-service discount. The same principle covers reviews — only a customer the database can prove ate in or ordered delivery may write one, and the star rating is recomputed by a trigger, never typed in.

Frequently asked questions

Can a customer order without scanning the QR at the table?

Not for dine-in. There is no manual table-number entry, because a printed number is a credential everyone in town eventually has. Staff open the table from the console, which mints the token the QR carries.

Delivery and takeaway are the other way round — those are ordered from the shop's page without any table involved.

What stops someone photographing our table QR and ordering later?

The token in the QR is single-use and tied to the session staff opened. Once that session ends, the token no longer matches and the scan resolves to nothing. The check happens in a SECURITY DEFINER database function, not in the app, so it cannot be bypassed by talking to the API directly.

Do orders go straight to the kitchen?

No. Every order lands in a waiting-for-confirmation queue on the kitchen display first, and staff confirm or reject it. That confirmation step is where mistakes and abuse get caught before they cost food.

Can each person at the table pay their own share by card?

No. The split view calculates what each person owes — their own items plus an equal share of any buffet — and payment is settled with the shop. Korat does not process separate per-person card payments.

What happens if a customer orders just as we close?

Shops configure opening hours plus a last-order buffer. Inside the buffer the app warns that the kitchen is closing soon; past it, ordering is blocked rather than accepted and cancelled afterwards.

Open a table and see the loop

The diner side and the console side are the same app. Install it, create a business, and scan your own QR.