# 抖音团购核销接口怎么接？和美团不一样的几个地方

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

美团团购核销刚接好，老板紧接着就会问：“抖音的券能不能也核？”不少开发者以为照着美团的代码改改域名和字段就行，做起来才发现两边差得不少。美团顾客报一串数字就能验，抖音顾客多半直接亮二维码。美团拿明文券码就能核销，抖音必须先调“验券准备”，换出一个 `verify_token` 和加密券码。美团价格是元，抖音是分。连判断“成功”该看哪个字段，两边都不一样。

下面按开发顺序把这些差异列出来。抖音部分依据抖音开放平台“生活服务商家应用”的接口文档和技术服务商接入文档，2026年9月29日查看。

**一句话回答：** 抖音团购券核销用的是抖音开放平台生活服务的 `/goodlife/v1/fulfilment/certificate/` 系列接口，顺序固定为“验券准备 → 验券”，验券后1小时内可以撤销。和美团相比，最要紧的有四点：

- 抖音用应用级 `client_token` 调用，每次请求用 `poi_id` 指定门店；
- 每次验券前都要重新调验券准备，用返回的 `verify_token` 和 `encrypted_code` 去核销，不能传明文券码；
- 顾客出示的二维码扫出来是短链，要取跳转后长链接里的 `object_id`；
- 金额单位是分；判断成功要同时看 `data.error_code`、`extra.error_code` 和每张券的 `verify_results[].result`。

自己对接，要先入驻抖音服务商平台，再通过联调、压测、安全三项验收。不想自己办资质，可以通过无限聚核这类第三方接口，和美团共用一套验券、核销接口，`platform` 传2。

![左边是美团团购券，顾客报数字券码，验券后直接核销；右边是抖音团购券，扫二维码得到短链，经验券准备换出 verify_token 和加密券码后再验券](assets/hero.png)

*差异示意：画面为示意图，不是平台真实页面。*

## 一、先确认要接的是哪一套接口

抖音开放平台上叫“核销”的接口不止一套。最常见的混淆是这两套：

| | 生活服务商家应用（本文讲的） | 抖音小程序交易系统 |
|---|---|---|
| 券从哪来 | 商家在抖音来客上架团购，顾客在抖音App团购页购买 | 商家自己的抖音小程序里下单 |
| 验券相关接口 | `GET /goodlife/v1/fulfilment/certificate/prepare/`、`POST /goodlife/v1/fulfilment/certificate/verify/` | `/api/apps/trade/v2/fulfillment/delivery_prepare`、`delivery_verify` |
| 门店标识 | `poi_id` | 以小程序交易文档为准 |

判断方法：顾客的券是在抖音App团购页买的，商家在抖音来客后台能看到这笔订单，就用生活服务这一套。网上搜到的示例代码，路径里带 `/api/apps/trade/` 的属于小程序交易系统，拿来核销团购券是调不通的。

还有一个小细节：生活服务接口的路径都以 `/` 结尾，接入文档特意写了“/不可省略”。从美团代码改过来时顺手删掉结尾斜杠，是很常见的低级错误。

## 二、调用凭证：抖音一个应用一份，美团一个商家一份

| | 美团 | 抖音 |
|---|---|---|
| 调用凭证 | 每个商家单独授权，各有一套 access token 和 refresh token | 一个应用一个 `client_token`，所有已授权商家共用 |
| 商家怎么授权 | 商家打开授权链接，登录美团确认 | 商家在抖音来客后台把应用授权给服务商，或者在服务商的系统里完成授权 |
| 凭证过期 | 用 refresh token 续期；refresh token 失效，这家店要重新授权 | 重新生成 `client_token`，商家不用操作 |

抖音所有接口的请求头都要带 `access-token`，值是 `/oauth/client_token/` 返回的 `data.access_token`。它是应用级凭证，带来三个后果：

1. **集中生成和缓存。** 多台服务器部署时，快过期前统一刷新。刷新要加分布式锁，拿到锁以后再看一眼缓存，如果别的机器已经刷新过就直接用，免得几台机器同时刷新、互相顶掉；
2. **门店全靠 `poi_id` 区分。** 凭证里没有门店信息。库里要存好“自己的门店 ↔ 抖音 `poi_id`”的映射，传错了轻则报无权限，重则把券核到别的门店；
3. **连锁场景多一个参数。** 验券准备接口的 `account_id`，文档写的是“云连锁/共管场景下，挂靠商户必传，其余场景可不传”。

美团正好相反，凭证跟着商家走。每家店的 refresh token 都要可靠地存下来：刷新拿到新的 refresh token 后，先写数据库，再更新缓存。异步保存丢了一次，那家店就只能重新授权。两套凭证的管理逻辑差别很大，不要硬套进同一个类。

