TECHNICAL DEVELOPMENT DESIGN · VWCG-1119

消费限制按“受限账户是否有钱”生效

覆盖 store 消费机 HTTP API、MQTT,以及 ai_api 小程序订餐的双项目技术方案。

V1.2 兼容修订稿 2026-07-29 store + ai_api 风险:高

1. 修订结论

本版不再根据“本单最终扣了哪个账户”决定规则,而是在下单/支付前读取所选受限账户的可用余额。

统一判定
所选账户中任意一个可用余额大于 0,继续执行原有范围/次数限制;全部为 0 时跳过本条规则。

次数口径

继续按当前成功订单数统计,不按 cash/subsidy/gift 最终扣款拆分。

资金来源

规则只决定是否拦截,不修改营销价格、扣款顺序,也不自动换账户。

项目边界

消费机在 store;小程序订餐在 ai_api。只改一个仓库无法闭环。

赠送账户

依赖 VWCG-1121;迁移与全链路验证前保持置灰。

旧数据默认值
历史规则没有受限账户字段,统一默认个人账户 + 餐补账户;赠送账户不默认选中。

2. 业务规则

账户“有钱”定义排除项
cash个人活动本金余额 + 可提现余额;旧数据未拆分时兼容回退 balance透支额度默认不算
subsidy餐补判断时点有效、未删除且大于 0 的补贴余额之和未生效、过期、负数
gift赠送gift_balance 大于 0能力未开放时不可配置
  • 金额使用 BCMath 两位小数比较,禁止浮点直接判断。
  • 未选账户余额不参与规则是否生效。
  • 余额查询失败不能按 0 放行,应返回可重试错误。
  • 范围、月/日/餐次次数阈值保持现状。

3. 三类入口

入口真实调用链接入要求
消费机 HTTP APIConsume controller → orderPayAsync覆盖 Redis 快照与 DB 回退
消费机 MQTTMqttServer → orderPayAsync与 HTTP 共享 Service,不复制规则
小程序点餐MealCheckout / DirectPay → MealPayment / PaySuccess提交早检 + 支付最终复检
代码事实
store 只识别 source=4 的订餐订单,没有小程序提交入口;小程序必须在 ai_api 改造。

4. 目标架构

入口HTTP / MQTT / 小程序
身份映射staff、zhct user、AI user
余额快照cash / subsidy / gift
规则评估余额门禁 + 范围/次数
原支付流程创建订单与扣款

两个项目使用相同的评估输入、输出和契约样例。输入至少包含租户、人员、智慧餐厅用户、场景、档口、餐次、消费时间、余额快照和幂等号。

5. 数据与接口

规则账户关系

新增 ydy_consume_limit_account(limit_id, account_type, store_id, create_time),唯一索引为 (limit_id, account_type)。所有未删除历史规则(包括启用和停用)幂等回填 cash + subsidy,不自动回填 gift。

CRUD

新增/编辑传 accountTypes: ["cash","subsidy"];详情返回 account_types。必须为数组、去重后至少一项,并执行枚举白名单与赠送能力校验。

拦截日志

增加命中账户、脱敏余额快照和渠道,渠道取值 consume_api、consume_mqtt、mini_program。

四层兼容
① 数据库回填;② 详情缺关系时返回 cash+subsidy;③ 新增和旧数据编辑默认勾选个人+餐补;④ 运行期缺关系按 cash+subsidy 判断并告警。旧规则首次编辑后把默认值真实持久化。

6. store 改造

  1. 新增账户余额解析器,完成 staff_uuid → store user → ai_user_id 映射。
  2. 从 Consume.php 抽出消费限制评估器,按“账户余额交集 → 范围 → 次数”执行。
  3. HTTP 与 MQTT 只传渠道标识,统一在 orderPayAsync() 的支付锁内判断。
  4. 同时覆盖 handleAsyncSnapshotPayment() 和 runAsyncFallback() → orderPay()。
  5. 在线设备时间需要漂移校验;补贴有效期、次数边界和日志使用同一消费时间。
Redis 快照风险
当前 TTL 为 300 秒。应在支付锁内刷新账户余额,或严格保证限制判断和后续扣款使用同一份最新快照。

7. 小程序改造

  • MealCheckout::submit():创建订单前早检,避免产生不可支付订单。
  • meal/DirectPay::pay():创建订单和扣款前,在同一事务内最终检查。
  • cashier/MealPayment / meal/PaySuccess:支付前防御复检,覆盖历史待支付订单和旁路调用。
  • 0 元订单必须在自动支付成功前检查。
  • AI 用户映射不到智慧餐厅用户/人员时失败,不得按“无规则”放行。

双检查的目的:提交时尽早反馈,支付时处理余额、规则和时间变化,避免提交后充值或规则调整造成绕过。

8. 隐形 Bug 风险

风险隐形后果防护
只改 HTTP 或 MQTT上报方式不同,限制结果不同共享 Service + 双入口契约测试
300 秒旧快照充值/扣款/补贴变化后误判锁内刷新并复用同快照
只改 store小程序完全不判断ai_api 三个接入点同步改
透支算余额实际 0 余额仍触发规则默认排除透支
账户异常当 0跨库故障时规则绕过失败关闭、提示重试
只在提交检查支付时余额变化后绕过支付前最终复检
次数先查后写并发两单同时通过最后额度跨项目同一用户/场景锁
两个项目算法漂移消费机与小程序行为不一致共享版本化契约样例
0 元自动支付范围/次数被旁路自动支付前检查
只做前端默认API/任务链路仍把旧规则当空配置SQL、详情、前端、运行期四层兼容
并发门禁
锁名统一为 consume_limit:{storeId}:{zhctUserId}:{scenario},覆盖最终复检到成功订单落库。两项目 Redis 不共享时必须采用 mysql_zhct 数据库锁/守卫行。

9. 测试与发布

余额边界

个人拆分/旧值、有效与过期餐补、赠送开关、多选任一有钱、透支、查询失败。

渠道一致性

同一载荷走 HTTP 和 MQTT,除 channel/trace_id 外结果一致。

小程序

submit、0 元自动支付、余额支付、directPay、历史待支付复检。

并发

MQTT 与小程序并发争抢最后一次额度,只允许一单成功。

上线顺序

  1. 完成 VWCG-1121 账户迁移,gift 未就绪则保持禁用。
  2. 创建关系表、日志字段,为全部未删除旧规则幂等回填 cash+subsidy。
  3. 验证重复回填无重复数据,旧规则详情默认勾选个人+餐补,gift 不默认选中。
  4. 部署 store 后端,再部署 ai_api,最后开放 PC 表单。
  5. 内部门店同时验证 HTTP、MQTT、小程序,观察后灰度。

本需求不改变订单列表字段或导出口径,九类异步导出无需同步修改。

10. 待确认项

P0
多选账户触发逻辑

本文按“任一所选账户有可用余额即生效”设计。

P0
透支口径

本文默认透支额度不属于“账户有钱”。

P0
跨项目锁

确认 store 与 ai_api 是否共享 Redis;否则采用 mysql_zhct 数据库锁。

P0
0 元订单

本文默认也执行范围与次数限制。

P1
设备时间与离线补传

确认在线时间漂移窗口;离线补传默认仍不实时拦截。

开发门禁
方案可实现,但属于双项目、高一致性改造。P0 口径、跨项目锁方案和赠送账户依赖未关闭前,不建议直接上线。