| 项目 | 内容 |
|---|---|
| 产品 | 智慧营养健康餐厅绑盘机 |
| 模块 | 消费结算 |
| 文档版本 | V0.9.6(三步页面简化与持卡人余额展示) |
| 日期 | 2026-08-28 |
| 需求来源 | 用户口述需求、现有绑盘三步流程、消费页现状、新开普一卡通接口 B/C |
| 目标读者 | 产品、UI、Android、后端、测试、交付实施 |
| 下一阶段 | 方案 B 视觉定稿后进入技术方案与开发计划 |
当前消费页已经具备餐盘识别、订单查询、一卡通读卡与扣款的基础能力,但现有页面偏调试展示,流程、异常提示和结算反馈仍不够产品化。本需求将消费过程整理为与绑盘页面一致的三步引导流程,并围绕“一笔订单只能安全结算一次”建立完整业务闭环。
业务目标:
| 术语 | 定义 |
|---|---|
| 餐盘会话 | 从识别到一只餐盘开始,直到该订单结束并允许识别下一只餐盘的一轮流程。 |
| 锁盘 | 锁定当前餐盘会话,暂不接收或处理其他餐盘码。餐盘物理移走不等于会话结束。 |
| 前读卡 | 扣款前完整读取卡片身份、余额和消费计数。 |
| 零元读卡 | 零元订单读取一次完整卡片信息,仅用于人员与卡片核验,不发送消费或写卡指令。 |
| 扣款结果 | 一卡通消费结果,可包含补贴钱包和主钱包两段成功交易。 |
| 后读卡 | 扣款结束后再次完整读取卡片,用于确认余额和消费计数。 |
| 扣款前失败 | 扣款指令尚未发送,可以确认没有发生扣款,例如前读卡失败、提前移卡、余额不足或扣款前读卡器断开。 |
| 扣款明确失败 | 扣款指令已经执行并返回明确失败,且一卡通结果能够确认实际扣款金额为 0。 |
| 结果不确定 | 扣款指令已发送,但无法确认是否已经写卡成功的状态。 |
| 交易结果反馈 | 调用接口 C,将零元结算、扣款成功或扣款明确失败结果提交给业务服务端。 |
| 上传失败 | 接口 C 网络不可用、超时、HTTP 失败、响应解析失败或业务返回失败;不等于一卡通扣款失败。 |
| 新卡入场 | 第三步先确认读卡区无卡,再检测到一次新的放卡事件;用于避免误扣上一位用户遗留的卡片。 |
| 卡片保护窗口 | 从检测到新卡并开始前读卡,直到后读卡完整成功的一段时间;窗口内不得拿走卡片。 |
| 安全终态 | 当前订单已确定未扣款、已完成结算、仅待反馈,或已转入禁止重扣的人工核对状态。 |
| 统一复位 | 业务已进入安全终态后,根据餐盘、卡片和结果展示条件释放会话并返回第一步。 |
amount = 0 为零元单,不进行一卡通消费,但必须进入第三步等待新卡、读取一次完整卡片信息后调用接口 C。amount > 0 进入一卡通支付流程;amount < 0 或金额缺失视为无效订单。amount 传实际扣款总额,psamID/psamJyNo/tac 取最后一段成功交易。Access-Token。meal_model = 4 时启用,本功能也只在该模式下开放。customerId 查询本地人员库,未同步时不得阻断交易。采用“单订单会话状态机 + 三页展示”的方案。三个页面只是同一订单状态的不同展示阶段,订单号是贯穿订单、卡片交易、本地记录和接口反馈的唯一业务关联键。
相较于“每页独立处理”或“一个页面堆叠全部状态”,该方案更适合处理餐盘提前移除、网络迟到、卡片移除和扣款后反馈失败,能避免页面切换造成交易状态丢失。
以下差距已经通过当前源码核对,后续不能只做页面换肤:
ConsumptionOrderCoordinator.onTrayRemoved() 会让未返回的订单请求失效;本需求要求查询继续并锁定本轮会话。flowchart TD
A[第一步:等待放盘] -->|识别成功并锁盘| B[第二步:获取订单]
B -->|订单请求中| C{餐盘是否拿走}
C -->|否| D[继续查询,不提示餐盘动作]
C -->|是| E[记录已移盘但流程继续]
D --> F{订单金额}
E --> F
F -->|无效或查询失败| G[展示异常并进入统一复位]
F -->|0 元或大于 0| I[进入第三步卡片结算]
I --> L{读卡区是否已有卡}
L -->|是| L1[提示先拿走遗留卡片]
L1 -->|检测到无卡| L2[等待新卡放入]
L -->|否| L2
L2 -->|新卡放入| M[读取完整卡片信息]
M -->|0 元| H[跳过扣款并反馈携卡零元结果]
H -->|反馈成功| J[零元完成]
H -->|反馈失败| K[保存原请求待补传]
M -->|大于 0| N[一卡通扣款]
N --> O[后读卡]
N -->|明确失败且实际扣款为 0| W[接口 C 反馈扣款失败]
O -->|成功并校验通过| P[结束卡片保护窗口并反馈接口 C]
P -->|反馈成功| Q[支付完成]
P -->|反馈失败| R[扣款已完成,仅重试反馈]
M -->|卡片移除或失败| S[确定未扣款,中断支付]
N -->|卡片移除或结果不确定| T[禁止再次扣款,进入异常核对]
O -->|卡片移除或失败| U[扣款可能已完成,禁止再次扣款]
J --> V[WAITING_RESET:统一复位]
G --> V
K --> V
Q --> V
R --> V
S --> V
T --> V
U --> V
W -->|上传成功或失败已落库| V
V -->|复位条件满足并释放锁盘| A
明确告诉用户把餐盘放到识别区,并等待系统自动识别。
请放置餐盘请将餐盘平稳放入识别区域让用户知道系统正在确认订单,并展示最终订单号和金额。
正在获取订单正在根据餐盘信息查询待结算订单tray_removed = true,页面改为:餐盘已取走,正在继续确认订单。| 条件 | 处理 |
|---|---|
amount = 0 |
显示“本单无需支付”,自动进入第三步等待放卡核验;读卡成功后调用接口 C。 |
amount > 0 |
显示金额并自动进入第三步执行卡片支付,不提示餐盘动作。 |
amount < 0、缺失或类型错误 |
显示“订单金额异常,请联系工作人员”,禁止支付。 |
本单金额为 0 元,请放卡核验,读取过程中提示 零元核验中,请勿移动卡片。结算完成,请取走卡片和餐盘。结算结果正在补传,不得重新读卡或扣款。| 异常 | 页面与流程 |
|---|---|
| 网络不可用、超时、服务端失败 | 显示“订单获取失败,请联系工作人员”;可按可配置策略重试查询,但同一时刻只能有一个请求。 |
| 未查询到订单 | 显示“未查询到待结算订单,请联系工作人员”。 |
| 缺少设备编号 | 显示“设备未完成配置,请联系工作人员”。 |
| 返回订单号为空 | 视为无效订单,不进入第三步。 |
| 餐盘仍在 | 停留异常结果,餐盘拿走后复位。 |
| 餐盘已拿走 | 异常提示短暂展示后复位;迟到响应不得重新打开旧会话。 |
引导用户放置卡片。零元单完成只读核验,正金额完成一卡通支付;卡片处理期间清晰说明不可移卡。
请放卡片进行支付本单金额为 0 元,请放卡核验;不播放带“支付”含义的语音。请将卡片平放在读卡区域。请先拿走卡片,此时禁止读卡和扣款。readCard,保存卡片身份、余额和消费次数。before_card_info 与 after_card_info 使用同一真实读卡快照;这是无写卡场景的协议镜像,不代表发生了第二次物理读卡。页面固定展示:
支付处理中,请勿拿走卡片正在读取卡片信息正在进行扣款扣款完成,正在确认卡片余额卡片读取完成,正在确认结算结果交易开始后隐藏或禁用所有可能重新发起支付的控件。餐盘在此阶段拿走不取消交易,只记录状态用于完成后的页面收尾。
后读卡完整成功并通过卡片一致性、金额及余额校验后,卡片保护窗口结束,页面改为 卡片读取完成,可拿走卡片。随后接口 C 的网络反馈可在卡片已经拿走的情况下继续,不得因移卡取消反馈或把反馈失败误判成扣款失败。
只有以下条件全部满足,页面才能显示“支付成功”:
code = 0。成功文案:支付成功,请拿走卡片。卡片拿走后:
请拿走餐盘,餐盘拿走后回到第一步。扣款已完成,结算结果正在补传,请勿再次支付。DEBITED_REPORT_PENDING。| 当前阶段 | 餐盘拿走后的处理 |
|---|---|
| 第一步等待放盘 | 无当前会话,不处理。 |
| 餐盘刚识别、尚未请求订单 | 会话继续,仍请求订单。 |
| 订单请求中 | 请求不中断,记录已移盘,锁盘持续。 |
| 零元读卡或反馈中 | 读卡任务/反馈不中断;成功或进入补传后可复位。 |
| 等待卡片 | 卡片会话保留,继续等待卡片。 |
| 前读卡、扣款、后读卡 | 不强制中断硬件交易,只记录已移盘。 |
| 结算反馈中 | 反馈不中断。 |
| 成功或失败终态 | 进入统一复位;餐盘已拿走时按最短结果展示时长返回第一步,餐盘仍在时可提示拿走餐盘。 |
锁盘释放条件:订单进入安全终态,且页面已经完成当前结果展示。单纯检测到餐盘物理移走不能提前释放锁盘。
锁盘范围是完整餐盘会话,而不只是订单查询阶段。即使订单金额已经判断完成,只要支付、反馈、异常落库或统一复位尚未达到安全条件,就不得接收下一只餐盘建立新会话。
| 中断点 | 扣款风险 | 页面提示 | 后续处理 |
|---|---|---|---|
| 放卡后、前读卡尚未完成 | 确定未扣款 | 卡片已拿走,未读取到完整卡片信息 |
中断本次支付。餐盘未拿走则保持此状态,拿走后复位;默认不在原会话自动重新寻卡。 |
| 前读卡成功、扣款指令尚未发送 | 确定未扣款 | 卡片已拿走,尚未扣款 |
同上;记录前读卡和失败阶段。 |
| 扣款指令返回明确失败且实际扣款为 0 | 明确未扣款 | 支付未完成,请取走卡片和餐盘后重新操作 |
记录一卡通失败结果,按失败格式调用接口 C;上传结果不阻塞统一复位。 |
| 扣款指令发送后、未确认结果 | 结果不确定 | 卡片已拿走,扣款结果待核对,请勿再次支付 |
立即阻止后续钱包扣款和整单重试,保存异常记录,进入人工核对。 |
| 部分钱包扣款成功、后续钱包失败 | 已发生部分扣款 | 已扣款部分金额,后续扣款未完成,请勿再次支付 |
保存每段成功结果和失败原因,不自动补扣剩余金额。 |
| 全部扣款成功、后读卡失败 | 已完成扣款 | 扣款已完成,但余额确认失败,请勿再次支付 |
保存前读卡和扣款结果;不得重新扣款,等待异常补偿或人工处理。 |
| 后读卡成功、接口 C 反馈失败 | 已完成扣款 | 扣款已完成,结算结果正在补传 |
仅重试反馈。 |
接口 C 服务端不校验前后卡片是否属于同一人员,也不校验反馈扣款金额是否等于订单金额,因此客户端在显示成功和调用成功反馈前必须完成:
uid、customerID、cardNO、cardASN 分别一致;任何关键身份字段缺失或不一致,都不得按成功处理,进入 DEBITED_VALIDATION_FAILED 并禁止再次扣款。卡内余额不足。POST /api/oneCardTerminal/acquireByPlateapplication/jsonplate_code:餐盘码。equipment_code:设备编号。order_no:订单号。amount:订单金额,单位分。客户端要求:同一餐盘会话只允许一个有效请求;换盘或页面销毁后的迟到响应不得覆盖新会话。订单号是服务端生成的唯一业务标识,接口 B 按其既有定义只返回待结算订单;本期不增加跨设备并发结算设计。
meal_model = 4。POST /api/oneCardTerminal/reportapplication/jsonorder_noequipment_code:必填,使用餐盘会话开始时锁定的当前设备编号。settlement_resultbefore_card_infoafter_card_info{
"order_no": "订单号",
"equipment_code": "设备编号",
"settlement_result": {
"code": 0,
"amount": 0
},
"before_card_info": { "customerID": 29373, "cardNO": 1705827, "...": "完整读卡字段" },
"after_card_info": { "customerID": 29373, "cardNO": 1705827, "...": "与前读卡相同" }
}
before_card_info 与 after_card_info 镜像同一真实快照,两个对象都不得为空。settlement_result 不携带 psamID、psamJyNo、tac。equipment_code;接口 C 入队后,后台补传复用原始请求中的设备编号,不重新读取配置。settlement_result.code = 0。settlement_result.amount 为实际扣款总额。before_card_info 为消费前完整读卡信息。after_card_info 为消费后完整读卡信息。psamID/psamJyNo/tac 取最后一段成功交易。settlement_result.code 使用一卡通返回的非零失败码,message 使用实际失败原因,amount = 0;不得编造失败码或用接口上传错误替代交易失败码。before_card_info,没有可用前读卡信息时传空对象 {}。after_card_info 传空对象 {}。order_no。code = 0 为准,不能只判断 HTTP 状态。code != 0;以上情况必须保存最近失败原因,但不得重新扣款或阻塞下一笔订单。order_no 和原始请求快照,不创建新订单号或新结算记录。每笔订单建立一条可持续更新的本地交易记录,至少包含:
trace_id。REPORTED,停止重试并保留审计记录。| 重启前最后持久化状态 | 恢复处理 |
|---|---|
ORDER_LOADING |
丢弃未完成的前台请求;不自动恢复旧页面,后续重新放盘时再查询。 |
WAITING_CARD、前读卡未完成且未发送扣款 |
可安全结束旧会话,不自动寻卡或扣款。 |
| 已写入扣款意图,但无确定硬件结果 | 恢复为 DEBIT_UNKNOWN,禁止重新扣款并进入人工核对。 |
| 部分扣款或扣款完成但后读卡失败 | 恢复对应异常状态,禁止重新扣款。 |
| 接口 C 请求快照已保存但未确认成功 | 恢复持久化补传,仅重放原请求。 |
COMPLETED、REPORT_PENDING、DEBITED_REPORT_PENDING |
已结算订单永不重新扣款;待反馈状态继续后台补传。 |
恢复判断只能依据已持久化事务记录,不能依据 Activity、Fragment、内存标志或语音播放状态。
| 状态 | 含义 | 可进入状态 |
|---|---|---|
WAITING_TRAY |
等待餐盘 | ORDER_LOADING |
ORDER_LOADING |
已锁盘,正在获取订单 | WAITING_CARD_CLEAR、WAITING_CARD、ORDER_ERROR |
WAITING_CARD_CLEAR |
第三步检测到遗留卡,等待读卡区无卡 | WAITING_CARD、CANCELLED |
WAITING_CARD |
读卡区已确认无卡,等待一次新卡放入 | READING_ZERO_AMOUNT_CARD、READING_BEFORE、WAITING_CARD_TIMEOUT、CANCELLED |
READING_ZERO_AMOUNT_CARD |
零元单读取一次完整卡片信息,不写卡 | REPORTING、WAITING_CARD |
WAITING_CARD_TIMEOUT |
扣款前等待新卡超时 | WAITING_RESET |
READING_BEFORE |
前读卡 | DEBITING、CARD_INTERRUPTED_NO_DEBIT |
DEBITING |
一卡通扣款 | READING_AFTER、DEBIT_FAILED_REPORTING、PARTIAL_DEBIT、DEBIT_UNKNOWN |
READING_AFTER |
后读卡及前后卡、金额、余额校验 | REPORTING、DEBITED_AFTER_READ_FAILED、DEBITED_VALIDATION_FAILED |
REPORTING |
零元或正金额成功结果反馈中 | COMPLETED、REPORT_PENDING、DEBITED_REPORT_PENDING |
CARD_INTERRUPTED_NO_DEBIT |
卡片在扣款指令前移除,确定未扣款 | WAITING_RESET |
DEBIT_FAILED_REPORTING |
扣款指令明确失败且实际扣款为 0,正在反馈接口 C | DEBIT_FAILED、FAILED_REPORT_PENDING |
DEBIT_FAILED |
扣款明确失败且接口 C 已上传成功 | WAITING_RESET |
FAILED_REPORT_PENDING |
扣款明确失败,接口 C 请求待补传 | WAITING_RESET,后台继续 |
REPORT_PENDING |
零元结果待补传 | WAITING_RESET,后台继续 |
DEBITED_REPORT_PENDING |
已扣款、结果待补传 | WAITING_RESET,后台继续 |
PARTIAL_DEBIT |
部分钱包扣款成功 | WAITING_RESET,人工核对 |
DEBIT_UNKNOWN |
扣款结果不确定 | WAITING_RESET,人工核对 |
DEBITED_AFTER_READ_FAILED |
扣款完成但后读卡失败 | WAITING_RESET,人工核对或专用补偿 |
DEBITED_VALIDATION_FAILED |
后读卡完成但身份、金额或余额校验不通过 | WAITING_RESET,人工核对 |
COMPLETED |
服务端确认结算成功 | WAITING_RESET |
ORDER_ERROR |
订单无效或查询失败 | WAITING_RESET |
CANCELLED |
未扣款前安全取消 | WAITING_RESET |
WAITING_RESET |
安全终态结果展示与设备清场 | WAITING_TRAY |
任何带 DEBITED、PARTIAL_DEBIT 或 DEBIT_UNKNOWN 的状态都必须设置 debit_retry_forbidden = true。WAITING_RESET 只有在本地终态记录落库成功、最短展示时长已满足、卡片不处于保护窗口且需要清场的卡片/餐盘已移除后,才能释放锁盘并进入 WAITING_TRAY。
| 场景 | 页面主文案 | 语音 |
|---|---|---|
| 第一步 | 请放置餐盘 | 可沿用放盘提示音,是否播放由原型评审确定 |
| 订单查询 | 正在确认订单;请稍候 | 无 |
| 查询时已移盘 | 正在确认订单;请稍候 | 无 |
| 零元等待放卡 | 请放卡核验;只读取卡片信息,不会扣款 | 无 |
| 零元读卡及反馈 | 卡片核验中;请勿移动卡片 | 无 |
| 零元完成或待补传 | 核验完成;请取走卡片和餐盘 | 可选成功提示音 |
| 正金额进入支付 | 请放卡片;请将卡片平放在读卡区域 | 请放卡片进行支付 |
| 读卡区已有卡 | 请先拿走卡片 | 无 |
| 等待卡片 | 请放卡片;请将卡片平放在读卡区域 | 请放卡片进行支付 |
| 等待卡片超时 | 等待支付超时,请取走餐盘后重新操作 | 可选异常提示音 |
| 前读卡、扣款、后读卡 | 结算处理中;请勿移动卡片 | 无 |
| 反馈中、成功或待补传 | 结算完成;请取走卡片和餐盘 | 可选成功提示音 |
| 确认未扣款 | 结算未完成;请取走卡片和餐盘后重新操作 | 异常提示音可选 |
| 结果不确定 | 结算结果待确认;请勿再次支付,并联系工作人员 | 异常提示音可选 |
首次完整读卡成功后,第三步原页面显示持卡人、补贴余额和现金余额。首次快照标记为读卡时余额,零元标记为卡片余额,正金额完整成功后更新为结算后余额。
所有用户可见文案避免“刷卡”“技术错误码”“APDU”“PSAM”等技术词汇。管理员诊断入口可查看详细阶段和错误码,但不在主流程页面展示。
COMPLETED、DEBITED_REPORT_PENDING、PARTIAL_DEBIT 或 DEBIT_UNKNOWN 时,不得再次扣款。DEBIT_UNKNOWN,禁止再次扣款。以下内容是为保证业务完整性补充的默认方案,若后续没有相反确认,原型和技术方案按此执行:
以下事项不阻塞方案 B 定稿与主流程开发,但会影响最终运维和异常处置范围:
DEBIT_UNKNOWN、PARTIAL_DEBIT、DEBITED_AFTER_READ_FAILED 的后台人工处理入口由哪个系统提供。原型必须至少覆盖以下可切换场景:
原型视觉需延续绑盘页面的三步结构、标题区、主插画和大字号提示,但不能照搬当前消费调试面板。主流程只保留用户需要理解的信息,详细订单和硬件数据通过管理员诊断入口查看。