Korat
功能

餐廳收銀、後廚顯示屏和桌邊掃碼點單

餐廳點餐系統從桌臺開始:食客掃一下二維碼就進了菜單。店家拿到廚房顯示屏、一份和收銀機對得上的逐項帳單,以及一個外送佇列——而沒有任何一個價格,是由發出請求的那部手機說了算的。整套免費,沒有月費。

餐廳點餐軟體通常敗在兩處:陌生人能把菜點到不屬於他的桌上的那一刻,以及顧客螢幕上的總額與收銀機上的總額對不上的那一刻。Korat 的點餐模組是從這兩點倒推著設計的。桌臺會話由店員開出,攜帶單次有效的令牌;帳單就是收銀機讀的那幾行;place_dining_order 在伺服器上給每一行定價。選項組、自助餐倒計時和拆賬檢視,都建在這兩條保證之上。

桌臺二維碼是單次有效的令牌,不是桌號

這裡刻意沒有“手動輸入桌號”的入口。那是最容易想到的功能,也是那麼多堂食系統能從停車場被濫用的原因:如果唯一的憑據是印在桌上的號碼,那麼任何在這家店吃過一次的人,從此都能往任何一桌點菜。取而代之的是:店員從控制檯開臺,開臺會鑄出一枚新的、單次有效的令牌,嵌進那一桌顯示的二維碼裡。拍下來的二維碼、轉發給朋友的截圖、上週二列印的那張,攜帶的都是已經不再有效的令牌,掃不出結果。

走到桌邊的食客既不是這家店的成員,也不是一個尚不存在的會話的主人,所以在行級安全之下他根本讀不到 dining_sessions 表——不是讀不到別人的行,是一行也讀不到。RLS 能判斷誰在呼叫,卻無法判斷請求是否攜帶了有效令牌。所以掃碼走的是 join_session_by_token,一個 SECURITY DEFINER 函式:它在服務端匹配令牌,把第一個掃碼的人設為主人,匹配不上就返回空——而且不說為什麼。一個能區分“令牌錯誤”和“令牌過期”的報錯,就是一臺猜測預言機。

進來之後,主人可以用連結或聊天卡片把同桌其餘的人拉進來,晚到的朋友加入同一個會話,點的菜落在同一張帳單上。

桌臺二維碼是 https 網址:應用裡能開,網頁也能開

桌上的二維碼是一個 https 網址,不是應用私有的協議。裝了 Korat 的手機會透過已驗證域名的 App Link 在應用裡開啟;沒裝的手機——包括每一臺 iPhone——則會開啟網頁版點單頁。這修好的是整個產品裡曾經最死的一個瞬間:顧客坐下、舉起相機掃碼,然後什麼都沒發生。那個頁面在未登入狀態下就能讀到菜單、自助餐倒計時和酒類規則,只在真正提交訂單的那一刻才要求登入,而那是唯一躲不掉的時刻。早先印好、帶著 korat://dine 的二維碼,永遠都還能掃。

點單:選項組、備註,和一份知道自己快打烊的菜單

菜單按分類組織,每個菜品都可以帶選項組——多加一份濃縮、辣度、麵條種類——帶價格差、必選標記和最多可選數量。價格差掛在選項上,而不是打進備註裡,這也是廚房小票和帳單行對得上的原因。菜品還接受自由文字備註和數量,購物車在發出之前一直可編輯。

營業時間、最後點單緩衝與酒類年齡核驗

店家設定營業時間和一段最後點單緩衝。接近這段緩衝時,菜單會提示廚房即將打烊;過了這段時間,點單被直接阻止,而不是先接下來再悄悄取消。標為不可用或缺貨的菜品會在卡片上寫明,出現在點選之前。酒類有自己的標記:按食客生日做年齡核對,店員也可以一次性為整桌完成年齡核驗。

確認佇列:沒有人點頭,訂單就不進廚房

提交的訂單不會直接送到出餐口。它們落進廚房顯示屏上的一個待確認佇列,由店員逐單確認或拒絕,之後才變成活兒。允許一部手機把小票直接塞進廚房的系統,等於把“讓店家白白損失食材”的能力交給了陌生人,同時也拿掉了那個由人發現“六號桌不小心點了四份一樣的套餐”的時刻。

在廚房顯示屏上,店員推進訂單狀態;控制檯每隔幾秒重新整理的實時角標顯示待處理的量——進行中的訂單、正在呼叫服務員的桌、待確認的訂位、新的外送單——一旦有桌按下呼叫服務員,頂部立刻出現紅色橫幅。食客拿到這個迴路的另一半:呼叫服務員、要帳單、或者離席。

