版本:V1.2
日期:2026-08-28
需求基线:2026-08-27-consumption-product-requirements.md V0.9.6
视觉基线:方案 B“金额聚焦型”定稿
本文件把已确认的产品需求和方案 B 原型转换为代码边界、状态机、数据结构与验证策略,作为本次代码实施的直接依据。
记录原因:当前项目中已经存在一版消费诊断页、读卡扣款和接口 C 代码。如果直接覆盖,容易丢失已验证的硬件实现;先建立实施设计,可以区分“复用、修正、新增”三类改动,并为后续真机问题定位保留依据。
PeripheralReader、ReaderFactory。POST /api/oneCardTerminal/acquireByPlate。ConsumptionActivity 承载三个互斥页面容器;业务上仍是三个页面阶段,避免三 Activity 之间丢失硬件会话。| 业务状态 | 用户展示状态 | 主要提示 | 允许的下一事件 |
|---|---|---|---|
WAITING_TRAY |
第一步 | 请放置餐盘 | 餐盘识别成功 |
LOADING_ORDER |
第二步 | 正在确认订单,请稍候 | 接口 B 成功/失败、餐盘移走 |
ORDER_FAILED |
第二步 | 用户可理解的订单异常 | 餐盘移走 |
WAITING_CARD |
第三步等待操作 | 放卡;遗留卡时先取卡 | 检测到卡片 |
READING_ZERO_AMOUNT_CARD |
第三步处理中 | 卡片核验中,请勿移动卡片 | 读卡成功/失败 |
READING_BEFORE、DEBITING、READING_AFTER |
第三步处理中 | 结算处理中,请勿移动卡片 | 成功/失败/结果不确定 |
REPORTING、COMPLETED、REPORT_PENDING |
第三步完成 | 结算或核验完成,请取走卡片和餐盘 | 清场后复位 |
TRANSACTION_FAILED |
第三步异常 | 结算未完成,取走卡片和餐盘后重试 | 餐盘移走 |
TRANSACTION_UNKNOWN |
第三步异常 | 结果待确认,禁止再次支付 | 餐盘移走 |
零元路径为 LOADING_ORDER → WAITING_CARD → READING_ZERO_AMOUNT_CARD → REPORTING;读取失败回到 WAITING_CARD,读取成功后不经过 DEBITING/READING_AFTER。正金额路径保持原三阶段。
ConsumptionUiStateMapper 是纯 Java 展示映射边界。ConsumptionFlowStateMachine 仍是唯一业务状态源;页面合并不影响阶段日志、持久化或接口 C。
终态复位规则:餐盘仍在时保持当前结果;餐盘已移走且读卡区无卡时,满足最短展示时间后回到 WAITING_TRAY。
TRAY_REMOVED 只把 tray_present 改为 false,不使接口 B 响应失效、不取消已武装交易、不打断硬件指令。这些场景必须保存本地交易阶段结果,其中结果不确定和部分扣款必须标记“禁止自动重扣”。
equipment_code。设备编号在餐盘会话开始时从 SETTING_TERMINAL_CODE 读取、去除首尾空白并锁定;接口 B 获取订单、零元反馈和正金额反馈共用同一快照。code=0、amount=0,只物理读卡一次,前后卡信息镜像同一真实读卡快照,不携带 PSAM/TAC。equipment_code。本地数据库以 order_no 为业务关联键,至少保存:
attempted=false 的扣款快照和空的物理后读卡快照;接口字段镜像只发生在请求构造层。记录原因:页面状态和日志不能承担应用重启后的恢复;结构化记录用于可靠补传、问题核对和避免重复扣款。
:app:testDebugUnitTest。:app:testDebugUnitTest :app:assembleDebug。ConsumptionTransactionGate 接受非负金额;负金额仍拒绝。金额为 0 只代表只读事务,不能触发消费。ConsumptionCardTransactionExecutor 先执行一次 readCard(expectedUid);金额为 0 时立即返回,consumptionAttempted=false,不调用 consumeSubsidyFirst,不执行第二次物理读卡。ConsumptionFlowStateMachine 使用独立的 READING_ZERO_AMOUNT_CARD 状态,读卡失败可安全回到 WAITING_CARD。OneCardDebitReportRequest.forZeroAmountOrder(orderNo, equipmentCode, cardInfo) 强制要求非空设备编号和卡信息,并在协议边界将同一快照映射到 before_card_info、after_card_info。ConsumptionActivity 仅在零元读卡成功后入队反馈;读卡失败保存 ZERO_AMOUNT_CARD_READ_FAILED 并等待移卡后重试。ConsumptionCardTransactionExecutor.ProgressListener 在首次完整读卡成功后通过默认方法上送 CardInfo,回调发生在消费指令之前;默认空实现保持已有阶段 Lambda 兼容。ConsumptionCardSearchManager 只对当前有效会话转发卡片快照,避免迟到回调污染下一笔订单。ConsumptionActivity 使用 customerId 从 SQLiteCustomerRepository 查询 NAME;未同步或本地查询异常只降级页面姓名,不阻断交易。结算后余额。读卡时余额;禁止使用计算余额冒充真实复读结果。