## 三、验券准备和验券：一次准备只配一次验券

抖音团购券核销分两步，这是和美团差别最大的地方。

**第一步，验券准备。** `code`（明文券码）和 `encrypted_data`（从二维码解析出来的标识）二选一，`poi_id` 必传：

```http
GET /goodlife/v1/fulfilment/certificate/prepare/?poi_id=<门店poi_id>&encrypted_data=<URL编码后的object_id>
access-token: <client_token>
```

返回里核销要用的主要是这几项（其他字段省略，数值为虚构）：

```json
{
  "data": {
    "error_code": 0,
    "verify_token": "<本次验券标识>",
    "order_id": "<订单ID>",
    "certificates": [
      {
        "encrypted_code": "<加密券码>",
        "expire_time": 1790000000,
        "amount": { "original_amount": 29900, "pay_amount": 26900 },
        "sku": { "sku_id": "<商品ID>", "title": "精洗10次卡", "groupon_type": 3 },
        "time_card": { "times_count": 10, "times_used": 3 }
      }
    ]
  },
  "extra": { "error_code": 0, "logid": "<抖音日志ID>" }
}
```

**第二步，验券。** 把上一步的 `verify_token` 和挑出来的 `encrypted_code` 传回去：

```http
POST /goodlife/v1/fulfilment/certificate/verify/
access-token: <client_token>
Content-Type: application/json

{
  "verify_token": "<验券准备返回的 verify_token>",
  "poi_id": "<门店poi_id>",
  "encrypted_codes": ["<验券准备返回的 encrypted_code>"]
}
```

文档里这几句要看仔细：

- 验券准备和验券接口的“调用次数需要一一对应”；已经验券成功的，“当前二维码会失效”，要用刷新后的二维码再调用；
- 验券准备“只返回未使用状态的券信息”，而且“不返回明文券码，只返回加密券码”；
- `encrypted_codes` 的说明是“验券准备接口返回的加密抖音券码”。

落到代码里，常见的坑有这几个：

**把明文券码塞进 `encrypted_codes`。** 从美团迁移过来最容易犯，平台会报“加密码解密失败”一类的错误，网上不少求助帖都是这个原因。明文券码只能用在验券准备的 `code` 参数里。

**缓存 `verify_token` 重试。** 核销失败或超时后，拿旧的 `verify_token` 再调一次验券，不符合“一一对应”。结果明确失败的，重新调验券准备再验。结果不明的（超时、连接断开），先查券状态，确认没核掉再重来。

**券正在转赠。** 转赠中的券，验券准备返回空数组。页面上别只写“券无效”，提示“这张券可能正在转赠中，请确认后再试”，能少接不少客服电话。

**提货券少算张数。** 文档写明，提货券订单一个订单多张券只会返回一个 `certificate_id`。按 `certificate_id` 去重计数的，这里会算少。

**`certificate_id` 的精度。** 验券准备响应里的 `certificate_id` 在文档中标为64位整数。Node.js 直接 `JSON.parse` 可能丢精度，用支持大整数的解析方式，或者按字符串处理。

## 四、一次扫码，可能扫出好几张券

抖音团购二维码对应的是订单。文档写明，扫码后“可返回当前订单下可用的全部券码信息”。顾客买了3张洗车券，今天只洗1次，验券准备会把3张都返回来。

所以核销前要让顾客选数量，只取需要的那几个 `encrypted_code`，不要把 `certificates` 整组塞进 `encrypted_codes`。一次核掉3张，顾客第二天才发现，已经过了1小时的撤销时限，只能走平台售后。

次卡是另一种情况：一张券，多次使用。`time_card` 里 `times_count` 是总次数，`times_used` 是已用次数。验券接口文档对 `encrypted_codes` 的说明是：“多次卡商品，如果需要一次核销多份，list中传多个相同encrypted_code”。一张洗车次卡今天洗两台车，就把同一个加密券码放两次。放之前先算 `times_count - times_used` 够不够，不够就别发请求。

## 五、扫二维码，拿到的是短链

顾客在抖音App里打开团购券，出示的是二维码。用 `wx.scanCode` 或扫码枪扫出来，是一个抖音短链。按抖音文档：

1. 访问短链，拿到跳转后的长链接；
2. 取长链接里 `object_id` 参数的值作为 `encrypted_data`，URL 编码后再传；
3. 只认 `object_id` 这一个参数，域名、路径和其他参数都可能调整；
4. 短链长度不固定，不要限制长度，也不要截断。

