做门店 SaaS 系统,怎么给几十上百家商户开通美团、抖音团购核销?
核销服务由无限聚核提供,美团、抖音团购核销一站式接入。

目录(8 节)
做自助洗车、24小时健身房、自助台球厅 SaaS 的团队,产品跑稳以后,销售转过来的需求很快会变成一句话:“客户问能不能直接核美团券、抖音券。”第一家商户接进来并不难,麻烦从第30家开始:哪些门店授权了,哪些链接发出去三天没人点,哪家的授权悄悄失效了,核销次数该记在谁头上,商户不续费了又怎么收尾。
这篇只讲规模化开通时 SaaS 后台要做的事。单个小程序怎么接预核销、核销和撤销,本站《微信小程序接入美团、抖音团购核销实战》已经写过,这里不再重复。
一句话回答: SaaS 给商户开通团购核销,有三条路:让每个商户自己去申请开放平台能力,基本走不通;SaaS 公司自己成为美团、抖音的技术服务商,要过保证金、安全资质和平台验收;或者接无限聚核这类第三方聚合接口,由第三方持有平台资质。走哪条路,系统里都要做好四件事:
- 自己的每家门店对应一个核销门店 ID,授权按“门店 × 平台”记录;
- 授权只能由商户本人确认,SaaS 负责按门店发链接、跟进度;
- 授权状态靠回调更新得快,靠每日对账和调用报错保证准确;
- 核销次数要能分到商户,余额不足提前提醒。

一、三条路,先把成本算清楚
| 商户各自申请 | SaaS 自己做服务商 | 接第三方聚合接口(如无限聚核) | |
|---|---|---|---|
| 平台资质在谁名下 | 每个商户 | SaaS 公司 | 第三方 |
| 美团门槛 | 同右,每个商户各办一次 | 服务零售团购核销开通时,保证金账户余额须达到10万元,另需 WAF 证明及等保或网络信息安全保险等材料 | 不需要自己满足 |
| 抖音门槛 | 同右,每个商户各办一次 | 注册满一年、注册资本不低于100万元、经营范围含技术开发服务、同主体 ICP 备案,上线前通过联调、压测、安全三项验收 | 不需要自己满足 |
| SaaS 要开发的 | 每家的凭证各自接入 | 两个平台的授权、令牌刷新、验券、核销、撤销全套 | 调统一的授权、验券、核销、撤销接口 |
| 能用的平台能力 | 各自开通的范围 | 开通的全部能力 | 以第三方开放的接口为准 |
| 适合 | 自带技术团队的大连锁 | 门店规模大、团购是核心业务 | 想尽快给存量商户开通 |
表中美团门槛来自美团技术服务合作中心入驻说明,抖音门槛来自抖音开放平台技术服务商入驻指南,均为2026年9月10日核对,以官方后台为准。
第一条路听起来最省事,实际很少走得通。开放平台的核销接口是给技术服务商用的,一家洗车店老板不会为了核券去办这些资质,就算办下来,他也没人写代码。
第二条和第三条的选择,关键看规模和时间。自己做服务商,资质和验收要几个月,之后能用的平台能力最全,门店多了单次成本也能压下来。接第三方聚合,本周就能开始调试,代价是能力范围受第三方限制、按核销次数付费。不少团队先接第三方把存量商户开起来,等门店规模大了,再评估要不要自己入驻。
二、数据模型:一家门店,一个核销门店 ID

