# 团购券核销了，门禁没开、洗车机没启动怎么办？自助门店异常排查手册

> 核销服务由无限聚核提供，美团、抖音团购核销一站式接入。

自助门店最怕一种投诉：顾客说“券已经核销了，门还是打不开”，或者“时长到账了，洗车机没反应”。店里没人，顾客站在门口或工位前，每一分钟都在变成差评。

这类问题看起来都是“核销出问题了”，其实从平台核销到设备动作之间有五段，每一段断掉的表现都差不多，处理方式却完全不同。本文给出一套按证据排查的顺序，适用于自助洗车、24小时健身房等“核销后自动开门或上电”的门店。

**一句话回答：** 先止损，由店员远程开门或手动上电，并记录操作人；再从“平台核销 → 店内权益 → 使用资格 → 设备指令 → 设备执行”五段里，从左往右找第一段断掉的证据。绝大多数情况只需要补发一次本地权益或修设备，不需要、也不应该让顾客重新核销，更不要急着撤销核销。第1段是否成功，以核销接口返回的顶层 `success` 和平台券状态为准；通过无限聚核这类第三方接口核销的，美团券可以用它的“查询已验券信息”接口、抖音券可以用“券状态查询”接口核对。

![从平台核销、店内权益、使用资格、设备指令到设备执行的五段排查链路图，并列出常见误判](assets/hero.png)

*排查示意：每一段列出应当查看的证据，不是真实后台界面。*

## 一、先止损：让顾客先用上

顾客在现场时，排查可以晚几分钟，体验不能等：

- 店员在后台远程开门或手动给工位上电，**同时记录操作人、原因和关联的核销记录**，事后对账才分得清哪次是顾客自己开的、哪次是店员帮开的；
- 洗车店可以先引导顾客换一个在线的工位；
- **不要让顾客重新输码核销。** 同一张券已经核销成功，顾客重新输码时，预核销会因为券已使用被平台拒绝（`PLATFORM_REJECTED`）；服务端拿原请求再提交核销，则返回 `ALREADY_CONSUMED`（code 10500）。两种提示都只会让顾客更困惑；
- **不要先撤销核销。** 设备没动作不是券的问题，撤销以后券恢复成未使用，店内权益却可能还在，账反而更乱。

## 二、五段证据：从左往右找第一段断点

| 段 | 要回答的问题 | 看什么证据 | 常见原因 | 处理方式 |
|---|---|---|---|---|
| 1 平台核销 | 券真的核掉了吗 | 顶层 `success=true`、`wxdgOrderNo`、`consumeCredential` | 结果未知、平台拒绝、授权过期 | 按错误码处理，见第三节 |
| 2 店内权益 | 权益记进账户了吗 | 权益记录、到账时长或次数、关联的确认单 | 核销成功后权益任务失败 | 只补发一次，见第四节 |
| 3 使用资格 | 系统认为此刻能用吗 | 本人、本店、有效期、时间窗、工位空闲 | 时间窗口不一致、账号不一致 | 统一校验规则，见第五节 |
| 4 设备指令 | 服务端发出指令了吗 | 指令记录、操作人、指令ID | 控制器离线、下发失败 | 见第六节 |
| 5 设备执行 | 设备真的动作了吗 | 设备回报、状态查询、现场表现 | 消息丢失、接线、来电状态 | 见第六节 |

排查时不要跳段。第1段有 `success=true` 不代表第2段一定成功；第4段显示“已下发”也不代表第5段门已经开了。

## 三、第1段：看核销结果和错误码

下面以无限聚核的V2团购核销接口为例；其他服务商的字段名不同，判断思路一样。

核销是否成功只看顶层 `success`，不看HTTP状态码，也不能只看 `code=10000`。失败时响应里有稳定的 `errorCode`，按它分三类处理：