顾客也可以报二维码下方的数字券码，这时用 `code` 参数。两个入口最后调的是同一个验券准备接口。扫码入口的完整做法，可以参考本站《小程序扫码核销团购券：美团券码、抖音二维码短链分别怎么处理？》一文。

## 六、金额单位是分，字段还很多

![抖音团购券金额字段示意：original_amount 是套餐售价，pay_amount 是用户实付，list_market_amount 是划线价，单位都是分；美团套餐价以元为单位](assets/01-amount-receipt.png)

*金额字段示意：字段来自抖音开放平台验券准备接口文档，金额为虚构示例。*

美团验券返回的套餐价是以元为单位的小数，比如 79.9；抖音验券准备的 `amount` 里全是以分为单位的整数，比如 7990。常用的几个：

| 字段 | 文档说明 | 用途 |
|---|---|---|
| `original_amount` | 原价，单位分 | 套餐售价，对账主要看它 |
| `pay_amount` | 实付金额，单位分 | 顾客实际付了多少 |
| `list_market_amount` | 划线价，单位分 | 页面上划掉的那个价 |
| `merchant_ticket_amount` | 商家营销金额，单位分 | 商家自己出的优惠 |
| `platform_discount_amount` | 平台优惠金额，单位分 | 平台补贴 |

`original_amount` 虽然叫“原价”，指的是套餐售价，划线价另有 `list_market_amount`，别弄反。两个平台的券放进同一张表对账时，统一存成以分为单位的整数，不要用浮点数存元。

## 七、判断成功：先看三层 error_code，再逐张看结果

![抖音验券响应结构标注：data.error_code 和 extra.error_code 都为0才算请求成功，还要逐张检查 verify_results 里的 result，并保存 verify_id 和 certificate_id 用于撤销](assets/02-response-anatomy.png)

*响应结构示意：字段来自抖音开放平台验券接口文档，数值为虚构。*

美团的验券接口，看顶层 `code` 是不是 `OP_SUCCESS`，再核对返回的券码和数量。抖音要看三处：

1. `data.error_code` 不为0：请求失败，原因在 `data.description`；
2. `extra.error_code` 或 `extra.sub_error_code` 不为0：同样按失败处理，原因在 `extra.description` 和 `extra.sub_description`；
3. 前两层都为0，还要逐张看 `data.verify_results[].result`，文档写的是“0表示成功，非0表示失败”。

抖音的技术服务商入驻指南里专门提醒过：“验券接口返回的验券结果 result 字段必须进行业务匹配检查”，result 非0“表示业务侧提示而非技术异常”。一次核销多张券时，完全可能一张成功、一张失败。这时按“部分成功”处理：成功的那张发放权益，失败的那张单独提示。

每张成功的券，把 `verify_id` 和 `certificate_id` 存下来，撤销时要用。`extra.logid` 也存上，找抖音技术支持排查问题时要提供它。

```go
// 按三层 error_code 和逐张 result 判断抖音验券结果
type verifyResp struct {
	Data struct {
		ErrorCode     int64  `json:"error_code"`
		Description   string `json:"description"`
		VerifyResults []struct {
			Result        int64  `json:"result"`
			VerifyID      string `json:"verify_id"`
			CertificateID string `json:"certificate_id"`
		} `json:"verify_results"`
	} `json:"data"`
	Extra struct {
		ErrorCode      int64  `json:"error_code"`
		SubErrorCode   int64  `json:"sub_error_code"`
		Description    string `json:"description"`
		SubDescription string `json:"sub_description"`
		Logid          string `json:"logid"`
	} `json:"extra"`
}

func judge(r verifyResp) (ok []Voucher, failed int, err error) {
	if r.Data.ErrorCode != 0 {
		return nil, 0, fmt.Errorf("验券失败 %d %s logid=%s", r.Data.ErrorCode, r.Data.Description, r.Extra.Logid)
	}
	if r.Extra.ErrorCode != 0 || r.Extra.SubErrorCode != 0 {
		return nil, 0, fmt.Errorf("验券失败 %d/%d %s %s logid=%s", r.Extra.ErrorCode, r.Extra.SubErrorCode,
			r.Extra.Description, r.Extra.SubDescription, r.Extra.Logid)
	}
	for _, v := range r.Data.VerifyResults {
		if v.Result != 0 {
			failed++ // 业务侧提示，逐张处理，不能把整笔当成功
			continue
		}
		ok = append(ok, Voucher{VerifyID: v.VerifyID, CertificateID: v.CertificateID}) // 撤销要用，必须落库
	}
	return ok, failed, nil
}
```

