DishRecognitionActivity 刷卡/扫码支付完整业务流
1. 目标
在 DishRecognitionActivity 内把刷卡、扫码支付能力收口到识别页本身,形成一套稳定的单页流程:
- 页面初始化时即完成刷卡、扫码能力初始化
- 初始状态下不允许刷卡、扫码
- 当支付状态进入“可支付”时,刷卡、扫码同时可用
- 当
autoPayReady == true 时,立即暂停预览和菜品识别
- 刷卡、扫码任一先返回结果,立即暂停两种输入,直接发起支付
- 从
autoPayReady == true 开始,到支付结果弹框消失前,页面持续停留在支付态
- 支付结果弹框消失后,页面再统一复位到“可预览 / 不可支付”,等待下一次识别
2. 状态拆分
建议把识别页支付相关状态拆成三层:
2.1 支付可用状态
autoPayReady
- 当前订单是否达到“可支付”条件
- 条件仍沿用现有规则:
- 识别结果连续稳定达到支付设置中配置的阈值
- 存在有效识别菜品
- 不包含未识别菜品
- 订单金额大于 0
- 当前没有暂停原因
- 当前不在支付中
- 额外约束:
- 一旦
autoPayReady == true,立即暂停预览和菜品识别
- 当前识别结果视为锁单结果,不再继续刷新
2.2 支付输入监听状态
cardListening
scanListening
注意:
- “已初始化”与“正在监听”是两回事
- 页面初始化时就创建
CustomNfcUtils 和 UsbHidScannerHelper
- 但只有进入“可支付”状态时,才真正允许卡/码输入生效
2.3 可支付阈值配置
- “连续多少次稳定识别后进入可支付状态”不再写死
- 该值由支付设置页动态配置
- 提供三档:
- 默认值:
DishRecognitionActivity 每次判断 autoPayReady 时都实时读取当前配置
说明:
- 这三档按当前产品约定直接落地为
3 / 5 / 7
- 即使文案是“慢 / 中 / 快”,实现仍严格以这三个数值为准
2.4 支付流程状态
PaymentFlowState.IDLE
PaymentFlowState.WAITING_INPUT
PaymentFlowState.PAYING
PaymentFlowState.SHOWING_RESULT
3. 页面初始化
进入 DishRecognitionActivity 后,除现有识别页初始化外,还需要增加:
- 初始化扫码能力
- 创建隐藏的扫码输入框
- 初始化
UsbHidScannerHelper
- 注册扫码回调
- 初始化刷卡能力
- 初始化
CustomNfcUtils
- 注册 NFC 读卡回调
- 生命周期管理
onResume():启用 NFC foreground dispatch
onPause():关闭 NFC foreground dispatch
onDestroy():释放扫码/NFC 资源
初始化完成后的页面默认状态:
- 可预览
- 不可支付
- 不监听刷卡
- 不监听扫码
- 等待识别结果稳定
4. 支付状态从“不可支付”进入“可支付”
当识别结果满足“连续稳定达到当前配置阈值”的条件后:
- 页面支付状态切到“可支付”
- 立即暂停预览和菜品识别
- 预览区保留进入可支付状态时的最后一帧画面
- 同步开启支付输入监听
- 如果设置中开启了刷卡支付,且设备 NFC 可用,则允许刷卡结果生效
- 如果设置中开启了扫码支付,则启动扫码等待
- 当前识别结果、金额、加菜内容保持不变
说明:
- 这里不是“自动直接付款”
- 而是“进入可支付后,锁住当前订单,再等待卡/码输入”
- “保留最后一帧画面”不是依赖底层相机继续保帧
- 实现上应在进入可支付状态时,先抓取当前
TextureView 画面,显示到预览覆盖层,再暂停真实预览
- 这样即使底层相机停止后清黑,界面上仍然展示进入支付态时的那一帧
5. 刷卡 / 扫码竞争规则
进入 WAITING_INPUT 状态后:
- 刷卡和扫码同时可用
- 谁先拿到结果,谁赢
- 另一通道立刻停掉
具体执行顺序:
- 收到刷卡结果或扫码结果
- 校验当前仍处于
WAITING_INPUT
- 立即停止刷卡监听
- 立即停止扫码监听
- 固定当前订单金额、菜品列表和支付介质数据
- 切到
PAYING
- 调
PaymentManager.requestPayment()
这样可以避免:
- 连续扫两次码触发两笔支付
- 扫码和刷卡同时上报导致重复支付
6. 支付中展示规则
从 autoPayReady == true 开始,到支付结果返回前:
- 不再接受新的刷卡、扫码输入
- 自动识别逻辑暂停,避免订单内容继续变化
- 相机预览暂停,不再继续更新画面
- 预览区保留进入支付态前的最后一帧
- 页面顶部状态展示为支付态 / 暂停识别
也就是说,用户看到的页面仍然是:
- 预览状态为“暂停识别”,但界面仍保留最后一帧画面
- 支付状态保持“可支付”或后续支付结果展示态
同时内部已经锁单,不再允许新输入、不再继续识别和新支付。
7. 支付结果展示规则
支付结果返回后:
7.1 成功
- 弹出成功弹框
- 如果是离线记账成功,则弹出“记账成功”标题的成功弹框
7.2 失败
7.3 弹框显示期间
以下状态都不允许变化:
- 预览继续保持暂停
- 支付状态展示不变
- 当前识别结果不清空
- 当前金额不清空
- 不恢复刷卡监听
- 不恢复扫码监听
- 不允许发起新支付
对应状态:
paymentResultShowing = true
PaymentFlowState = SHOWING_RESULT
- 增加暂停原因
PAYMENT_RESULT_DIALOG
8. 何时恢复到下一单
不是拿到支付结果就立刻恢复,而是:
- 等支付结果弹框自动消失后
- 再统一复位到下一单的初始状态
复位动作:
- 关闭支付结果展示态
- 清掉本单支付上下文
- 清空识别结果
- 清空加菜
- 清空固定金额
- 清空稳定识别计数
- 清空上次识别快照
- 停止刷卡监听
- 停止扫码监听
- 恢复为:
9. 关键边界
9.1 订单内容变化
以下任一变化都要立刻取消“可支付”资格,并停止卡/码监听:
处理方式:
- 清空稳定计数
- 清空识别快照
autoPayReady = false
- 停止刷卡/扫码监听
补充:
- 如果用户在设置页把阈值从一种档位切到另一种档位,新的阈值对后续判定立即生效
- 默认档位为中档(5)
9.2 页面切后台
- 页面进入后台时停止刷卡/扫码监听
- 页面恢复时重新根据当前状态决定是否恢复监听
9.3 支付中防重入
当处于以下任一状态时,必须拦截新的支付:
9.4 人脸支付兼容
底部按钮仍可保留“人脸支付”入口:
- 当支付状态可用时,允许点击进入人脸识别
- 人脸识别成功后同样走识别页内支付
- 不再跳转到独立支付测试页
10. 建议代码落点
10.1 DishRecognitionActivity
负责:
- 初始化刷卡/扫码能力
- 根据
autoPayReady 控制监听开关
- 根据支付设置动态读取“稳定识别阈值”
- 在进入可支付状态时抓取当前预览帧并显示到覆盖层
- 处理刷卡/扫码回调
- 直接发起支付
- 展示支付结果弹框
- 在结果弹框消失后统一复位页面
补充:
- 预览冻结建议由“截图覆盖层”实现,而不是只调用底层暂停预览
- 原因是部分设备在
TextureView 对应的预览停止后会直接清黑
10.2 UsbHidScannerHelper
负责:
- 管理扫码输入框
- 一次等待一笔扫码
- 回传结果后自动停止本次扫码
10.3 CustomNfcUtils
负责:
10.4 PaymentManager
负责:
- 统一发起 HTTP / MQTT 支付
- 回传成功 / 失败 / 加载状态
11. 最终用户视角业务流
- 进入识别页
- 刷卡、扫码能力已初始化,但此时不可用
- 识别结果持续稳定,并达到支付设置页配置的阈值
autoPayReady = true,页面进入“可支付”
- 立即暂停预览和菜品识别,锁住当前订单
- 界面保留进入可支付状态时的那一帧画面
- 开始等待刷卡或扫码
- 任一先出结果
- 立即停止刷卡和扫码
- 发起支付
- 支付中继续保持预览暂停、识别暂停,并保留最后一帧
- 显示支付成功 / 记账成功 / 支付失败弹框
- 弹框显示期间页面仍保持暂停
- 弹框消失
- 页面恢复为“可预览 / 不可支付”
- 等待下一次识别