多租户 SaaS 最容易犯的错,是在商户上记一个“已开通团购核销”就算完事。平台授权、核销和计费都是按门店走的:同一个老板的三家健身房,可能两家在美团授权了,一家还没有;抖音那边可能是另一个账号在管。
建议单独建一张门店绑定表,一行对应自己的一家门店:
CREATE TABLE groupbuy_store_binding (
local_store_id BIGINT PRIMARY KEY, -- 自己系统的门店
local_tenant_id BIGINT NOT NULL, -- 自己系统的商户(租户)
hx_merchant_id BIGINT NOT NULL, -- 核销服务里的商户 ID
hx_shop_id BIGINT NOT NULL UNIQUE, -- 核销服务里的具体门店 ID
category VARCHAR(16) NOT NULL, -- service_retail / dining
mt_status VARCHAR(16) NOT NULL DEFAULT 'none',
mt_name VARCHAR(128) NOT NULL DEFAULT '', -- 授权后平台返回的显示名
dy_status VARCHAR(16) NOT NULL DEFAULT 'none',
dy_name VARCHAR(128) NOT NULL DEFAULT '',
link_sent_at DATETIME NULL, -- 最近一次发授权链接
checked_at DATETIME NULL, -- 最近一次对账
rejected_at DATETIME NULL -- 最近一次因未授权被拒
);
几个设计要点:
hx_shop_id加唯一约束。 两家门店共用一个核销门店 ID,券就会核到别人家;- 授权状态按平台分开存。 状态建议用
none(未发链接)、sent(已发未授权)、authorized、suspect(调用被拒,待确认)四个值,比一个布尔字段好排查; - 业务类目放在商户上。 同一个 SaaS 如果既服务洗车店又服务餐厅,要按商户区分服务零售和到店餐饮,两类走的授权和接口都不一样;
- 保存平台显示名。 授权后回写平台返回的账号显示名,后台核对“这家店授权的是不是自己的账号”全靠它。
三、批量开通:授权只能商户本人确认
平台授权必须由商户在美团、抖音的页面上自己确认,SaaS 没法替他点。自己做服务商也一样。按抖音开放平台“商服授权”文档(2026年9月29日查看),商家授权服务商应用有三种方式:在服务商的 SaaS 系统里通过授权 SDK 完成,文档推荐度最高;商家登录抖音来客 PC 端,在“店铺管理 > 服务方应用授权”里新增授权;或者服务商在服务商平台发起、商家在来客里确认,这种方式单次只能授权一个抖音商家,商家 ID 和名字必须严格匹配。
所以 SaaS 能做的,是让商户少走弯路:
- 一家门店、一个平台,一条链接。 不要把一堆链接丢进老板群里。连锁老板一个账号管着好几家店,授权页面上选错门店是最常见的问题;
- 链接指向自己的中转页。 短信、企业微信或 SaaS 后台待办里放的是自己系统的地址,比如
/groupbuy/auth/start?store=1024&p=mt&sig=...。商户点开时,后端再实时去要平台授权链接并跳转。这样可以记录谁点过,也不用担心提前生成的链接过期; - 中转页写清楚要用哪个账号。 例如“请用管理‘滨江店’的美团账号登录”,比任何说明文档都管用;
- 授权完先核对再放开。 收到授权成功后,把平台显示名展示给商户确认。名字对不上的,先别打开这家店的扫码核销入口;
- 看板按天跟进。 发出链接超过3天没授权的,转给客服打电话。实际推进中,卡住的多半不是技术问题,而是老板不知道哪个账号是管这家店的。

