SAWASDEE / ORDER方案评审首页交互原型 ↗v0.1 · 2026.10.25

扫码点餐 · 技术设计文档

v0.1 方案评审稿|2026-10-25|尚未开发或选定供应商

1. 架构选择

建议首期采用模块化单体 API + 独立后台任务与打印适配器,避免小规模阶段过早拆分微服务。顾客端移动 Web/PWA,商户移动 Web 后台;需后台接单和内置打印 SDK 的设备使用 Android 原生接单 APP。Web 浏览器不能直接稳定驱动任意 POS 内置打印机。

建议栈:TypeScript 前端(React/Vue 二选一,团队能力决定)、TypeScript API(NestJS 等)、PostgreSQL 事务库、Redis 短时缓存与限流、持久任务队列、对象存储与 CDN。打印代理建议 Kotlin 接各厂商 Android SDK。此处为候选设计,不锁定开发团队或部署云。

flowchart TD
    C[顾客移动 Web · 四语] --> API[API · 身份/来源/限流]
    M[商户移动后台] --> API
    A[Android 接单 APP] --> API
    API --> CORE[菜单 / 会话 / 订单 / 台账 / 支付 / 打印配置]
    CORE --> DB[(PostgreSQL + Outbox)]
    DB --> W[事件及后台任务 Worker]
    W --> PAY[支付服务商适配器]
    PAY --> CB[签名校验回调入口]
    CB --> CORE
    W --> N[长连接 / 系统推送 / 未确认升级提醒]
    N --> A
    W --> R[打印路由与任务账本]
    R --> S[厂商云打印 API / CloudPRNT]
    R --> L[店内 Android 或网关代理]
    L --> P[内置 / USB / LAN 打印机]
    S --> CP[云打印机]

推荐云区域按泰国访问性能、服务商支持、备份与数据政策评估;可候选新加坡区域,不能把候选地点等同于合规结论。首期按单区域高可用设计,跨区灾备后续按预算确定。

系统架构图

2. 领域模型与隔离

实体 关键字段与约束
Merchant / Store tenant_id、store_id、country、currency、timezone、status;收款主体绑定到店
Location / QRLocator store_id、type、label、opaque_token_hash、version、active;轮换撤销旧码
DiningSession location_id、session_id、policy_snapshot、status、expires_at;同一位置最多一笔活动后付台账
GuestSession 随机会话令牌、服务位置、可访问本人订单、语言;不放个人身份证明
Product / Variant / Modifier SKU、最小货币单位价格、必选限制、库存、版本
ProductTranslation product_id、locale、name、description、allergens;语言不复制 SKU
Category / Department 销售分类与生产部门分别维护;组件可覆盖部门
Order / OrderLine tenant/store/session、order_version、policy、prices/tax/name/department 快照、batch
Bill / BillLine 后付台账、冻结版本、未付额、结账锁;追踪每笔订单是否已计入
PaymentAttempt order_id 或 bill_id、provider、provider_ref、amount、currency、status、expires_at
Refund payment_id、amount、reason、approval、provider_ref、status;退款累计不超过已付
Device / DeviceStoreGrant 租户归属、认证凭证、门店授权、能力、固件、在线状态;凭证可撤销
PrintRoute / Template 事件、门店/部门、设备、份数、版本、启停、主备策略
PrintJob / Attempt business_event_id、order_version、destination、copy_no、payload_hash、ack_level
Outbox / WebhookInbox 事务事件与已处理回调;唯一事件标识,防重复
AuditLog 操作人、门店、对象、前后值、原因、时间;退款/改价/补打均记录

所有业务表带 tenant_id,查询先校验租户与门店授权;可叠加 PostgreSQL 行级安全。共享设备仅通过 DeviceStoreGrant 读取获授权门店的最小打印内容,不允许借设备凭证调用完整订单列表。跨商户共享首期关闭。

订单保存价格、税费、规格、商品名和生产部门快照。后续菜单、分类或规则变化不改变历史账单及重打内容。个别打印错误纠正应形成新修订并审计。

3. 金额、库存与并发

4. 订单、支付与台账状态

订单履约与支付是两个状态机,不能用一个 status 混合表达。

