# 抖音验券失败怎么查，先看 errorCode 再决定能不能重试, 无限聚核

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

联调抖音团购验券时，失败后最容易踩的坑和美团侧类似，但抖音还有几处专属分叉：`platform` 必须是 2；短链扫码可能解析失败；次数卡（次卡）在结果未知时**禁止**自动重试；漏做输码验券校验会直接 `TICKET_INVALID`。店员看到「失败」就让顾客重扫、超时后换参数再打一枪，往往把「其实已核掉」做成「本地没成功明细」。

**一句话回答：** 抖音验券／核销失败先看顶层 `success`，再看稳定的 `errorCode`，不要只凭 HTTP 状态或数字 `code` 做分支。只有 `CONSUME_IN_PROGRESS`、`RESULT_UNKNOWN`、`SERVICE_UNAVAILABLE`、`PROVIDER_UNAVAILABLE`，或请求超时没拿到响应时，才用原请求原参数有间隔、有上限地重试；`PLATFORM_REJECTED`、`ALREADY_CONSUMED`（10500）等明确失败要停止自动重试。**抖音次卡结果未知时不自动重试**，先查平台券状态或对账。依据无限聚核公开 FAQ 与 API 文档口径整理，查阅日期 2026-10-19，以当前公开文档与平台后台为准。

- **成功只认 `success=true`。** HTTP 200、`code=10000` 都不能单独当成功；
- **`platform=2` + 原样五参数。** `shopId`、`platform`、`code`、`num`、`ticketInfo` 与首次核销请求逐字一致；
- **次卡例外。** 预核销 `ticketInfo`／`ticketData` 含 `time_card` 时，结果未知禁止自动重试；
- **已核销 10500 是核对，不是成功。** 保存 `wxdgOrderNo`，不要补发权益、不要编造撤销凭证；
- **预核销成功 ≠ 已核销。** 校验只读，权益只能在真实核销成功后发。

![决策树示意：①是否拿到完整响应；②顶层 success 是否为 true；③失败时读稳定 errorCode，分成可重试白名单、停止重试展示原因、ALREADY_CONSUMED 用 wxdgOrderNo 核对；底部标出抖音次卡结果未知禁止自动重试](assets/hero.png)

*示意图：排查链路按无限聚核公开 FAQ／API 口径整理（2026-10-19），非某家后台截图。*

## 一、排查链路总览：先 success，再 errorCode

建议把「一次抖音核销调用」的结果处理固定成四步，后端和前端共用同一套状态机：

1. **有没有拿到完整响应？** 连接中断、读超时、没 body，一律先记「结果未知」，保留原请求五元组（`shopId`、`platform=2`、`code`、`num`、`ticketInfo`），页面显示「正在确认，请勿重复提交」，不要立刻报失败、不要引导顾客重扫码。
2. **顶层 `success` 是否为布尔值 `true`？** 是则进入成功分支：保存首次完整响应、`wxdgOrderNo`、按 `data[i]` 与 `consumeCredential[i]` 下标对应存明细与撤销凭证，再处理自己的预约／会员／出菜。
3. **`success=false` 时读 `errorCode`。** 用字符串枚举分支，不要用 `code` 数字猜：结果未知、参数错误、门店未授权、次数不足等，公开口径里可能共用同一类数字码（常见举例是 `10001`），「见 10001 就重试」会把明确拒绝也重试进去。
4. **落到三类动作：** 可重试（原样重发）、停止并展示原因、已核销转核对。任何一类都先落库，再决定 UI。若预核销已判定为次卡，结果未知走「待核对」，**不要**进自动重试队列。

对照表（聚合侧稳定字段；平台内部业务码以官方后台为准）：

| 类别 | 典型信号 | 后端动作 | 不要做什么 |
|---|---|---|---|
| 明确成功 | `success=true` | 保存首次成功全量，再发权益 | 不要只看 HTTP／`code` |
| 可重试 | 白名单 `errorCode` 或超时无响应（非次卡） | 原请求有限重试；未确定前不入账 | 不要重新预核销、换券码／门店／数量 |
| 次卡结果未知 | 白名单或超时，且含 `time_card` | 标记待核对；查券状态／对账 | 不要自动重试 |
| 业务／平台拒绝 | `PLATFORM_REJECTED` 等 | 停重试，展示返回原因 | 不要自动换平台再试 |
| 已核销提示 | `ALREADY_CONSUMED`，`code=10500` | 停重试，用 `wxdgOrderNo` 核对 | 不要当成功、不要补发、不要编造凭证 |
| 配置／参数类 | 授权、短链、凭证、额度等 | 停重试，修配置或减 `num` 后再人工发起 | 不要用同一错误请求死循环 |