四、授权状态:回调负责快,对账负责准
授权成功通常能收到回调,失效却往往不会通知你。商户在平台侧取消授权、授权到期、换了账号,系统里还显示“已授权”,直到顾客扫码时才报错。
所以授权状态要从三个地方更新:
| 来源 | 更新什么 | 注意 |
|---|---|---|
| 授权成功回调 | 标记为 authorized,回写显示名 |
快,但可能丢 |
| 每日对账 | 按门店列表逐个核对授权状态和显示名 | 准,但有一天延迟 |
| 核销调用报错 | 返回“平台未授权”类错误时,标记为 suspect 并通知商户 |
最及时,但要等到有人来核券 |
以无限聚核为例,它的公开 API 文档对 Webhook 写得很直接:业务事件 merchant.authorization.succeeded 在首次授权和重新授权成功时推送;每个事件只做一次 best-effort 投递,总超时1.5秒,不自动重试;当前不推送授权过期、授权撤销事件;开发者必须通过查询接口核对最终授权状态,不能把 Webhook 当作唯一事实来源。对账用“商户列表”和“门店列表”接口:门店列表返回每家门店的 meituanAuthorized、douyinAuthorized 和平台显示名,文档说明这是已保存的有效授权记录,不是实时可调用性检查。调用验券时返回 PLATFORM_UNAUTHORIZED,就是第三个信号。
对账时有个小坑:门店列表的分页是实时视图,翻页期间有记录更新,就可能出现重复或遗漏。对账逻辑按 shopId 做集合比对,一轮跑完再统一更新,不要边翻页边改状态。
回调接收端按文档要求做,下面是 Go 的写法:
// 接收无限聚核 Webhook:先验签和时间窗,再按 eventId 去重
func handleWebhook(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
ts := r.Header.Get("X-Event-Timestamp")
sec, err := strconv.ParseInt(ts, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-sec)) > 300 { // 5分钟重放窗口
w.WriteHeader(http.StatusUnauthorized)
return
}
mac := hmac.New(sha256.New, []byte(apiKey)) // 签名密钥就是当前 API Key
mac.Write([]byte(ts + "." + string(raw))) // 用原始请求体,不要先解析再序列化
want := "v1=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(want), []byte(r.Header.Get("X-Event-Signature"))) {
w.WriteHeader(http.StatusUnauthorized)
return
}
if id := r.Header.Get("X-Request-Id"); validRequestID(id) {
w.Header().Set("X-Request-Id", id) // 合法值原样回显
}
var ev struct {
EventID string `json:"eventId"`
Event string `json:"event"`
Data json.RawMessage `json:"data"`
}
if json.Unmarshal(raw, &ev) != nil || ev.EventID != r.Header.Get("X-Event-Id") {
w.WriteHeader(http.StatusBadRequest)
return
}
switch ev.Event {
case "webhook.endpoint.verification": // 保存 URL 时的握手,3秒内回显 challenge
var d struct{ Challenge string `json:"challenge"` }
json.Unmarshal(ev.Data, &d)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"challenge": d.Challenge})
case "merchant.authorization.succeeded":
// 在同一个事务里写入 eventId(唯一索引)和门店授权状态,重复事件直接忽略
if err := markAuthorized(r.Context(), ev.EventID, ev.Data); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
default:
w.WriteHeader(http.StatusNoContent) // 未知事件也回2xx
}
}
两个容易忽略的地方:API Key 轮换后,后续事件立刻用新 Key 签名,接收端没同步就会全部验签失败;回调里只写状态,发短信、刷新看板这类慢操作放到队列里,别让1.5秒的超时把事件丢掉。
如果是自己做服务商、自己接平台 OAuth,还要多管几件事:授权回调里的 state 用足够长的随机值,设短有效期并且只能用一次;多实例刷新令牌时先加锁,拿到锁后再查一次是否已被别的实例刷新;美团这类每次刷新都会换新 refresh token 的,先把新令牌写进数据库,再更新缓存;refresh token 失效时把门店标成“需重新授权”,而不是无限重试。
五、核销次数算谁的
计费有两种常见做法。一种是 SaaS 统一购买核销次数,再按月向商户收服务费,结算简单,但商户之间用量差得很远,高频门店会把 SaaS 的利润吃掉。另一种是让商户自己充值,次数记在商户名下,SaaS 不用垫钱,也不用替商户解释账单。

