自助预订一体机
一体机是一种信任模型反转的预订网站:客户端默认未认证且物理共享,因此所有信任存在于服务器端(价格、容量、幂等性),所有状态都是可丢弃的(内存中、自动重置、从不持久化)。此技能中的所有内容都源自这两个事实。
何时使用
- 构建基于时段容量的自助预约终端(工位 × 时间窗口),支持可选设备和消耗品附加。
- 为已有在线预订的应用添加一体机模式。
- 对照下面硬规则审核现有的一体机;provenance.md 记录了每条规则背后的原因。
何时不使用
- 现场离线后备服务器——那是姊妹技能
island-mode-server;此技能仅定义一体机的客户端故障转移契约。 - 数字标牌/展示屏——不同的能力(播放,无输入),不同的技能。
- 面向员工的 POS 或管理预订工具——员工经过认证且可信;此技能的信任模型不适合他们。
- 普通预订网站——如果后端接缝有用可以借用,但一体机的机制(键盘、计时器、触摸强化)在那里是冗余的。
架构
KioskProvider(纯 Reducer:步骤、选择、购物车、sessionId)
│ props: 字典片段 hooks: pricing · equipment · availability
屏幕:service→date→time→participants→equipment→summary→confirmation
│ └─ 侧流:booking-lookup → booking-edit
▼ 一个 fetch 包装器(设备密钥 + 离线 base-URL 故障转移)
/api/kiosk/booking/{create,lookup,add-items,edit} ← 所有信任决策
▼ KioskBackend 接缝:服务器定价 · 促销 · 库存锁定(TTL 15 分钟)
事务容量重新检查 · 到店/在线支付 · webhook
实时:location.lastBookingChangeAt → 静默重新获取 → REFRESH_SLOT
关键事实
- 用户点击导航;后台刷新从不导航。
SET_SLOT与REFRESH_SLOT的区别在于实时可用性和在购买中途将客户从摘要页面传送开。 - 服务器为所有内容定价。 任何与金钱相关的数据都不超过入站线路;客户端总价仅为预览。
- 到店付款是弹性路径——预订确认时状态为
PENDING_COUNTER_PAYMENT,不涉及支付集成;一体机在线支付意味着客户扫描的 QR 码,绝不导航离开一体机。 - 每个文本输入都打开屏幕键盘 并设置
inputMode="none"。两者缺一不可,否则屏幕不可用。 - 库存锁定是 TTL 预留的(15 分钟)并在每个失败路径上释放——清理任务只是安全网,而非机制。
- 客户端 sessionId 是幂等性键。 双击、重试、响应丢失:最多一个预订。
硬规则
绝不持久化一体机会话状态。 下一个客户不应看到上一个客户的姓名。内存中 + 120 秒无操作重置 + 30 秒确认重置,并在硬件上验证。
绝不信任客户端的价格、容量或优惠有效性。 服务器计算每笔金额,并对无效优惠返回
PROMO_INVALID;一体机上的客户无法核对金额。
每个一体机请求使用一个 fetch 包装器。 包装器携带设备头部、错误封装和故障转移 base URL,因此认证、错误和离线故障转移同时适用于每个请求。
如果配置了设备密钥,缺少头部则返回 401。 仅在密钥错误时拒绝等同于无认证。
在获取库存锁后,在每个失败路径上释放它们。 保持预留的 409 错误会在整个 TTL 期间冻结库存。
快速开始
- 阅读 state-machine.md;实现 reducer 和 provider,运行其测试套件。
- 根据 screens.md 的契约,使用宿主的设计系统构建屏幕——键盘、计时器、触摸强化。
- 基于 booking-backend.md 接缝实现 api-contract.md 中的路由。
- 按照 realtime-offline.md 连接新鲜度和离线功能。
- 根据 operations.md 部署并进行冒烟测试。
适配契约
| 接缝 | 此技能提供 | 宿主提供 |
|---|---|---|
| 领域实体 | service/station/equipment/consumable + 重命名表 | 其术语(court, kart, bay…) |
| 租户范围 | locationId 在一体机 URL 中,服务器验证 | 其位置/场地模型 |
| 设备认证 | x-kiosk-api-key 契约 | 密钥管理;可选的每设备令牌 |
| 数据访问 | KioskBackend 接口;Firestore 参考 + SQL 草稿 | 其 ORM/SDK |
| 支付 | 到店状态 + checkout-session/webhook 契约 | Stripe 或等价物 |
| 实时 | SubscribeFreshness 接口 | Firestore/Supabase/SSE/轮询 |
| 离线 | NetworkStatus 契约 | 局域网服务器(island-mode-server)或无 |
| UI 原语 | 结构、状态、交互规则 | 按钮、对话框、旋转器、外观 |
| 样式 | 布局意图、触摸目标、一个 --primary 令牌 | 其设计系统 |
| 字符串 | 字典键树,所有键必需 | 其 i18n 文件,所有语言 |
| 验证 | 无依赖守卫 | 其模式库(如有) |
参考目录
| 场景 | 触发关键词 | 参考文档 |
|---|---|---|
| 状态、步骤、reducer、会话、测试 | step, reducer, GO_BACK, REFRESH_SLOT, sessionId, reset | state-machine.md |
| 屏幕、键盘、计时器、触摸、i18n 键 | on-screen keyboard, inactivity, countdown, QR, touch target, dictionary | screens.md |
| 路由、认证、验证、幂等性、错误 | x-kiosk-api-key, 401, 409, idempotency, PROMO_INVALID, lookup, PII | api-contract.md |
| 容量、库存、支付、webhook、定时任务 | transaction, stock lock, TTL, PENDING_COUNTER_PAYMENT, webhook, refund | booking-backend.md |
| 实时刷新、缓存、离线故障转移 | lastBookingChangeAt, onSnapshot, s-maxage, offline banner, LAN, health poll | realtime-offline.md |
| 部署、门控、运维界面 | Edge --kiosk, assigned access, heartbeat, feature gate, smoke test | operations.md |
| 模板为何与早期实现不同 | audit, ledger, kept deliberately, added | provenance.md |