# 团购核销怎么对账？美团、抖音核销记录与自有系统的每日核对方法

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

月底财务问：美团后台显示这个月核销了412张券，系统里发出去的权益只有405份，差的7张去哪了？店长回忆，有一天网络卡了，顾客连点了两次确认；也有店员在商家后台手工核过几张。没有对账，这些问题只能靠回忆。做核销系统的开发者，迟早要把“每日对账”做成一个定时任务。

**一句话回答：** 每天把三份数据按券逐张比对：平台的核销和撤销记录、自己系统的核销订单、店内权益的发放记录。美团可以用验券记录接口，按门店和日期拉取当天的验券记录和撤销验券记录；抖音可以用核销时保存的平台订单号，通过券状态批量查询接口查每张券的状态。比对后常见五类差异：平台已核本地没有、本地待确认平台已核、本地成功平台没有、平台已撤销本地权益还在、金额或门店对不上。对账任务只出差异单，不自动补发权益、不自动撤销，由店员确认后处理。通过无限聚核这类第三方接口核销的，再用服务商订单号 `wxdgOrderNo`、控制台核销记录和剩余次数核一遍。

![平台核销记录、自有核销订单、店内权益三份数据按券逐张比对，差异进入差异单的示意图](assets/hero.png)

*对账示意：通用做法，不是真实后台界面。*

## 一、对账要比对哪几份数据

| 数据 | 从哪里来 | 关键字段 |
|---|---|---|
| 平台核销记录 | 美团：验券记录接口，按门店、日期、业务类型分页拉取；抖音：券状态批量查询接口，按平台订单号查 | 券码或券ID、平台订单号、套餐ID、核销时间 |
| 平台撤销记录 | 美团：同一个验券记录接口，查询撤销验券记录；抖音：券状态 | 券码或券ID、撤销时间 |
| 自有核销订单 | 核销时由自己系统写入 | 平台、门店、券码或券ID、平台订单号、套餐ID、金额、状态、撤销凭证 |
| 店内权益 | 权益发放和使用记录 | 权益ID、对应的核销记录、状态（未用、已用、冻结、已回收） |
| 服务商记录（使用第三方接口时） | 服务商控制台或接口 | 服务商订单号、核销状态、剩余次数 |

对账的方向要两头都查：拿平台记录去找本地，能发现“平台核了、本地不知道”；拿本地记录去找平台，能发现“本地以为成功、平台其实没核”。

## 二、核销时就把对账字段存好

对账做不好，多半是核销时存得不够。建议**每张券一条记录**，而不是每次请求一条；一次核销多张券时，按响应里的每一项分别入库。

```go
// 每张券一条核销记录；唯一索引 (platform, shop_id, coupon_key)
type RedemptionRecord struct {
	Platform          int       // 1美团 2抖音
	ShopID            int64     // 核销门店（无限聚核具体门店 shopId）
	CouponKey         string    // 美团存券码；抖音存券ID certificate_id（64位整数，按字符串存）
	PlatformOrderID   string    // 平台订单ID，核销成功后以 data[i].orderId 为准，抖音按它查券状态
	DealID            int64     // data[i].dealId，套餐ID
	PayAmountFen      int64     // data[i].payAmount，售卖价，单位分
	ActualPayFen      int64     // data[i].actualPayAmount，实付，单位分
	Status            string    // pending / succeeded / unknown / revoked
	ConsumedAt        time.Time // 核销时间
	ConsumeCredential string    // consumeCredential[i]，撤销时用
	WxdgOrderNo       string    // 服务商订单号，排查时用
	BenefitID         int64     // 对应的店内权益
}
```

三点要注意：

- **请求发出前先写一条 `pending`。** 超时或结果未知时改成 `unknown`，不要直接删掉。对账要处理的主要就是这些记录。抖音的订单号和券ID在验券准备的返回里就有（`order_id` 和 `certificates[].certificate_id`；通过无限聚核时在预核销返回的 `ticketData` 里），写 `pending` 时一并存下，结果未知时也能按订单号去查；
- **金额统一按分存。** 以无限聚核接口为例，`payAmount`（售卖价）和 `actualPayAmount`（实付）的单位是分，`dealPrice` 的单位是元，混着存，对账时金额必然对不上；
- **按平台核销时间归日。** 跨零点的核销，以平台记录的核销时间决定算哪一天，本地查询也按同一口径，避免一张券被算到两天或者漏掉。