| 类别 | errorCode | 含义 | 处理 |
|---|---|---|---|
| 可以重试 | `CONSUME_IN_PROGRESS`、`SERVICE_UNAVAILABLE`、`PROVIDER_UNAVAILABLE` | 同券在途或服务暂时不可用 | 稍后用**原请求原参数**重试，不换确认单 |
| 结果未知 | `RESULT_UNKNOWN` | 平台没给出明确结果 | 页面进入“待确认”；用**原请求原参数**间隔重试并限制次数，不重新预核销；仍未知再查券状态或对账 |
| 不要重试 | `ALREADY_CONSUMED`（code 10500） | 本店已成功核销过 | 按返回的 `wxdgOrderNo` 查原单，不再发权益 |
| 不要重试 | `PLATFORM_REJECTED`（code 为10000，或10000加平台业务码，如11006） | 券不存在、已使用、已过期或不适用本店 | 提示顾客核对券码和门店 |
| 需要处理配置 | `PLATFORM_UNAUTHORIZED`、`STORE_NOT_FOUND`、`MASTER_STORE_UNSUPPORTED`、`QUOTA_EXHAUSTED` | 授权过期、门店不对、传了商户ID、次数用完 | 重新授权、核对具体门店 `shopId`、充值 |

抖音次卡还要多注意一条：公开文档写明次卡核销结果未知时不要自动重试，先查询平台券状态或完成对账。通过无限聚核核销的，美团券可以用“查询已验券信息”接口按券码核对；抖音券用“券状态查询”接口核对，要传预核销时拿到的加密券码 `encrypted_code`，不是顾客输入的明文券码。具体路径以当前公开API文档为准。

顾客投诉“核销了”但第1段查不到成功记录，多数是页面在结果未知或超时时提示得太笼统，让顾客以为已经成功。结果未知时，页面应明确显示“正在确认，请勿重复提交”。

## 四、第2段：权益到没到账，只补发一次

第1段成功、第2段缺失，说明平台核销成功以后，本地发权益的步骤失败了。这时只补发本地权益，**不要再核销同一张券**。

补发要满足两个条件，才不会发成两份：

- **以确认单或 `wxdgOrderNo` 为唯一键。** 补发前先查这个键下是否已有权益记录，已有就直接返回；
- **核销成功记录和权益任务同一事务写入。** 首次成功响应只返回一次，重复请求只会得到10500提示，不会再给出原始成功数据。成功响应里的 `wxdgOrderNo`、`data[i]` 和按下标对应的 `consumeCredential[i]` 必须第一次就存好。

补发由系统任务或店员在后台发起，都要记录操作人。不要让顾客自己再点一次“确认使用”来触发补发。

## 五、第3段：使用资格，前后校验要用同一个时间窗口

这一段最容易被忽略：权益明明在账户里，系统却认为“现在不能用”。

我们（无限聚核团队）自营的线下自助门店业态与本文不同，但在对它的系统做代码审查时，发现过一个典型的不一致：对“立即开始”的订单，上电接口在开始时间前留了10秒缓冲，开门接口却没有。两个接口的判断条件不一样，顾客下单后马上开门，就有可能被开门接口判为“不在使用时间内”，而上电接口却允许。它不需要复杂的故障才会触发，只要顾客手快一点。

排查和预防的做法：

- 开门、上电、断电、计次这些接口，**共用一个“是否可用”的判断函数**，不要各写一遍时间比较；
- 时间窗口的边界（提前多少秒、延后多少分钟、是否包含结束那一刻）写进配置和测试用例；
- 服务器、数据库和设备的时区、时钟要一致，时间字符串解析统一指定时区；
- 被拒绝时把原因写进日志：是不在时间窗、不是本人、不是本店，还是工位被占用。只记一句“校验失败”，下次还得从头查。

## 六、第4、5段：指令发了，设备没动

设备控制通常是业务服务端通过MQTT等协议给联网控制器发指令。以下几条是通用的排查顺序，具体以设备厂商协议为准：

1. **看控制器是否在线。** 控制器一般会定期上报在线状态、信号强度和各路通断状态。最后一次上报超过阈值，先当离线处理，查供电和网络；信号强度一直很弱的，考虑换位置或加中继；
2. **区分“已发送”和“已执行”。** MQTT使用QoS 0时，消息丢失不会重发，服务端日志里的“已发送”不能证明设备收到了。以设备回报为准，没有回报就主动查询一次状态；
3. **看来电后的默认状态。** 控制器通常可以设为来电全断开、全闭合或恢复断电前状态。停电恢复后设备“自己通电”或“一直不通电”，多半是这里设错了；
4. **看锁的类型和接线。** 电锁分“断电开”和“断电锁”，同样是“全部断开”指令，接线不同，门的表现正好相反。换过锁或改过线以后，要现场重新测一遍开门和断电；
5. **最后才考虑重启控制器。** 重启前记录当时的状态上报，重启后先查询一次状态，再恢复业务。

