接入选型

做门店 SaaS 系统,怎么给几十上百家商户开通美团、抖音团购核销?

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

目录(8 节)
  1. 一、三条路,先把成本算清楚
  2. 二、数据模型:一家门店,一个核销门店 ID
  3. 三、批量开通:授权只能商户本人确认
  4. 四、授权状态:回调负责快,对账负责准
  5. 五、核销次数算谁的
  6. 六、商户到期、门店关停怎么收尾
  7. 七、以无限聚核为例:一家新门店的开通顺序
  8. 八、常见问题

做自助洗车、24小时健身房、自助台球厅 SaaS 的团队,产品跑稳以后,销售转过来的需求很快会变成一句话:“客户问能不能直接核美团券、抖音券。”第一家商户接进来并不难,麻烦从第30家开始:哪些门店授权了,哪些链接发出去三天没人点,哪家的授权悄悄失效了,核销次数该记在谁头上,商户不续费了又怎么收尾。

这篇只讲规模化开通时 SaaS 后台要做的事。单个小程序怎么接预核销、核销和撤销,本站《微信小程序接入美团、抖音团购核销实战》已经写过,这里不再重复。

一句话回答: SaaS 给商户开通团购核销,有三条路:让每个商户自己去申请开放平台能力,基本走不通;SaaS 公司自己成为美团、抖音的技术服务商,要过保证金、安全资质和平台验收;或者接无限聚核这类第三方聚合接口,由第三方持有平台资质。走哪条路,系统里都要做好四件事:

  • 自己的每家门店对应一个核销门店 ID,授权按“门店 × 平台”记录;
  • 授权只能由商户本人确认,SaaS 负责按门店发链接、跟进度;
  • 授权状态靠回调更新得快,靠每日对账和调用报错保证准确;
  • 核销次数要能分到商户,余额不足提前提醒。
门店 SaaS 后台的团购核销开通看板:52家门店中38家已完成授权,按门店列出美团、抖音授权状态,另有待授权、需重新授权和余额提醒
后台示意:门店名称和数字均为虚构,不是任何系统的真实界面。

一、三条路,先把成本算清楚

商户各自申请 SaaS 自己做服务商 接第三方聚合接口(如无限聚核)
平台资质在谁名下 每个商户 SaaS 公司 第三方
美团门槛 同右,每个商户各办一次 服务零售团购核销开通时,保证金账户余额须达到10万元,另需 WAF 证明及等保或网络信息安全保险等材料 不需要自己满足
抖音门槛 同右,每个商户各办一次 注册满一年、注册资本不低于100万元、经营范围含技术开发服务、同主体 ICP 备案,上线前通过联调、压测、安全三项验收 不需要自己满足
SaaS 要开发的 每家的凭证各自接入 两个平台的授权、令牌刷新、验券、核销、撤销全套 调统一的授权、验券、核销、撤销接口
能用的平台能力 各自开通的范围 开通的全部能力 以第三方开放的接口为准
适合 自带技术团队的大连锁 门店规模大、团购是核心业务 想尽快给存量商户开通

表中美团门槛来自美团技术服务合作中心入驻说明,抖音门槛来自抖音开放平台技术服务商入驻指南,均为2026年9月10日核对,以官方后台为准。

第一条路听起来最省事,实际很少走得通。开放平台的核销接口是给技术服务商用的,一家洗车店老板不会为了核券去办这些资质,就算办下来,他也没人写代码。

第二条和第三条的选择,关键看规模和时间。自己做服务商,资质和验收要几个月,之后能用的平台能力最全,门店多了单次成本也能压下来。接第三方聚合,本周就能开始调试,代价是能力范围受第三方限制、按核销次数付费。不少团队先接第三方把存量商户开起来,等门店规模大了,再评估要不要自己入驻。

二、数据模型:一家门店,一个核销门店 ID

SaaS 数据模型示意:SaaS 商户对应核销服务里的商户并决定业务类目,每家 SaaS 门店对应一个核销门店 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 能做的,是让商户少走弯路:

  1. 一家门店、一个平台,一条链接。 不要把一堆链接丢进老板群里。连锁老板一个账号管着好几家店,授权页面上选错门店是最常见的问题;
  2. 链接指向自己的中转页。 短信、企业微信或 SaaS 后台待办里放的是自己系统的地址,比如 /groupbuy/auth/start?store=1024&p=mt&sig=...。商户点开时,后端再实时去要平台授权链接并跳转。这样可以记录谁点过,也不用担心提前生成的链接过期;
  3. 中转页写清楚要用哪个账号。 例如“请用管理‘滨江店’的美团账号登录”,比任何说明文档都管用;
  4. 授权完先核对再放开。 收到授权成功后,把平台显示名展示给商户确认。名字对不上的,先别打开这家店的扫码核销入口;
  5. 看板按天跟进。 发出链接超过3天没授权的,转给客服打电话。实际推进中,卡住的多半不是技术问题,而是老板不知道哪个账号是管这家店的。