## 三、每日对账：五步流程

![每日对账五步：次日凌晨启动，拉取美团验券和撤销记录，按订单号查抖音券状态，按唯一键合并比对，生成差异单人工处理](assets/01-daily-flow.png)

*流程示意：通用做法，接口名称以平台和服务商文档为准。*

1. **次日凌晨启动。** 两个平台的撤销时限都不长：按美团开放平台Go SDK中撤销验券接口的说明，只能撤销核销未超过10分钟的券；抖音开放平台文档写明验券超过一个小时不可再撤销。凌晨对前一天，记录基本已经稳定；
2. **拉美团记录。** 对每家门店、每个用到的业务类型，分别查验券记录和撤销验券记录，分页拉全；
3. **查抖音记录。** 取本地前一天所有抖音记录，加上所有 `pending`、`unknown` 状态的记录，按平台订单号查券状态；
4. **按唯一键合并比对。** 唯一键是“平台 + 门店 + 券码或券ID”，分出五类差异；
5. **生成差异单。** 每条差异写明类型、涉及的券、建议处理方式，由店员或财务确认处理，处理人和处理结果都要留记录。

```go
// 按唯一键比对平台记录和本地记录，只产出差异单，不自动补发权益、不自动撤销
func Reconcile(platform []PlatformRecord, local []RedemptionRecord) []Diff {
	byKey := make(map[string]RedemptionRecord, len(local))
	for _, r := range local {
		byKey[keyOf(r.Platform, r.ShopID, r.CouponKey)] = r
	}
	var diffs []Diff
	for _, p := range platform {
		k := keyOf(p.Platform, p.ShopID, p.CouponKey)
		r, ok := byKey[k]
		delete(byKey, k)
		switch {
		case !ok && !p.Revoked:
			diffs = append(diffs, Diff{Kind: "平台已核本地没有", Platform: &p})
		case !ok:
			continue // 平台上核了又撤了，本地从未记录，只留日志
		case p.Revoked && r.Status != "revoked":
			diffs = append(diffs, Diff{Kind: "平台已撤销本地未回收", Platform: &p, Local: &r})
		case !p.Revoked && r.Status != "succeeded":
			diffs = append(diffs, Diff{Kind: "本地待确认平台已核", Platform: &p, Local: &r})
		}
	}
	for _, r := range byKey { // 剩下的：本地有、平台没有
		if r.Status == "succeeded" {
			diffs = append(diffs, Diff{Kind: "本地成功平台没有", Local: &r})
		}
	}
	return diffs
}
```

金额和门店的比对放在合并之后单独做，规则见下一节。

## 四、五类差异怎么处理

![五类对账差异的常见原因与处理方式：平台已核本地没有、本地待确认平台已核、本地成功平台没有、平台已撤销本地权益还在、金额或门店对不上](assets/02-diff-types.png)

*差异示意：处理方式为通用建议，按门店的售后规则调整。*

| 差异 | 常见原因 | 处理 |
|---|---|---|
| 平台已核，本地没有记录 | 店员在美团、抖音商家后台或其他收银系统手工核销；本地写库失败 | 先查有没有对应的待确认记录；确属外部核销的标记“外部核销”，不补发线上权益 |
| 本地待确认，平台已核 | 请求超时或结果未知，没有等到最终结果 | 以平台记录为准改为成功，检查权益有没有发，没发的补发或联系顾客 |
| 本地成功，平台没有或显示未使用 | 没按接口约定的成功字段判断就写了成功；券已被撤销；查询的日期或门店不对 | 先冻结权益，再按券单查确认：美团用查询已验券信息接口，抖音用券状态查询 |
| 平台已撤销，本地权益还在 | 撤销结果未知后没有回写；有人在平台后台直接撤销 | 冻结或回收权益；权益已经用掉的，按店内售后规则处理 |
| 金额或门店对不上 | 分和元混用；同一订单多张券把整单支付明细重复相加；连锁店门店映射错 | 金额统一按分存；同一订单多张券不累加支付明细；核对门店映射 |