设备段查清以后，本次没有真正使用的时长或次数要退回会员账户，并在记录里注明原因。

## 七、顾客要退款：撤销核销不等于退款

有时候顾客不想等了，直接要求退款。这时先看权益有没有用掉，再决定是否撤销核销：

![顾客申请退款时，按券是否核销、权益是否已用、撤销结果逐项状态处理的决策图](assets/01-refund-decision.png)

*决策示意：撤销核销只改变券的使用状态，退款走平台售后。*

以无限聚核的撤销接口为例，当前公开API文档写明：

- 撤销使用核销成功时保存的 `consumeCredential`，一次1到10个，只能撤销新版V2聚合核销的记录；V1、平台原生接口和历史无凭证记录不能通过聚合撤销接口撤销；
- 结果逐项返回，`data[].status` 为 `succeeded`、`rejected` 或 `unknown`，只有全部成功时顶层 `success` 才为 `true`；
- 撤销接口**不做幂等**，相同凭证重复请求仍会再次请求平台。结果是 `unknown` 时，先查券状态或对账，再决定是否重新撤销；
- 撤销免费。

撤销成功只表示券恢复为未使用，顾客的钱怎么退，走平台售后流程；店内权益要在撤销之前先冻结，撤销成功后回收，被拒绝则恢复。

## 八、每次核销要留下的证据

下面这些字段第一次就要存好，事后排查全靠它们：

- 自己的确认单ID、会员ID、业务门店ID和无限聚核具体门店 `shopId`；
- 平台 `platform`、券码的安全关联（不要在日志里打印完整券码）、套餐和本地权益ID；
- 首次成功响应的 `wxdgOrderNo`、`traceId`、`data[]` 以及按下标对应的 `consumeCredential[]`；
- `payAmount`、`actualPayAmount` 等金额快照；
- 每次开门、上电、断电的指令ID、操作人、时间和设备回报。

有了这些，“券核销了门没开”基本能在几分钟内定位到具体哪一段。

## 九、把排查变成预防

- **设备离线告警：** 控制器超过阈值没有上报，就通知店长，别等顾客到了才发现；
- **“核销成功但一直没用”告警：** 核销后一段时间内没有开门或上电记录，主动检查，多半是第3到5段出了问题；
- **待确认单每日对账：** 所有结果未知、10500、撤销 `unknown` 的记录，每天和平台券状态核对一遍，不留悬而未决的单；
- **上线前用测试券走一遍失败路径：** 断网、拔掉控制器、停电再来电、连续点击，确认每种情况下权益和设备状态都对得上。

## 十、常见问题

### 团购券核销成功了但门没开，要不要撤销核销？

一般不要。设备没动作不是券的问题；撤销以后券恢复成未使用，店内权益却可能还在。先由店员远程开门或上电，再查设备；顾客坚持退款时，先冻结店内权益，再撤销核销。

### 核销结果未知，可以让顾客再输一次码吗？

不要。页面显示“正在确认，请勿重复提交”；服务端用同一份原请求（同一门店、券码、数量和 `ticketInfo`）间隔重试几次，仍未知再查券状态或对账。以无限聚核接口为例，公开文档写明抖音次卡结果未知时不要自动重试，直接查券状态或对账。

### 同一张券再核销一次会怎样？

分两种情况。顾客重新输码，会先走预核销，券已经用掉，平台拒绝，返回 `PLATFORM_REJECTED`；服务端用原请求再提交核销，以无限聚核接口为例，返回 `ALREADY_CONSUMED`（code 10500）和原来的 `wxdgOrderNo`。两种情况都按原单处理，不要再发一次权益。

### 撤销核销就等于给顾客退款了吗？

不是。撤销只让券恢复为未使用，钱怎么退，走平台售后流程。

### 排查核销问题时，无限聚核的接口能提供哪些证据？

核销成功时，无限聚核返回顶层 `success=true`、订单号 `wxdgOrderNo`、每张券的明细 `data[]`（含 `dealId`、`receiptCode`、`orderId`）和按下标对应的撤销凭证 `consumeCredential[]`，响应里还有平台链路ID `traceId`；失败时返回稳定的 `errorCode`。事后要核对平台侧状态，美团券可以调“查询已验券信息”，抖音券可以调“券状态查询”。这些字段第一次就要存好，排查时按第1段到第5段逐段对照。

接口字段、错误码和重试规则以无限聚核当前公开API文档为准。