以无限聚核为例,这两种做法都能实现:
- 给商户单独充值。 “查询可充值金额”接口传商户 ID 作为
shopId,返回这个商户可买的档位;省略或传0,查的是账号自己的档位。再用“创建充值订单”下单,返回的wxPayQrCodeUrl原样渲染成二维码给商户扫,订单30分钟内有效; - 扣减顺序。 按当前实现,门店核销时先扣所属商户的专属次数,商户次数为0后再扣账号次数,两边都没有时返回
QUOTA_EXHAUSTED; - 查询余额。 “查询剩余核销次数”接口不传
shopId时查账号次数,data.source为admin;传商户 ID 时查该商户的专属次数,data.source为store; - 哪些计费。 预核销、团购列表和撤销核销不计费,核销成功才计费。
这里有一个设计上的取舍:账号里留着次数,商户用完后就会自动用账号次数兜底,顾客不会被拦下,但这部分是 SaaS 在买单。如果不想兜底,账号次数保持为0,商户次数用完就会被拒,靠余额提醒让商户及时充值。
余额提醒建议每天跑一次:按最近7天日均核销量算出还能用几天,少于5天提醒商户,少于2天同时提醒客服。只看剩余次数不够,一家日核销80次的健身房和一家日核销3次的台球厅,剩100次的紧迫程度完全不同。
六、商户到期、门店关停怎么收尾
开通做得再顺,收尾没做好,一样会出问题:商户不续费了,店员还能扫码核销;或者门店删了,顾客刚才那张误核的券撤销不了。
收尾建议按这个顺序:
- 先关自己系统的核销入口。 扫码页、店员端的核销按钮都停掉,这一步最快也最可控;
- 过了撤销时限再删门店。 美团通过开放接口核销的券只能在10分钟内撤销,抖音是1小时。最后一笔核销超过1小时,再删核销门店;
- 让商户在平台侧取消授权。 以无限聚核为例,美团服务零售可以通过“获取美团取消授权链接”接口拿到链接,交给商家在美团页面确认。文档特别提醒,这个链接不携带门店 ID,取消范围由商家在美团页面核对确认,同一账号下的其他门店可能也会受影响,拿到链接也不代表取消已经完成。抖音侧解除授权,以抖音来客当前的页面操作为准;
- 删除核销门店和商户。 无限聚核的“删除商户或门店”是软删除,删除后会停用这家门店的美团、抖音平台路由。商户下还有未删除的门店时,删除商户会返回 HTTP 400,接口不会级联删除,要先逐一删除门店;
- 保留核销记录。 核销流水和撤销凭证至少保留到对账周期结束,商户事后对账、顾客投诉都要用。
七、以无限聚核为例:一家新门店的开通顺序
把上面几节串起来,SaaS 后台新开一家门店,调用顺序是这样的:
- 新商户先调“创建商户或门店”接口建商户,不传
masterShopId,businessCategory传service_retail或dining,返回的是商户 ID; - 同一接口传
masterShopId建具体门店,返回的shopId才是授权和核销用的门店 ID,写进绑定表; - 商户点中转页时,调“获取美团/抖音授权链接”接口,
platform传1是美团、2是抖音; - 收到 Webhook 或对账发现授权成功后,把平台显示名给商户确认,再打开这家店的核销入口;
- 按需给商户充值,或者先用账号次数;
- 之后的验券、核销、撤销和单店接入完全一样。
POST /api/hexiao/v2/store/create
Authorization: Bearer <key>
Content-Type: application/json
{
"shopName": "滨江店",
"masterShopId": 10086
}
POST /api/hexiao/v2/get/auth/url
Authorization: Bearer <key>
Content-Type: application/json
{
"shopId": 123,
"platform": 2
}
POST /api/hexiao/v2/store/list
Authorization: Bearer <key>
Content-Type: application/json
{
"masterShopId": 10086,
"page": 1,
"pageSize": 100
}
授权、核销类接口只接受具体门店 ID,传商户 ID 会返回 HTTP 400;充值档位查询则反过来,传的是商户 ID。这两个 ID 在绑定表里分两列存,调用前按接口取对应的那一列。
八、常见问题
SaaS 系统接美团、抖音团购核销,需要每个商户自己去申请开放平台吗?
不需要,也很难走通。开放平台的核销能力是给技术服务商用的,门店老板既没有研发,也很少会为此去办资质。常见做法是 SaaS 公司自己成为技术服务商,或者接无限聚核这类第三方聚合接口,商户只需要完成授权。
SaaS 公司自己成为美团、抖音服务商,要满足什么条件?
美团服务零售团购核销开通时,保证金账户余额须达到10万元,另需 WAF 证明及等保或网络信息安全保险等材料;抖音技术服务商认证要求注册满一年、注册资本不低于100万元、经营范围含技术开发服务、同主体 ICP 备案,上线前还要通过联调、压测、安全三项验收。以上为2026年9月10日核对,以官方后台为准。
商户的平台授权可以由 SaaS 后台替他完成吗?
不可以。授权必须由商户在美团、抖音的页面上自己确认。SaaS 能做的是按门店生成授权入口,写清楚用哪个账号登录,授权后回写平台显示名给商户核对,并跟进迟迟没授权的门店。
怎么知道哪些门店的团购授权已经失效?
不能只等回调。授权过期、商户取消授权通常没有推送,要靠每日对账核对门店列表里的授权状态,再加上验券时返回“平台未授权”类错误的实时信号。以无限聚核为例,当前 Webhook 只推送授权成功,调用返回 PLATFORM_UNAUTHORIZED 时就应把门店标记为待重新授权。
一个老板有好几家店,授权要做几次?
按门店和平台分别做。以无限聚核为例,授权链接按具体门店 shopId 和 platform 生成,三家店都要开美团和抖音,就是六次授权。连锁老板用一个平台账号管多家店时,授权页面上要选对门店,这是最容易出错的一步。
核销次数能按商户分开计算吗?
可以。以无限聚核为例,查询充值档位时传商户 ID,就能给这个商户单独充值;按当前实现,门店核销时先扣所属商户的专属次数,用完再扣账号次数。不想替商户兜底的,账号次数保持为0,商户次数用完会返回 QUOTA_EXHAUSTED。
商户不续费了,怎么停掉他的团购核销?
先关掉自己系统里的核销入口,等最后一笔核销超过撤销时限(美团10分钟、抖音1小时),再删除核销门店,并让商户在平台侧取消授权。以无限聚核为例,门店删除是软删除,会停用这家门店的平台路由;商户下还有门店时不能直接删除商户。
无限聚核的 Webhook 投递失败会重试吗?
不会。按无限聚核当前公开 API 文档,每个业务事件只做一次 best-effort 投递,总超时1.5秒,失败不自动重试,也不影响商家授权成功。接收端要尽快返回2xx,并用门店列表接口对账核对最终授权状态。