HTTP 超时、连接断开、响应解析失败，这三种情况都不是“失败”，是“结果未知”，券可能已经核掉了。先调券状态查询接口 `GET /goodlife/v1/fulfilment/certificate/get/` 确认，再决定发不发权益、要不要重试。

## 八、撤销：1小时内，带上 verify_id 和 certificate_id

抖音“撤销核销”接口文档写明，验券超过一个小时就不可再撤销，撤销后订单状态由“已使用”改回“待使用”；`extra.error_code` 和 `data.error_code` 均为0，才代表撤销成功。请求里带验券时保存的 `verify_id` 和 `certificate_id`。

美团是10分钟，两个平台差了6倍。系统里的撤销按钮要按平台分别倒计时。撤销功能怎么做、超时了怎么办，本站《团购券核销错了能撤销吗？》一文里有完整说明。

## 九、自己对接：先入驻，再过三项验收

![抖音技术服务商接入进度示意：入驻认证、创建应用、开通解决方案、基础配置、验收准备，联调、压测、安全三项验收，最后应用上线和商家授权](assets/03-onboarding-board.png)

*接入流程示意：依据抖音开放平台技术服务商接入文档整理（2026年9月29日查看），不是真实后台界面。*

直接调用上面这些接口，前提是你已经是抖音生活服务的技术服务商，而且应用已经上线。按抖音开放平台的技术服务商入驻指南，大致要走这些环节：

1. **入驻并认证。** 在服务商平台认证“生活服务商家应用代开发”能力，文档写的是“平台一般在 10 个工作日内完成审核”，通过后还要做对公认证。认证条件包括注册满一年、注册资本不低于100万元、经营范围含技术开发服务、同主体 ICP 备案（2026年9月10日核对，以平台当前要求为准）；
2. **创建应用。** 创建“第三方生活服务商家应用”，审核时效1个工作日；
3. **开通解决方案。** 核销能力在行业解决方案里，比如“到综行业团购解决方案”“到店餐饮解决方案”，可以整体开通，也可以按能力开通；
4. **基础配置。** 包括 Webhooks 和 SPI 地址注册，以及 IP 白名单。白名单是可选项，启用后所有接口请求的源 IP 都必须在白名单里；
5. **验收准备。** 绑定测试店铺，把测试用的抖音账号加白；
6. **联调、压测、安全三项验收**，全部通过后申请应用上线；
7. **商家授权。** 应用上线后，商家把应用授权给你，你才能调这家商家的接口。

三项验收里，容易卡住的是这些细节。

**联调验收。** 用例里填的订单号和 logid 必须来自测试店铺的测试账号。测试商品名称必须包含“测试”，售卖有效期、使用有效期都不能超过14天，提交后人工审核，时效1个工作日。测试店铺不对外展示，下单要在抖音来客后台“商品管理 → 更多 → 生成二维码”，用加白的抖音账号扫码购买。调用商品创建接口时，请求头要加 `Rpc-Persist-Life-Test-Data-Access: all`。

**压测验收。** 压的是你提供给抖音调用的 SPI 接口。压测请求都带 `X-Stress-Tag: 1` 请求头，服务端要识别它，别把压测请求当成真实订单处理。点“发起调试”时，抖音会取参数文件第一条数据重复请求两次，接口没做去重就会出问题。通过标准是成功率大于99.95%（接口返回 `error_code=0` 才算成功），PCT99 耗时不超过压测计划里配置的超时时长。压测时段只能选周一到周四的23:00到次日7:00。

**安全验收。** 要逐项提交材料：可登录的系统地址和测试账号；登录口令至少8位、包含三种以上字符，登录页有验证码或访问频次限制；手机号、姓名等隐私字段脱敏展示，手机号保留前3位和后4位；`client_secret`、`client_token` 加密存储，不能硬编码、不能明文保存，也不能下发给客户端；从开放平台拿到的敏感字段加密存储，文档推荐 AES256 一类算法；接入 WAF 并提供最近一个月的拦截记录或购买凭证，只开放必要端口。

这些要求都合理，加起来却是实打实的工作量。只想让自己的小程序或门店设备能核抖音券，走完整套流程往往不划算。

## 十、不想自己办资质：以无限聚核为例

通过无限聚核的 V2 聚合接口，抖音和美团共用一套验券、核销接口，`platform` 传2代表抖音。无限聚核当前公开 API 文档写明：