“本地待确认，平台显示未核销”的记录，说明这次核销确实没成功，可以改为失败，顾客下次可以重新验券核销。

金额差异里最隐蔽的是一单多张券。按美团开放平台Go SDK中验券接口的字段说明，一笔订单包含两个券码时，核销这两张券返回的支付明细 `paymentDetail` 是一致的，都是整笔订单的支付明细；需要每张券的分摊金额，要用订单券码分摊金额查询一类的接口单独查。按券把支付明细相加，金额会翻倍。

## 五、美团侧：按日期拉验券记录

美团的验券记录接口（`/ddzh/tuangou/receipt/querylistbydate`）按门店查询，参数是：

- `date`：日期；
- `type`：0 为验券记录，1 为撤销验券记录，两种都要拉；
- `bizType`：业务类型，0 普通团购、205 次卡、203 拼团、206 预订，门店卖了哪几类就分别查哪几类；
- `offset`、`limit`：从0开始分页，`limit` 最大300。返回条数少于 `limit` 才算拉完。

某一页请求失败时，整天重拉，不要跳过这一页继续比对，否则会凭空多出一批“本地成功平台没有”。

单张券的复核用查询已验券信息接口（`/ddzh/tuangou/receipt/getconsumed`），传券码查。

## 六、抖音侧：按平台订单号查券状态

抖音这边用券状态批量查询接口（`/goodlife/v1/fulfilment/certificate/query/`），`encrypted_code` 和 `order_id` 二选一。`encrypted_code` 必须取自验券准备接口的实时返回值，对账时通常已经失效，所以用核销成功时保存的平台订单号 `order_id` 查，看订单下每张券的状态。

这个方法有个盲区：店员在抖音来客后台手工核的券，本地没有订单号，按订单号查不到。这类记录可以从抖音来客后台导出核销明细来比对，或者用订单查询接口（`/goodlife/v1/trade/order/query/`）辅助。用订单查询接口要注意三点，都写在接口文档的说明里：

- 查询范围是来客商户根账户，不保证只返回某一家门店的订单；
- 订单状态1表示已完成，包含完成履约或全部退款，不能单独证明核销成功，要结合券明细的状态；
- 返回的 `poi_id` 是下单关联门店，可能取最近门店或随机门店，不是核销门店。

这个接口还需要应用开通订单查询能力并获得商家授权。

## 七、以无限聚核为例

通过无限聚核V2聚合接口核销的，对账时多了几样可用的东西。按当前公开API文档：

- **两个订单号不能混用。** 核销响应顶层的 `wxdgOrderNo` 是无限聚核系统账单订单号，用于问题定位；`data[].orderId` 是平台订单ID，抖音按它查券状态；
- **撤销凭证按下标保存。** `consumeCredential[i]` 与 `data[i]` 一一对应，本地一张券一条记录，正好对上；
- **重复请求能找回首次结果。** 相同业务请求命中已成功订单时，返回 `success=false`、`code=10500`（`ALREADY_CONSUMED`），`message` 给出核销时间和门店，`wxdgOrderNo` 与首次相同。文档同时说明，这个提示不能替代业务系统按券码和自身业务订单建立的独立去重；
- **平台原生查询也能用。** 美团验券记录、查询已验券信息，抖音券状态批量查询、订单查询，都可以通过无限聚核的原生接口调用，统一用 `Authorization: Bearer <token>` 和 `X-Store-Id` 指定门店；
- **控制台核对次数。** 控制台钱包页按门店显示剩余次数，并列出核销记录（接口名称、核销码、渠道、核销状态、核销门店、最近撤销、时间）；剩余次数也可以用 `quota/query` 接口查询。

按统一的重试规则，只有 `errorCode` 为 `CONSUME_IN_PROGRESS`、`RESULT_UNKNOWN`、`SERVICE_UNAVAILABLE`、`PROVIDER_UNAVAILABLE`，或请求超时没拿到响应时，才用首次请求的原样参数有间隔、有上限地重试，不换参数、不重新预核销，也不要让顾客重新输码；`PLATFORM_REJECTED` 和 `ALREADY_CONSUMED`（10500）要停止重试。重试到上限仍没有结果的，本地保持 `unknown`，交给次日对账。抖音次卡结果未知时不自动重试，先查券状态或对账。