## 二、抖音专属：预核销、ticketInfo 与短链

抖音侧两阶段与美团聚合口径一致，但字段更「抖音味」：

- **输码验券校验（预核销）** 是只读确认：检查券是否可用、拿回不透明的 `ticketInfo`；抖音还会在 `ticketData` 中返回上游官方 `data` 对象的完整 JSON 字符串。它**不改变券状态**，也**不能发权益**。
- **验券／核销** 才是写操作：必须原样回传预核销保存的 `ticketInfo`，以及同一组 `shopId`、`platform=2`、`code`、`num`。成功依据仍是顶层 `success=true`。
- **`code` 支持明文券码，也支持完整 抖音 `v.douyin.com` 扫码短链。** 短链解析失败时公开口径会落到 `INVALID_COUPON_LINK`（如「解析抖音券码短链失败／超时／响应无效」）——应改传明文券码，不要当可重试网络错误死循环。
- **漏做预核销或凭证与门店／券码不一致：** 常见 `TICKET_INVALID`（「核销凭证无效，请先调用预核销接口获取 ticketInfo 后再核销」）。处理是用当前门店重新做输码验券校验，而不是盲着重试核销请求。

排查时先问四句：

1. 这次失败发生在校验接口还是核销接口？（是否带 `ticketInfo`，`platform` 是否为 2）
2. `ticketInfo` 是否作为不透明字符串整段保存，而不是解析重组？
3. 扫的是短链还是明文？短链失败是否已改试明文？
4. 页面是否在校验成功后就提示「已核销」？（文案问题会导致「核了但没权益」投诉）

超时或结果未知时，**不要为了重试再跑一遍预核销**，也不要让顾客重新扫码：券若已在平台侧核掉，重新预核销会变成平台拒绝，日志更难对上。

## 三、次卡（次数卡）：可用次数与禁止自动重试

抖音次数卡是本篇相对美团失败码文最重要的差异点。公开口径要点：

1. **识别：** 预核销成功时，`ticketInfo` 内可含 `time_card`（如 `times_count`／`times_used`）；`ticketData` 的 `certificates[].time_card` 也可对照。核销成功明细里还有 `tgTimesCardFlag`。
2. **下单前校验：** 核销会先校验 `times_count - times_used >= num`；可用次数不足时不会创建核销 operation，常见落到 `TICKET_INVALID`（「ticketInfo.time_card 可用次数不足」）——应减少 `num`，不要自动重试同一 `num`。
3. **`encryptedCodes` 与 `num`：** 校验通过但 `encryptedCodes` 少于 `num` 时，次数卡会将首项复制为 `num` 份；非次数卡仍拒绝（「ticketInfo.encryptedCodes 数量不足」）。
4. **结果未知禁止自动重试：** 即使 `errorCode` 落在可重试白名单，或请求超时没拿到响应，**抖音次卡也不要自动重试**。先查询平台券状态或完成对账，再决定是否人工补处理。

伪代码意图（字段名以当前公开文档为准）：

```text
RETRYABLE = {
  CONSUME_IN_PROGRESS,
  RESULT_UNKNOWN,
  SERVICE_UNAVAILABLE,
  PROVIDER_UNAVAILABLE
}

isTimeCard = 预核销 ticketInfo／ticketData 含 time_card
             （或业务侧已标记次卡）

若 超时无响应 或 errorCode ∈ RETRYABLE:
  若 isTimeCard → 标记待核对（不自动重试）
  否则 → 用首次请求 JSON 原样有限重试
否则若 errorCode == ALREADY_CONSUMED:
  停止；保存 wxdgOrderNo；转核对
否则若 errorCode == TICKET_INVALID 且提示次数不足:
  停止；减少 num 后由人工重新发起（先重新预核销确认剩余次数）
否则:
  停止；展示原因；不自动重试
```

## 四、可重试白名单与原请求规则

按无限聚核公开 API／FAQ 口径，**只有**下列情况才允许业务后端自动重试（次卡除外）：