- **门店授权。** 调 `POST /api/hexiao/v2/get/auth/url`，传具体门店 `shopId` 和 `platform=2`，把返回的链接发给商家，商家在抖音侧确认授权；
- **验券。** 调“输码验券校验”接口，`code` 传数字券码，或者扫码得到的完整短链。`object_id` 解析、`client_token` 和 `poi_id` 都由服务端处理；
- **返回。** `payAmount` 是售卖价（分），`dealPrice` 是售卖价（元），`actualPayAmount` 是用户实付（分），和美团券口径一致。`ticketInfo` 是后续核销必需的凭证，要整段原样保存，不要解析；`ticketData` 是抖音官方响应 `data` 对象的完整原文，次卡剩余次数可以从这里读；
- **核销。** 调“验券”接口，把 `ticketInfo` 原样传回，`num` 是核销数量。次卡会先校验 `time_card` 剩余次数；
- **判断结果。** 只看顶层 `success`。成功时按下标保存 `consumeCredential`，撤销时要用。`platformResult` 保留抖音原始的 `data` 和 `extra`，排查问题时看它；
- **计费。** 预核销不计费，核销计费，撤销免费。

```http
POST /api/hexiao/v2/ddzh-tuangou-receipt-prepare
Authorization: Bearer <key>
Content-Type: application/json

{
  "shopId": 123,
  "platform": 2,
  "code": "<扫码得到的完整短链，或数字券码>"
}
```

有两件事，走第三方接口也不会变：每次核销前都要重新预核销，“一次准备配一次验券”的规则还在；撤销时限仍然是1小时。

| | 自己入驻抖音服务商 | 通过第三方接口（如无限聚核） |
|---|---|---|
| 资质 | 入驻认证、对公认证 | 不需要自己的服务商资质 |
| 上线前 | 联调、压测、安全三项验收 | 商家授权后用真实券调试 |
| 自己要实现的 | `client_token`、`poi_id` 映射、验券准备、短链解析、三层结果判断 | 调统一的验券、核销、撤销接口 |
| 能用的能力 | 解决方案里开通的全部能力 | 以第三方开放的接口为准 |
| 成本 | 研发、验收和持续运维 | 按核销次数付费 |

## 十一、常见问题

### 抖音团购券能直接用明文券码核销吗？

不能直接核销。明文券码只能作为验券准备接口的 `code` 参数，验券接口的 `encrypted_codes` 要传验券准备返回的加密券码。把明文券码直接传给验券接口，会报加密码解密失败一类的错误。

### 抖音验券报“加密码解密失败”是什么原因？

最常见的原因是 `encrypted_codes` 里传了明文券码，或者传了别的订单、上一次验券准备得到的加密券码。每次验券前重新调验券准备，用这一次返回的 `verify_token` 和 `encrypted_code`。

### 同一个 verify_token 可以用两次吗？

不建议。抖音文档要求验券准备和验券的调用次数一一对应，验券成功后二维码会失效。明确失败的，重新调验券准备再验；结果未知的，先查券状态再决定。

### 顾客买了3张券，扫码后只想用1张怎么办？

扫码后验券准备会返回这个订单下全部可用的券。让顾客选数量，只把对应数量的 `encrypted_code` 放进 `encrypted_codes`，不要整组传入。

### 抖音次卡怎么一次核销多次？

按抖音验券接口文档，多次卡一次核销多份时，在 `encrypted_codes` 里放多个相同的加密券码。放之前用 `time_card` 的 `times_count` 减去 `times_used` 算出剩余次数，次数不够就不要发起核销。

### 抖音团购核销接口的金额单位是什么？

抖音验券准备返回的金额都以分为单位，`original_amount` 是套餐售价，`pay_amount` 是用户实付，划线价是 `list_market_amount`。美团套餐价以元为单位，两个平台混合对账时统一换算成分。

### 抖音团购券核销后多久内可以撤销？

抖音撤销核销接口文档写明，验券超过一个小时就不可再撤销。撤销要带验券时返回的 `verify_id` 和 `certificate_id`，`data.error_code` 和 `extra.error_code` 均为0才算撤销成功。

### 不是抖音服务商，能调用抖音团购核销接口吗？

不能直接调用。抖音生活服务接口要求服务商完成入驻认证、开通解决方案、通过联调压测安全三项验收并上线应用，商家还要把应用授权给你。没有这些条件的，可以用已接入抖音的门店系统，或者通过无限聚核这类第三方接口核销。

### 通过无限聚核核销抖音券，和核销美团券有什么不同？

接口是同一套，`platform` 传2代表抖音，`code` 除了数字券码，还可以传扫码得到的完整抖音短链。金额口径两个平台一致：`payAmount` 和 `actualPayAmount` 以分为单位，`dealPrice` 以元为单位。抖音的撤销时限是1小时，美团是10分钟。