實時逐項帳單,與 PromptPay 二維碼拆賬

實時帳單顯示的正是收銀機讀的那幾行,隨著出餐確認而更新——沒有飯後揭曉,也不需要靠回憶重建。桌臺結束時,應用生成一張收尾頁,包含把同桌的人加為好友的建議,之後還有一次評價提醒;收據留在個人的帳單歷史裡。

拆賬:我該付多少

拆賬檢視回答人們真正會爭論的那個問題:我該付多少。每個人看到自己的單點小計,加上桌上任何自助餐套餐的均攤份額。可以按金額生成 PromptPay 二維碼,採用帶 CRC-16 校驗的泰國標準 QR 格式。

拆賬是一份明細,不是分別付款。它計算每個人該付多少;它不會為四份份額刷四張卡。錢怎麼分,仍然發生在同桌的人和店家之間。

而且刻意沒有“手動輸入桌號”的備用路徑。如果食客的二維碼掃不出來,店員從控制檯重新開臺。加一條手動入口,等於把當初鑄造令牌所要堵上的那個洞原樣還回去。

自助餐:人均套餐、倒計時與剩菜計費

自助餐有自己的模型,而不是用一個按人頭計價的菜品糊弄過去。一個套餐有人均價、可由店員延長的倒計時,以及分開的成人與兒童人數。菜品帶自助餐角色——包含、僅單點、或包含但加價——所以一份菜單同時服務兩類顧客。剩菜計費也支援,帶單位標籤:那條大多數泰國自助餐早就印在桌上、而大多數軟體假裝不存在的規則。

外送、自取與桌位預訂

同一份菜單也支撐外送和自取。訂單帶配送費、配送方、預約取餐時間和地址,在新單、製作中、配送中和已完成之間流轉,每次狀態變化兩側都收到通知。訂位收集日期、時間、人數和備註,等店家確認——待處理數量以實時角標停在儀表盤上。

每一個價格都在伺服器上計算

讓上述一切成立的那條規則是:客戶端從不設定價格。place_dining_orderplace_shop_order 從資料庫讀取菜品價格、選項價差和配送費,忽略手機傳來的任何值。一個被信任的客戶端欄位就是一張自助折扣券。同樣的原則覆蓋評價——只有資料庫能證明確實堂食過或點過外送的顧客才能寫評價,而星級由觸發器重算,從來不是誰手填的。

不想讓客人坐下等、只想發號的,看排隊叫號;廚房以外的商品也要賣出去(外帶、周邊、預製包)的,在零售裡和店內庫存共用同一份資料。

廚房和外場今天誰上班、誰請假,來自我的職場那張排班表;第一次把一家真能幹活的店開出來,從面向商家那一頁開始。

常見問題

顧客能不掃桌上的二維碼就點單嗎?

堂食不能。這裡沒有手動輸入桌號,因為印出來的號碼是一份全城遲早都有的憑據。店員從控制檯開臺,開臺會鑄出二維碼所攜帶的令牌。

外送和自取則相反——那些從店鋪主頁下單,不涉及任何桌臺。

有人拍下我們的桌臺二維碼,事後能點單嗎?

不能。二維碼裡的令牌是單次有效的,且繫結在店員開出的那次會話上。會話一結束,令牌就匹配不上,掃碼解析不出任何東西。這項檢查發生在 SECURITY DEFINER 資料庫函式里而不是應用裡,所以直接呼叫 API 也繞不過去。

訂單會直接進廚房嗎?

不會。每一單先落進廚房顯示屏上的待確認佇列,由店員確認或拒絕。這一步確認,正是錯誤和濫用在造成食材損失之前被攔住的地方。

同桌的每個人能各自刷卡付自己那份嗎?

不能。拆賬檢視只計算每個人該付多少——自己點的東西,加上任何自助餐套餐的均攤份額——付款仍然和店家結清。Korat 不處理按人分開的刷卡付款。

顧客卡在我們打烊那一刻下單,會怎樣?

店家配置營業時間和一段最後點單緩衝。在緩衝期內,應用會提示廚房即將打烊;過了這段時間,點單被直接阻止,而不是先接下來事後再取消。

開一桌,把迴路走一遍

食客那一側和控制檯那一側是同一個應用。裝上它,建一家店,掃你自己的二維碼。