- `errorCode` 为 `CONSUME_IN_PROGRESS`
- `errorCode` 为 `RESULT_UNKNOWN`
- `errorCode` 为 `SERVICE_UNAVAILABLE`
- `errorCode` 为 `PROVIDER_UNAVAILABLE`
- **或**请求超时／连接中断，根本没拿到可用响应

重试规则必须同时满足：

1. **原样五参数：** `shopId`、`platform`、`code`、`num`、`ticketInfo` 与首次核销请求逐字一致（抖音务必保持 `platform=2`）；
2. **有间隔、有上限：** 上限与退避是你业务侧配置，不是「无限打」；超限仍未知则转待核对／查券状态／对账；
3. **页面态：** 未确定前不报成功也不报最终失败，避免顾客被引导重扫码；
4. **次卡例外：** 见上一节——结果未知不进自动重试队列。

接口侧幂等的含义是：**同一业务请求不会被重复核销成多笔。** 它不等于「每次重复调用都返回同一份首次成功明细」。因此业务系统仍要按券码 + 自身业务订单去重，并保存首次完整成功记录；公开 FAQ 也写明聚合核销调用方**无需传** `idempotencyKey`（旧客户端传入也会被忽略），去重责任在业务侧。

`PROVIDER_UNAVAILABLE` 在抖音侧常见文案举例包括：获取抖音 `client_token` 超时／暂不可用、授权凭据正在刷新等——属于退避重试，不是换券码再试。

## 五、PLATFORM_REJECTED 与常见业务拒绝

聚合侧常见会把平台明确拒绝收成 `PLATFORM_REJECTED`。输码验券校验阶段公开口径举例：外层 `code` 可能是 `10000`，message 为平台原始提示，或兜底「获取抖音券详情失败」；抖音无券记录时也可能是「该抖音券无效或者已使用」。验券阶段还可能出现 `10000` 加平台业务码一类写法。**具体数字以你账号当前返回与平台后台为准，本文不把未公开码表写成硬编码分支。**

现场高频现象（作「常见现象举例」，处理以平台与聚合返回文案为准）：

| 现象举例 | 店员／开发侧先查什么 | 系统侧建议 |
|---|---|---|
| 券不存在／无效／已使用 | 是否扫成无关链接、空格未去；是否他店已核 | 停自动重试；提示核对券码与门店 |
| 短链解析失败 | `INVALID_COUPON_LINK`；网络是否能解析短链 | 改传明文券码；勿当白名单网络错误死循环 |
| 门店不适用／未授权本店 | 聚核 `shopId` 是否对应正确门店；该店抖音授权是否有效 | 停重试；修门店映射与授权后再人工发起 |
| 已过期／未到可用时间 | 券详情页可用时段；门店营业规则 | 停重试；展示平台原因 |
| 次数不足／额度类 | 次卡剩余次数，或控制台剩余核销次数 | 停重试；减 `num` 或处理额度后再发起 |
| 凭证无效 | 是否跳过预核销、是否换过门店／券码 | 重新预核销；勿盲重试核销 |

原则：**拒绝类错误展示原因即可，不要自动换平台轮询试。** 轮流试抖音再试美团，只会把错误提示打乱，也增加无效调用。

## 六、ALREADY_CONSUMED（10500）：停重试，用 wxdgOrderNo 核对

命中已成功核销时，当前公开口径的典型形态是：

- `success=false`
- `code=10500`
- `errorCode=ALREADY_CONSUMED`
- `wxdgOrderNo` 为原核销单号
- `data` 为空／`null`，`consumeCredential` 为空数组
- `message` 里可能带核销时间与门店信息（展示前注意脱敏）

正确处理：

1. **立刻停止**该次自动核销重试；
2. **保存** `wxdgOrderNo`、`message`、完整响应与原请求；
3. **用** `wxdgOrderNo` 和平台侧「已验券信息／券状态」核对：是否本门店、是否本笔业务订单、本地是否已有首次成功记录；
4. **若本地已有成功记录：** 复用本地权益状态，不要再发一遍；
5. **若本地没有首次成功：** 标记待核对；确认前不要仅凭 10500 补发权益，也不要编造 `consumeCredential`（没有首次成功响应就没有合法撤销凭证）。

这类工单最常见的场景，是「第一次其实已经成功，但客户端超时只看到失败／未知，又点了一次」。10500 是在告诉你「别再核了，去对原单」，不是「再试一次就能拿到成功报文」。

