设备交易不能只用“支付成功”表示完成。钱由支付渠道确认,命令由 MQTT Broker 投递,实体出币由设备执行,这三个结果来自不同系统,必须分别记录并最终对账。

完整时序

sequenceDiagram
  participant U as 微信用户
  participant API as 业务 API
  participant PAY as 微信支付
  participant DB as MySQL
  participant GW as MQTT 发布服务
  participant MQ as MQTT Broker
  participant DEV as 街机设备
  participant RX as 回执接收器
  U->>API: 扫码并选择币价
  API->>DB: 创建待支付订单与价格快照
  API->>PAY: 创建支付单
  PAY-->>U: 返回支付参数
  U->>PAY: 完成支付
  PAY->>API: 支付回调
  API->>DB: 验签、核对金额、标记已支付
  API->>DB: 创建 MQTT 命令记录
  API->>GW: IMEI、币数、命令 ID
  GW->>MQ: 发布设备 Topic
  MQ-->>DEV: 投币命令
  DEV->>MQ: 命令 ID 与执行结果
  MQ->>RX: 回执或 Broker Webhook
  RX->>DB: 幂等写回命令和订单状态
  RX->>DB: 写代理收益流水
  API-->>U: 查询到最终结果

1. 扫码与设备校验

二维码或页面参数提供设备 IMEI。服务端根据 IMEI 查询设备,并校验:

  • 设备存在且属于允许投币的设备类型;
  • 设备已经激活,没有被停用或迁出当前渠道;
  • 当前价格来自该设备配置或有效默认配置;
  • 选择的价格和币数关系仍然启用;
  • 用户身份来自可信微信会话,不直接信任页面提交的用户 ID。

在线状态可以作为提示,但不能代替最终设备回执。设备刚刚断线时,缓存中的在线状态可能尚未更新。

2. 创建支付订单

业务 API 先创建自己的订单,再向微信支付创建预支付单。订单至少保存:

字段 用途
平台订单号 全链路业务幂等键
用户与微信应用 确认支付身份和渠道
设备 ID 与 IMEI 固化目标设备
代理、场所和渠道 固化收益归属
价格项、金额和币数 防止支付后配置变化
支付状态与设备状态 分别表达资金和履约结果
创建与过期时间 关闭超时订单并限制重放

调用支付渠道失败时,订单保留为创建失败或待重试,不删除记录。客户端重新提交时按业务幂等键返回原订单,避免生成多个可支付订单。

3. 处理支付回调

支付回调按照以下顺序处理:

  1. 验证签名、商户身份和回调格式;
  2. 用平台订单号锁定订单;
  3. 核对支付金额、币种和支付方应用;
  4. 已处理的支付单直接返回成功;
  5. 保存第三方支付编号和支付时间;
  6. 把订单从待支付转换为已支付、待出币;
  7. 提交设备命令任务;
  8. 快速向支付平台返回成功。

回调接口不应等待设备执行完毕。设备可能离线数分钟,而支付渠道要求回调快速应答。发送命令和设备确认属于支付后的异步履约阶段。

4. 创建并发布 MQTT 命令

系统在数据库先创建命令记录,再进行网络发布。命令包含平台命令 ID、设备 IMEI、币数、关联订单、业务类型、请求时间和当前状态。

1
2
3
created -> publishing -> published -> acknowledged
\-> publish_failed
published -> timeout -> retrying / manual_review

发布服务只接受内网请求或经过服务签名的请求。Topic 由服务端根据 IMEI 生成,调用方不能任意指定 Topic。消息中只传命令 ID、动作和必要参数,不携带用户、支付或代理的完整信息。

HTTP 发布成功只说明消息已经交给 Broker,不表示设备已经出币。因此此时订单保持“待设备确认”。

5. 设备执行与回执

设备收到命令后进行本地校验并驱动投币器。无论成功或失败,回执都应带回命令 ID、设备身份、结果码和设备时间。接收器完成以下校验:

  • Topic 中的 IMEI、Broker 提供的 Client ID 和原命令设备一致;
  • 命令存在且尚未得到最终响应;
  • 回执结果属于协议定义的合法值;
  • 回执没有超过允许的业务时限;
  • 相同命令的重复回执不会再次改订单或结算。

设备还会报告上线、离线、缺货或卡币等运行状态。这些状态更新设备运行投影和在线日志,但不能伪造成订单成功回执。

6. 完成订单与代理结算

设备成功回执后,在一个事务中完成:

  1. 将 MQTT 命令标记为已确认;
  2. 将出币订单标记为设备成功;
  3. 根据订单价格和分成快照计算各级代理收益;
  4. 写代理收益流水;
  5. 更新用于展示的收益汇总;
  6. 写订单事件和完成时间。

收益流水使用“订单 + 代理 + 收益类型”作为唯一业务键。回执重放、进程重启或人工补偿都不能重复增加收益。

如果设备明确失败,订单进入可重试或退款状态;如果长时间没有回执,则进入超时待核对,不能自动假设成功或直接再次出币。

账户游戏币投币

用户已有游戏币余额时,不经过新的支付回调,但履约链路仍然相同:

  1. 校验用户在当前代理体系下的余额;
  2. 锁定余额账户,创建独立出币订单;
  3. 冻结或预扣一枚游戏币;
  4. 创建并发布 MQTT 命令;
  5. 设备成功后确认扣减;
  6. 设备失败或超时确认失败后退回余额。

推荐使用“冻结 + 确认”的方式,而不是发布 MQTT 成功后永久扣币。这样能正确处理 Broker 可用但设备机械执行失败的情况。

超时、重试与人工补偿

场景 自动处理 人工入口
支付成功但命令未创建 事务外盒或扫描任务补建命令 查看支付原单和订单事件
发布服务超时 先查询命令状态,再按同一命令 ID 重试 禁止手工创建第二笔订单
设备离线 保持待确认或按业务规则延迟发布 联系现场并决定退款
重复设备回执 幂等返回,不重复结算 无需处理
设备报告失败 标记失败,按策略退币或退款 核查投币器与库存
长时间无回执 转为超时,停止无限重试 对照支付、Broker 和现场记录
已出币但平台未收到回执 不自动退款,进入争议核对 根据设备日志人工完成订单

所有补偿操作都写操作人、原因、原状态、新状态和关联凭证。人工页面只能调用领域服务执行合法转换,不能直接修改数据库状态。

对账与可观测性

系统每天至少核对四组数据:支付成功金额与平台已支付订单、平台订单与 MQTT 命令、成功命令与设备出币订单、完成订单与代理收益流水。

日志使用平台订单号、命令 ID 和 IMEI 作为关联字段,分别记录 API 请求、支付回调、发布结果、Broker 回执和结算结果。监控指标包括支付回调延迟、命令发布失败率、设备确认耗时、超时订单数、重复回执数和不同设备型号的失败率。

这条链路的目标不是让每一步永远不失败,而是确保任何一步失败后都能判断钱是否收到、命令是否发布、设备是否执行、收益是否结算,并能安全恢复。

站内搜索

没有找到内容!