维度 状态及主要转换
履约 PENDING_PAYMENT → PENDING_ACCEPTANCE → ACCEPTED → PREPARING → READY → SERVED → COMPLETED;拒单进入 REJECTED;取消进入 CANCELLED
支付尝试 CREATED → PENDING → SUCCEEDED / FAILED / EXPIRED;SUCCEEDED 不因迟到失败回调回退
财务汇总 UNPAID / PAID / PARTIALLY_REFUNDED / REFUNDED;由成功支付与退款账本计算
退款 REQUESTED → APPROVED → PROCESSING → SUCCEEDED / FAILED;失败可人工处理并保留原记录
后付台账 OPEN → CHECKOUT_LOCKED → SETTLED → CLOSED;未支付且渠道已确认取消/失效才可安全解锁

先付:创建待付款订单,支付成功并持久化后进入待接单;自动接单可同事务或可靠事件执行。人工接单成功才发布 ProductionReleased 事件。后付:下单直接待接单,接单后生产;整桌支付成功只结账,不能再触发生产打印。

客房服务的 READY → DELIVERING → SERVED 可作为场景分支。各部门单独维护任务状态;订单准备完成需所有必要部门完成,打印 ACK 不算制作完成。

关键异常:先付过期订单收到迟到到账,不自动丢弃;验证 provider_ref 与金额后进入 PAID_LATE_REVIEW,视库存与营业状态人工接单或退款。已成功尝试不允许再发起新付款;并发成功导致多付需进入差额异常账本及退款流程。

5. 支付集成边界

本期只列渠道,不写支付接入。未来建议每店通过合适的持牌服务商商户账户收款;平台不默认归集资金或自行分账。商户入驻、结算币种、退款能力、渠道在线/线下模式由服务商合同确定。

接口抽象:createPayment、getPayment、cancel/expire(如支持)、requestRefund、getRefund、verifyWebhook。记录 provider 的原始有限字段及不可逆脱敏日志,不存卡号/CVV;卡支付优先托管页/服务商 tokenization。

回调流程:验证服务商支持的签名/认证及防重放机制 → 插入 Inbox 去重 → 再查支付状态(按服务商规范)→ 校验商户收款账户、provider_ref、金额和币种 → 锁定支付尝试 → 写支付账本与订单变更及 Outbox → 提交事务 → 返回成功。不能假定每个服务商有相同的签名机制;不具备签名时使用其认证与主动查询方案。IP 白名单只能补充,不能替代可信到账核验。

回调超时/丢失由对账任务补偿。浏览器回跳只触发状态查询,不修改财务状态。付款二维码包含有效期及金额绑定;支付二维码与桌面来源二维码是两类不同对象。

退款原路能力、时效、部分退款、手续费退还及收款人补充资料按渠道逐一确认;不能承诺 PromptPay、钱包、卡具备同样退款体验。每日对账比较订单应收、成功扣款、退款和渠道结算,差异进入人工队列。

6. 打印路由与可靠性

6.1 任务生成

ProductionReleased 或 OrderAmended 事件提交后,Worker 按已冻结的路由版本生成计划:

  1. 找到订单租户及门店可用规则,验证所有目标设备授权。
  2. 收银生成 FULL_ORDER 任务;部门按订单行生产部门聚合生成 DEPARTMENT 任务。
  3. 套餐按组件展开生产行,保持父商品和数量关联;不能把套餐金额重复计入部门票。
  4. 每目标、模板、份数单独生成 PrintJob。唯一键为 event_id + destination + template_version + copy_no。
  5. 无部门、无路由、设备解绑进入 ROUTING_EXCEPTION,提醒店员;禁止“打印成功”掩盖未分配项目。
  6. 渲染成设备支持的指令或位图,存 payload_hash、订单版本、路由版本及源数据摘要。

同一物理打印机按队列串行调度,跨门店按公平顺序,防止票据字节交叉。前台和各厨房的逻辑任务可独立并行。配置错误时支持发布前预览路由图和试打。

6.2 投递与状态层级

QUEUED → LEASED → SENT → ACKNOWLEDGED / PRINT_CONFIRMED;可能进入 RETRY_WAIT / FAILED / UNKNOWN / CANCELLED。ACKNOWLEDGED 只代表服务商或设备收到了任务;PRINT_CONFIRMED 仅在 SDK 或设备确有对应回执能力时使用,并显示证据等级。

不承诺端到端物理 exactly-once。网络在“已打印但回执丢失”时中断,会造成状态不确定。可通过唯一任务号和代理持久账本尽量减少重复,但物理出纸无法由数据库事务回滚。UNKNOWN 不盲目自动补打或切备机,优先让店员核对;明确投递失败的任务才安全自动重试。

建议:代理租约与心跳、短断线持久队列、指数退避加抖动、达到阈值告警;具体重试次数和时间由样机验证。代理保存 task_id 与最后投递状态,重启后去重拉取;驱动调用后到落盘前崩溃仍可能产生未知状态。主备切换前检查旧设备是否可能已经出纸,不能承诺无重复。