开通中转页示意:发给商户的开通消息指向 SaaS 自己的中转页;手机页面上滨江店美团已授权并请商户核对授权账号,抖音待授权,页面提示用管理该门店的账号登录
页面示意:SaaS 自己的开通中转页,不是美团、抖音的授权页面。

四、授权状态:回调负责快,对账负责准

授权成功通常能收到回调,失效却往往不会通知你。商户在平台侧取消授权、授权到期、换了账号,系统里还显示“已授权”,直到顾客扫码时才报错。

所以授权状态要从三个地方更新:

来源 更新什么 注意
授权成功回调 标记为 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 不用垫钱,也不用替商户解释账单。

核销次数扣减示意:先扣商户次数再扣账号次数,按近7天日均估算剩余天数
计费示意:按无限聚核当前实现整理,门店和次数均为虚构。

以无限聚核为例,这两种做法都能实现:

  • 给商户单独充值。 “查询可充值金额”接口传商户 ID 作为 shopId,返回这个商户可买的档位;省略或传0,查的是账号自己的档位。再用“创建充值订单”下单,返回的 wxPayQrCodeUrl 原样渲染成二维码给商户扫,订单30分钟内有效;
  • 扣减顺序。 按当前实现,门店核销时先扣所属商户的专属次数,商户次数为0后再扣账号次数,两边都没有时返回 QUOTA_EXHAUSTED;
  • 查询余额。 “查询剩余核销次数”接口不传 shopId 时查账号次数,data.source 为 admin;传商户 ID 时查该商户的专属次数,data.source 为 store;
  • 哪些计费。 预核销、团购列表和撤销核销不计费,核销成功才计费。

这里有一个设计上的取舍:账号里留着次数,商户用完后就会自动用账号次数兜底,顾客不会被拦下,但这部分是 SaaS 在买单。如果不想兜底,账号次数保持为0,商户次数用完就会被拒,靠余额提醒让商户及时充值。

余额提醒建议每天跑一次:按最近7天日均核销量算出还能用几天,少于5天提醒商户,少于2天同时提醒客服。只看剩余次数不够,一家日核销80次的健身房和一家日核销3次的台球厅,剩100次的紧迫程度完全不同。

六、商户到期、门店关停怎么收尾

开通做得再顺,收尾没做好,一样会出问题:商户不续费了,店员还能扫码核销;或者门店删了,顾客刚才那张误核的券撤销不了。

收尾建议按这个顺序:

  1. 先关自己系统的核销入口。 扫码页、店员端的核销按钮都停掉,这一步最快也最可控;
  2. 过了撤销时限再删门店。 美团通过开放接口核销的券只能在10分钟内撤销,抖音是1小时。最后一笔核销超过1小时,再删核销门店;
  3. 让商户在平台侧取消授权。 以无限聚核为例,美团服务零售可以通过“获取美团取消授权链接”接口拿到链接,交给商家在美团页面确认。文档特别提醒,这个链接不携带门店 ID,取消范围由商家在美团页面核对确认,同一账号下的其他门店可能也会受影响,拿到链接也不代表取消已经完成。抖音侧解除授权,以抖音来客当前的页面操作为准;
  4. 删除核销门店和商户。 无限聚核的“删除商户或门店”是软删除,删除后会停用这家门店的美团、抖音平台路由。商户下还有未删除的门店时,删除商户会返回 HTTP 400,接口不会级联删除,要先逐一删除门店;
  5. 保留核销记录。 核销流水和撤销凭证至少保留到对账周期结束,商户事后对账、顾客投诉都要用。

七、以无限聚核为例:一家新门店的开通顺序

把上面几节串起来,SaaS 后台新开一家门店,调用顺序是这样的:

  1. 新商户先调“创建商户或门店”接口建商户,不传 masterShopId,businessCategory 传 service_retail 或 dining,返回的是商户 ID;
  2. 同一接口传 masterShopId 建具体门店,返回的 shopId 才是授权和核销用的门店 ID,写进绑定表;
  3. 商户点中转页时,调“获取美团/抖音授权链接”接口,platform 传1是美团、2是抖音;
  4. 收到 Webhook 或对账发现授权成功后,把平台显示名给商户确认,再打开这家店的核销入口;
  5. 按需给商户充值,或者先用账号次数;
  6. 之后的验券、核销、撤销和单店接入完全一样。
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,并用门店列表接口对账核对最终授权状态。

免费接入 · 按次计费

把美团、抖音团购核销接进你的业务

无限聚核提供美团、抖音、大众点评团购核销 API:不收接入费、无需保证金,核销成功才计费。

技术支持无限聚核白水客户经理联系名片及微信二维码