## 七、自检清单（联调／线上各走一遍）

| 检查项 | 预期 |
|---|---|
| 成功判断 | 仅 `success=true` 进入成功；HTTP／`code` 不作唯一条件 |
| `platform` | 抖音请求为 2；与门店授权平台一致 |
| 首次成功落库 | 完整响应、`wxdgOrderNo`、明细与凭证按下标保存后再发权益 |
| 预核销与核销 | 校验成功只进确认页；权益只在核销成功后发 |
| `ticketInfo` | 不透明整段回传，不解析重组；抖音保存 `ticketData` 便于次卡判断 |
| 短链 | `INVALID_COUPON_LINK` 改明文，不进白名单死循环 |
| 可重试白名单 | 仅四类 `errorCode` + 超时；原请求原参数；有上限 |
| 抖音次卡 | 结果未知不自动重试；次数不足减 `num` |
| `PLATFORM_REJECTED` | 停重试，展示平台／聚合原因 |
| `ALREADY_CONSUMED`／10500 | 停重试；按 `wxdgOrderNo` 核对；不补发、不编造凭证 |
| 顾客侧文案 | 结果未知显示「正在确认，请勿重复提交」，不引导重扫码 |
| 业务去重 | 按券码 + 本地订单去重；不依赖调方传 `idempotencyKey` |

## 八、小结

抖音验券失败怎么查：把「网络结果」和「业务结果」拆开——先确认有没有完整响应，再用 `success` 判定成败，用 `errorCode` 白名单决定能不能重试。可重试就原样重发；拒绝就停；10500 就核对。相对美团侧，额外记住三件事：`platform=2`、短链失败改明文、**次卡结果未知禁止自动重试**。按这套决策树落地，日志里的「失败」才能变成可解释的状态，而不是一串盲目重试。

## 常见问题

### 抖音验券失败时，为什么不能只看 HTTP 状态或 code？

成功只认顶层布尔值 `success=true`。HTTP 200 或数字 `code`（例如 `10000`、`10001`）都不能单独当成功或失败依据：公开口径里不同业务结果可能共用同一类数字码，用 `code` 分支容易把明确拒绝也当成可重试。失败时读稳定字符串 `errorCode`，再决定重试、停重试还是核对。

### 哪些 errorCode 允许自动重试？抖音次卡有什么例外？

只有 `CONSUME_IN_PROGRESS`、`RESULT_UNKNOWN`、`SERVICE_UNAVAILABLE`、`PROVIDER_UNAVAILABLE`，或请求超时／连接中断没拿到响应时，才允许业务后端自动重试。重试必须与首次核销请求的 `shopId`、`platform`、`code`、`num`、`ticketInfo` 逐字一致，并设置间隔与上限。抖音次卡（预核销 `ticketInfo`／`ticketData` 含 `time_card`）在结果未知时**不自动重试**，先查平台券状态或对账。

### 返回 ALREADY_CONSUMED 或 code=10500 该怎么办？

立刻停止该次自动核销重试，保存返回的 `wxdgOrderNo`、完整响应与原请求，再用 `wxdgOrderNo` 与平台「已验券／券状态」核对是否本店本单、本地是否已有首次成功记录。不要把 10500 当成功、不要仅凭提示补发权益，也不要编造 `consumeCredential`；没有首次成功响应就没有合法撤销凭证。

### 预核销已经成功，为什么还说没核销？短链失败算哪一类？

预核销（输码验券校验）是只读确认：检查券是否可用并返回不透明的 `ticketInfo`（抖音另有 `ticketData`），不改变券状态，也不能发权益。真正核销必须另调核销接口，原样回传 `ticketInfo` 与同一组门店、平台、券码、数量，且成功依据仍是顶层 `success=true`。短链解析失败常见为 `INVALID_COUPON_LINK`，应改传明文券码，不要当可重试网络错误死循环。

### PLATFORM_REJECTED 或次卡次数不足，要不要自动换参数再试？

不要自动换平台、换券码或加大 `num` 重试。`PLATFORM_REJECTED` 应停止自动重试并展示返回原因。次卡可用次数不足常见为 `TICKET_INVALID`（time_card 可用次数不足），应减少 `num`，必要时重新预核销确认剩余次数后再由人工发起。自动轮流试只会打乱错误提示并增加无效调用。