人工补打创建新任务,引用原任务、操作者与原因,票头醒目标记“补打 REPRINT”;不覆盖原失败记录。打印成功不能消除业务接单超时告警。

6.3 四种连接方式

方式 接入设计 网络及限制
Android POS 内置 APP / 打印代理调用厂商 SDK 需验证安装权限、SDK、后台运行与设备区域固件
店内 LAN/USB Android 或专用网关代理驱动打印机 服务端不能直接访问门店私有 IP;代理主动连云
厂商云打印 服务端调用云 API,接收回执或轮询 厂商准入、区域、订阅、回执语义和数据路径须确认
Star CloudPRNT 打印机通过协议轮询/支持型号的消息机制取任务 需要实现协议服务;不是任意 LAN 打印机天然支持

打印机 LAN 端口不暴露公网。代理主动 HTTPS/WSS 连接,设备单独短期凭证;不把全平台 secret 安装到商户设备。

6.4 多语言票据

泰文涉及组合字形与上下标,不能仅依据“支持 ESC/POS”判断打印正确。优先使用经验证的厂商 Unicode 排版;不支持时在服务端/代理用含合法字体的排版引擎栅格化为单色位图,并测试速度与内存。58mm 与 80mm 使用实际点宽和可打印宽度,不能只按纸宽计算。

测试泰文附标、中文、英文长规格、马来文、混排、换行、取消标记和重复份数。打印字体缺失应报错或回退到已确认名称,不能静默乱码。

7. 候选 API 契约

下表仅为接口设计,无实现。所有金额由服务端计算,私有接口校验 RBAC、租户与门店。列表分页,变更接口校验 version。

接口 要点
POST /v1/qr/resolve 输入不透明 locator;返回门店、来源、有效策略及受限 GuestSession
GET /v1/menu?locale=th 按会话确定门店;返回版本、语言回退标识、商品与规格
POST /v1/orders/quote 输入 SKU/规格/数量;返回 quote_id、明细、费用、有效期
POST /v1/orders quote_id、location_confirmed、submission_id;返回原订单或新订单
POST /v1/orders/:id/payment-attempts 只能支付本人可授权订单;绑定金额/币种/渠道
GET /v1/orders/:id 个人会话或店员授权,返回履约、支付与部门汇总
POST /v1/bills/:id/checkout 整桌账单授权;锁定 bill_version,返回结账凭证
POST /v1/merchant/orders/:id/accept 幂等接单,验证版本与营业规则
POST /v1/merchant/orders/:id/reject 原因、版本;已付款进入退款流程
POST /v1/refunds 权限、原支付、金额、原因、审批凭证与幂等键
PUT /v1/print-routes/:id 配置版本,目标门店及设备授权校验,草稿/发布
POST /v1/print-routes/simulate 输入示例订单,返回全单/部门行/异常预览,不出纸
POST /v1/devices/pair 一次性配对码,确认租户、门店授权、设备能力
POST /v1/devices/:id/heartbeat 凭证校验,报告能力、网络、可用状态与时间
POST /v1/print-jobs/lease 原子领取并续租,避免多代理同时处理
POST /v1/print-jobs/:id/result 回执等级、驱动结果、attempt_id;防伪造和重放
POST /v1/print-jobs/:id/reprint 角色、原因、引用原任务;生成新补打任务
POST /v1/webhooks/payments/:provider 按服务商规范认证、去重、快速响应与补偿

错误结构建议含 code、message_key、details、trace_id、retryable。典型码:LOCATION_DISABLED、SESSION_EXPIRED、PRICE_CHANGED、ITEM_SOLD_OUT、BILL_LOCKED、PAYMENT_PENDING、DEVICE_UNAUTHORIZED。日志不能包含支付密钥和房客敏感信息。

8. 非功能、安全与运维

9. 实施前验证与阶段门槛

阶段 输出与退出条件
客户确认 范围、支付模式、店型、路由规则、语言责任、UI 签字
样机与服务商验证 泰文打印、后台接单、网络中断、云回执、渠道沙箱/准入;形成证据矩阵
最小闭环开发 一店一桌码 → 菜单 → 付款/后付 → 接单 → 全单及部门打印
试点 多店共享、断网/缺纸/重复回调/迟到到账演练,对账无遗漏
扩展 更多支付、PMS、KDS、营销和其他国家

在客户确认与样机验证前,不据此给出固定开发工期、报价或设备采购承诺。