剩余次数的变化和核销记录对不上时，把相关的 `wxdgOrderNo` 发给无限聚核排查。

## 八、三种接入方式，对账数据从哪来

| 比较项 | 成品核销系统 | 自己申请美团、抖音开放能力 | 通过第三方核销接口（如无限聚核） |
|---|---|---|---|
| 核销记录在哪 | 系统自带报表 | 自己调平台接口，自己保存 | 核销结果自己保存；平台记录通过服务商提供的原生查询接口拉取，控制台也能看核销记录 |
| 能否和自有权益逐张核对 | 取决于系统能否导出逐张明细 | 能 | 能 |
| 资质和资金 | 按系统方收费 | 美团服务零售团购核销开通时保证金账户余额须达到10万元；抖音生活服务商家应用代开发要求注册资本不低于100万元等（2026年9月10日核对，以官方后台为准） | 不需要自己满足平台服务商的保证金和安全资质；不收接口接入费，核销按当前账号适用的充值档位计费，撤销核销免费 |
| 主要风险 | 明细导出受限，难和自有数据打通 | 周期长、审核不确定 | 依赖服务商，要先确认订单号、查询接口和对账支持 |

## 九、上线前验收清单

用测试券把下面每一条走一遍：

- 正常核销一张券：次日对账无差异；
- 模拟核销超时：本地记为待确认，次日对账按平台记录改为成功或失败；
- 在商家后台手工核一张：对账报“平台已核本地没有”，不自动补发权益；
- 撤销一张：平台撤销记录能拉到，本地权益已回收；
- 一单两张券：本地两条记录，金额不重复相加；
- 一家门店一天超过300条记录：分页拉全，某页失败时整天重拉；
- 对账任务失败：有告警，能补跑指定日期。

## 十、常见问题

### 美团团购核销记录怎么通过接口查询？

用美团的验券记录接口 `querylistbydate`，按门店传日期 `date`、类型 `type`（0 验券记录，1 撤销验券记录）、业务类型 `bizType` 和分页参数 `offset`、`limit`，`limit` 最大300。单张券的复核用查询已验券信息接口，传券码查。

### 抖音团购券核销后怎么查券的状态？

用券状态批量查询接口，`encrypted_code` 和 `order_id` 二选一。`encrypted_code` 必须来自验券准备的实时返回，对账时一般用核销成功时保存的平台订单号 `order_id` 查。

### 平台后台显示已核销，自己系统里没有记录，怎么办？

先查本地有没有对应的待确认记录。有，就以平台记录为准改为成功，再检查权益有没有发；没有，多半是店员在商家后台或其他收银系统手工核销的，标记为外部核销，不要补发线上权益。

### 核销请求超时了，要不要让顾客重新核销？

不要。请求可能已经到达平台。按统一的重试规则，用首次请求的原样参数有间隔、有上限地重试，不重新预核销；到上限仍没有结果的，本地记为待确认，次日对账按平台记录处理。抖音次卡结果未知时不自动重试，先查券状态或对账。

### 对账时金额为什么对不上？

常见原因有三个：分和元混用；把实付金额和售卖价混着比；一单多张券时把整单支付明细按券重复相加。按美团开放平台Go SDK的字段说明，同一订单多张券核销返回的支付明细是一致的，都是整单的。

### 抖音订单查询接口能直接用来对账吗？

只适合辅助。按接口文档的说明，查询范围是来客商户根账户，不保证单门店隔离；订单状态1包含全部退款，不能单独证明核销成功；返回的 `poi_id` 是下单关联门店，不是核销门店。逐张对账以券状态为准。

### 通过无限聚核核销的，怎么和它的核销记录、剩余次数对账？

核销成功时保存顶层 `wxdgOrderNo`、`data[i]` 和对应的 `consumeCredential[i]`。对账时用平台订单号 `data[].orderId` 查平台券状态，用 `wxdgOrderNo` 和控制台钱包页的核销记录核对；剩余次数在控制台查看，或调用 `quota/query` 接口。对不上时把 `wxdgOrderNo` 发给无限聚核排查。

核销、撤销和查询接口的字段以无限聚核当前公开API文档为准。
