开发实战

微信小程序接入美团、抖音团购核销实战:从门店授权到核销与撤销

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

目录(11 节)
  1. 一、先把完整效果串起来
  2. 二、把自己的门店对应到聚核门店
  3. 三、完成商家授权,把凭据放到服务端
  4. 四、接口按用途接,不必一次全部铺开
  5. 五、先认识套餐,再决定发什么权益
  6. 六、预核销后展示确认页,保存原始凭证
  7. 七、确认核销;按 errorCode 决定重试还是核对
  8. 八、先保存成功结果,再完成自己的业务
  9. 九、需要撤销时,使用首次成功的原凭证
  10. 十、联调按业务结果验收
  11. 十一、微信小程序接入团购核销常见问题

顾客买好团购券,打开你的微信小程序,选门店、输入券码、确认套餐。核销成功后,系统给他一份三小时的门店服务权益,接着就能预约或使用服务。

手机打开自助小程序,经业务后端网络调用无限聚核接口的技术动漫示意
技术场景示意:手机中的自助小程序经业务后端调用无限聚核,凭据由服务端保存。

微信小程序接入美团、抖音团购核销,可以通过无限聚核(下文简称聚核)的 V2 服务零售聚合接口串起门店授权、预核销、核销与撤销。本文面向已有小程序和业务后端的开发团队,用 Java 示例把聚核接口的调用结果接回自己的预约或权益系统。

一、先把完整效果串起来

顾客侧需要选店、输码、确认和结果页。后台则要知道:这是谁的券、在哪家门店使用、对应什么套餐,以及成功后发什么权益。尚未确定接口申请路径,可以先看本系列的《微信小程序怎么接入美团、抖音团购核销?申请步骤、材料与成本对比》。

建议采用“小程序 → 自己的业务后端 → 无限聚核”的调用方式。小程序交互围绕自己的业务门店和确认单展开,后端负责权限、门店映射和接口调用,再把核销结果接回预约或会员系统。

小程序、业务后端和无限聚核核销接口的调用流程,以及按 errorCode 区分成功、拒绝、已核销和可重试的处理分支
开发流程示意:先预核销,顾客确认后核销;只有超时、结果未知等可重试情况才用原请求重试。

下文使用 Java 11+ 与 Jackson 写接入示例,保留真实 HTTP 请求、JSON 参数和关键控制流,省略 import、完整服务类和框架配置。代码片段可放在同一服务类中,使用第三章的 postJson 封装;这些是接入示例,不是聚核 SDK,本文未编译代码或调用真实业务接口。

business 是接入方的 BusinessPort 业务接口,负责权限、门店和商品映射、确认上下文、核销记录及权益操作;PreparedRedemption、Receipt 是自己的业务对象,只使用代码中所示访问器。adapter 的 String productId(int platform, String shopId, JsonNode prepareData) 负责商品识别,也由接入方实现。

二、把自己的门店对应到聚核门店

先分清三个层次:自己的业务门店、聚核的商户与门店、美团或抖音门店。聚核商户用于组织一组门店,本身不做授权和核销;本文把每家实际经营的业务门店对应到聚核商户下的一家具体门店,再核对它关联的平台门店。

业务调用使用保存下来的聚核门店 shopId。 别把自己系统的门店 ID、聚核商户 ID 或平台门店 ID 混在同一个字段里;核销类接口只接受具体门店 ID,传商户 ID 会直接返回错误。

业务门店、聚核商户下的门店与美团抖音门店的对应关系
门店关系示意:业务门店保存与聚核门店的映射,平台门店分别核对。

在无限聚核开发者后台的门店列表页,可以看到商户卡片,以及商户下每家门店的美团、抖音授权状态和授权入口。

无限聚核门店列表,使用商户和门店层级并展示逐店的美团、抖音授权状态
真实后台截图,已裁切,敏感信息用色块遮盖:商户名、门店名、门店 ID 和平台账号均已替换;保留“新建商户”“新建门店”和逐店授权入口。

使用控制台准备门店时,先新建商户,再在该商户下新建门店。商户表单填写内部名称并选择业务类目,本文使用服务零售;门店表单在已选商户下填写具体门店名称。

无限聚核新建商户弹窗,可选择服务零售或到店餐饮
真实后台截图:新建商户先填写内部名称并选择业务类目;本文使用服务零售。
无限聚核新建门店弹窗,在已选商户下新增具体门店
真实后台截图,商户名称已用色块遮盖:门店创建后会获得独立 shopId。

如果要把门店入驻放进自己的 SaaS 后台,可以接聚核的“创建商户或门店”接口 store/create,同样分两步:不传 masterShopId 时只创建一个商户,返回的 data.shopId 是商户 ID,不会自动创建门店;再传 masterShopId 在该商户下创建具体门店,这次返回的 data.shopId 才是后续授权和核销使用的门店 ID。业务类目 businessCategory 只在创建商户时生效,门店继承所属商户。

String createMerchant(String merchantName) throws IOException, InterruptedException {
    business.requireAccountAdmin();
    ObjectNode request = json.createObjectNode().put("shopName", merchantName)
            .put("businessCategory", "service_retail"); // 服务零售;到店餐饮为 dining
    String merchantId = postJson("store/create", request.toString()).requireData()
            .path("shopId").asText();
    if (merchantId.isBlank()) throw new IllegalStateException("Missing merchant id");
    business.saveMerchant(merchantId);
    return merchantId; // 商户 ID 只用于创建门店,不用于授权和核销
}

String createStore(String localStoreId, String shopName, String masterShopId)
        throws IOException, InterruptedException {
    business.requireStoreAdmin(localStoreId);
    ObjectNode request = json.createObjectNode().put("shopName", shopName)
            .put("masterShopId", masterShopId);
    String shopId = postJson("store/create", request.toString()).requireData()
            .path("shopId").asText();
    if (shopId.isBlank()) throw new IllegalStateException("Missing store id");
    business.saveStoreMapping(localStoreId, shopId, masterShopId);
    return shopId;
}

示例先用 createMerchant 建好商户并保存商户 ID,再为每家业务门店调用 createStore。创建出来的门店还要完成平台授权。建议在映射记录旁保留平台门店的核对结果,方便后续发现“顾客选的是 A 店,实际关联了 B 店”这类配置问题。

三、完成商家授权,把凭据放到服务端

门店创建、获取授权链接、商家完成授权,是三个步骤。先在控制台从对应门店发起美团或抖音授权,商家完成后,再核对平台门店与授权状态。拿到一个链接,还不能把门店标成已授权。

自建入驻流程时,聚核 API 文档中的“获取美团/抖音授权链接”接口接受具体门店 shopId 和 platform;平台值为 1 美团、2 抖音,成功时 data 是授权 URL。

String authorizationUrl(String localStoreId, int platform)
        throws IOException, InterruptedException {
    business.requireStoreAdmin(localStoreId);
    String shopId = business.shopId(localStoreId);
    ObjectNode request = json.createObjectNode()
            .put("shopId", shopId).put("platform", platform);
    JsonNode data = postJson("get/auth/url", request.toString()).requireData();
    if (!data.isTextual() || data.textValue().isBlank()) {
        throw new IllegalStateException("Missing authorization URL");
    }
    return data.textValue(); // 展示给商家;授权完成后另行核对门店和状态
}

从无限聚核开发者后台的开发者中心获取当前账号 API key,保存在自己的服务端。API Host 按聚核快速开始文档填写,同样放在服务端配置里;请求头示例为 Authorization: Bearer <key>。

private final ObjectMapper json = new ObjectMapper();
private final HttpClient http = HttpClient.newHttpClient();
private String apiHost; // 聚核快速开始文档中的 API Host,从服务端配置注入
private String apiKey; // 从自己的服务端配置注入,不放进小程序

static final class ApiResponse {
    final int httpStatus;
    final JsonNode body;
    ApiResponse(int httpStatus, JsonNode body) {
        this.httpStatus = httpStatus;
        this.body = body;
    }
    boolean isSuccess() {
        return body.path("success").isBoolean() && body.path("success").booleanValue();
    }
    String errorCode() {
        return body.path("errorCode").asText(""); // 失败分支按它处理,不按中文提示匹配
    }
    JsonNode requireData() {
        if (!isSuccess()) throw new IllegalStateException("API result requires handling");
        return body.path("data");
    }
}

ApiResponse postJson(String path, String requestJson)
        throws IOException, InterruptedException {
    HttpRequest request = HttpRequest.newBuilder(URI.create(
            apiHost + "/api/hexiao/v2/" + path))
            .timeout(Duration.ofSeconds(10)) // 本例的客户端超时设置
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestJson)).build();
    HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
    JsonNode body;
    try {
        body = json.readTree(response.body());
    } catch (JsonProcessingException e) {
        body = NullNode.getInstance(); // 保留 HTTP 状态,由调用处分类
    }
    return new ApiResponse(response.statusCode(), body == null ? NullNode.getInstance() : body);
}

小程序请求到达自己的后端时,要先检查用户和业务门店权限,再查映射调用接口。聚核会校验门店是否属于当前账号,你自己的用户权限仍由业务后端负责。

四、接口按用途接,不必一次全部铺开

先做顾客确认与核销,再补商品配置、门店入驻和售后页面。下表都是聚核提供的接口,除注明外使用 POST,路径以 /api/hexiao/v2/ 为前缀;参数与响应以聚核 API 文档中的对应接口为准。

能力 接口路径后半段 本文如何使用
预核销(输码验券校验) ddzh-tuangou-receipt-prepare 必接,检查具体券并取得核销凭证
核销(验券) ddzh-tuangou-receipt-consume 必接,顾客确认使用后执行
获取团购信息 ddzh-tuangou-deal-queryshopdeal 用于展示套餐、配置本地权益映射
创建商户或门店 store/create 自建入驻后台时接,先建商户再建门店
获取授权链接 get/auth/url 把商家授权整合进自己的后台时接
撤销核销 ddzh-tuangou-receipt-cancel 有撤销售后流程时接
美团查询已验券信息 meituan/ddzh/tuangou/receipt/getconsumed 核销或撤销结果仍不确定时核对
抖音券状态查询 douyin/goodlife/v1/fulfilment/certificate/get/(GET) 核销或撤销结果仍不确定时核对

文档把预核销称为“输码验券校验”,把真正执行核销的接口称为“验券”。开发时可以在自己的封装里明确命名为 prepare 和 consume,避免在顾客尚未确认时误调后者。最后两行是聚核提供的美团、抖音原生查询接口,保持平台原始响应,请求头要带 X-Store-Id 指定门店。

五、先认识套餐,再决定发什么权益

商品列表解决的是“这家店卖什么”。例如将某个平台商品配置成“三小时服务权益”,后续识别到这款商品,自己的系统才知道该给顾客什么。

建议用 平台+聚核门店+平台商品 ID 作为映射条件。套餐名称用于展示,不能只靠名称判断商品;不同平台的同名套餐也应分别配置。

void configureProduct(String localStoreId, int platform, int page, int pageSize,
                      String selectedDealId, String benefitId)
        throws IOException, InterruptedException {
    business.requireStoreAdmin(localStoreId);
    String shopId = business.shopId(localStoreId);
    ObjectNode request = json.createObjectNode().put("shopId", shopId)
            .put("platform", platform).put("offset", page).put("limit", pageSize);
    JsonNode deals = postJson("ddzh-tuangou-deal-queryshopdeal", request.toString()).requireData();
    for (JsonNode deal : deals) {
        if (selectedDealId.equals(deal.path("dealId").asText())) {
            business.saveProductMapping(platform, shopId, deal.deepCopy(), benefitId);
            return;
        }
    }
    throw new IllegalArgumentException("Selected product is not in this page");
}

示例把管理员在当前页选中的商品与本地 benefitId 关联,并保存这条商品记录;美团券后面要用其中的套餐名称 title 和售卖价 price 做预匹配。三小时服务对应哪一个 benefitId,由自己的权益系统配置。

聚核“获取团购信息”接口的 offset 是页码,从 1 开始,limit 是每页条数,最大 100;列表中的 price、marketPrice 单位是元。

套餐配置完成后,可以按自己的业务需要同步或维护。顾客每次用券仍要预核销:列表有这款商品,不等于他输入的这张券可以使用。

六、预核销后展示确认页,保存原始凭证

顾客选好门店、平台并输入券码,后端先找出聚核门店 shopId,以同一组 shopId、platform、code 调用预核销(聚核文档称“输码验券校验”)。成功后展示套餐和本地对应权益,等顾客确认。

预核销成功时,统一的 data 里有这几项,其中几处容易接错:

  • ticketName 是套餐名称;payAmount 是售卖价,单位分;dealPrice 也是售卖价,单位元;actualPayAmount 是顾客实付,单位分,可能为空。展示时换算清楚,不能与商品列表的金额单位混用。
  • ticketInfo 是后续核销要原样回传的字符串。不要解析后重组、删字段或替换它。
  • ticketData 是抖音官方券信息 JSON,美团券为空字符串。
  • 响应里没有统一的商品 ID,也没有门店 ID;后续调用始终使用自己保存的聚核门店 shopId。
ObjectNode prepare(String localStoreId, int platform, String code, int num)
        throws IOException, InterruptedException {
    String userId = business.requireUserAccess(localStoreId);
    String shopId = business.shopId(localStoreId);
    if (num < 1 || num > 10) throw new IllegalArgumentException("Invalid quantity"); // 接口允许 1–10
    ObjectNode request = json.createObjectNode().put("shopId", shopId)
            .put("platform", platform).put("code", code);
    JsonNode data = postJson("ddzh-tuangou-receipt-prepare", request.toString()).requireData();
    // 抖音从 ticketData 读取商品;美团用 ticketName + dealPrice 与已配置商品预匹配
    String productId = adapter.productId(platform, shopId, data.deepCopy());
    if (productId == null || productId.isBlank()) {
        throw new IllegalStateException("Product needs review"); // 识别不到或命中多个
    }
    String benefitId = business.findBenefit(platform, shopId, productId);
    if (benefitId == null) throw new IllegalStateException("Configure product mapping first");
    String ticketInfo = data.path("ticketInfo").textValue();
    if (ticketInfo == null || ticketInfo.isEmpty() || !data.path("payAmount").isNumber()) {
        throw new IllegalStateException("Incomplete prepare response");
    }
    ObjectNode consumeRequest = request.deepCopy().put("num", num).put("ticketInfo", ticketInfo);
    String contextId = business.savePending(userId, consumeRequest.toString(),
            productId, benefitId, data.deepCopy());
    return json.createObjectNode().put("contextId", contextId)
            .put("ticketName", data.path("ticketName").asText()).put("benefitId", benefitId)
            .put("payAmountYuan", data.path("payAmount").decimalValue().movePointLeft(2));
}

adapter.productId 按平台识别商品:抖音券从 ticketData 里读取商品;美团券用套餐名称 ticketName 和售卖价 dealPrice,与这家门店已配置的商品标题和售卖价对照,命中唯一一个才继续。识别不到、命中多个或没配置映射,就停在确认页之前,提示管理员补配置,别按套餐名称猜测后发权益。ticketInfo 只作为不透明凭证原样保存、回传,不拆解它来做商品匹配。核销成功后,响应 data[] 里的 dealId 才是最终商品依据。数量 num 来自本次业务确认,一次最多 10。

返回对象中的 contextId、benefitId、payAmountYuan 是自己的小程序接口字段;聚核请求体只有代码中列出的真实参数。savePending 要原样保存 consumeRequest.toString(),核销时直接读取该字符串;同时保存预核销的 data,后面判断是否为抖音次卡时要用。确认页只给小程序必要的展示数据与自己的上下文 ID。券码、门店、数量和原始凭证留在服务端,后续从同一个上下文读取。预核销成功时,顾客还没有完成实际核销。

七、确认核销;按 errorCode 决定重试还是核对

顾客点击确认后,小程序提交上下文 ID。后端核对上下文归属,取出已保存的 shopId、platform、code、num、ticketInfo,调用聚核核销接口(文档称“验券”)。进入成功处理的依据是顶层 success 为布尔值 true,不能只看 HTTP 状态、code 或某个平台内部字段。

失败时按响应里的 errorCode 处理。聚核 API 文档的重试规则是:只有 CONSUME_IN_PROGRESS、RESULT_UNKNOWN、SERVICE_UNAVAILABLE、PROVIDER_UNAVAILABLE 可以自动重试,而且必须使用原请求原参数。 请求超时、连接中断这类没拿到响应的情况,也只用原请求重试。五个参数全部保持不变,包括 ticketInfo;不要为了重试再预核销,也不要换券码、门店或数量。

static final Set<String> RETRYABLE = Set.of(
        "CONSUME_IN_PROGRESS", "RESULT_UNKNOWN", "SERVICE_UNAVAILABLE", "PROVIDER_UNAVAILABLE");

void consume(String contextId) throws InterruptedException {
    PreparedRedemption context = business.loadOwnedPending(contextId);
    if (business.hasSuccess(context.id())) return;
    final String requestJson = context.requestJson(); // 循环前固定,逐字复用
    // 抖音次卡结果未知时不自动重试,直接转入查券状态或对账
    final int maxAttempts = context.douyinTimeCard() ? 1 : 3;
    JsonNode last = NullNode.getInstance();
    for (int attempt = 0; attempt < maxAttempts; attempt++) {
        if (attempt > 0) Thread.sleep(1000L * attempt); // 本例的有限串行退避
        ApiResponse reply;
        try {
            reply = postJson("ddzh-tuangou-receipt-consume", requestJson);
        } catch (IOException e) { // 包含 HttpTimeoutException;不捕获本地履约异常
            continue;
        }
        last = reply.body.deepCopy();
        if (reply.isSuccess()) {
            saveFirstSuccess(context, reply.body); // 在网络重试的 catch 之外
            return;
        }
        String errorCode = reply.errorCode();
        if ("ALREADY_CONSUMED".equals(errorCode)) {
            business.markAlreadyConsumed(context.id(), last); // 按 wxdgOrderNo 核对原单,不补发权益
            return;
        }
        if (!RETRYABLE.contains(errorCode)) {
            business.stopAttempt(context.id(), last); // PLATFORM_REJECTED、参数、授权、次数不足等
            return;
        }
    }
    business.markPending(context.id(), last); // 仍未确定:查券状态或对账
}

上述重试次数、间隔与客户端超时是本例的配置值。InterruptedException 向上传播,调用方可终止等待;首次成功的保存和权益处理不在网络异常的 catch 内,本地业务异常不会触发这一循环再次核销。douyinTimeCard() 由自己根据预核销保存的 ticketData 判断:certificates[].time_card 有值就是抖音次卡。聚核文档写明,抖音次卡结果未知时禁止自动重试,应先查询平台券状态或完成对账。

分支用 errorCode,不用 code:结果未知、参数错误、门店未授权、次数不足等都使用 code=10001,不能写成“10001 就重试”;平台明确拒绝 PLATFORM_REJECTED 的 code 是 10000,或 10000 加平台业务码(如美团 1006 返回 11006)。所以 code=10000 也不代表成功,成功只看 success。平台拒绝、参数和授权类错误要停止自动重试,把原因展示给顾客或店员。

还有一个边界:接口幂等不等于每次重复请求都返回同一份成功数据。 已成功的请求再次提交时,接口返回 success=false、code=10500、errorCode=ALREADY_CONSUMED,wxdgOrderNo 是原核销单号,data 为空,consumeCredential 为空数组。如果首次响应丢失,后来只收到这个已核销提示,就停止自动重试,用 wxdgOrderNo 定位原单,并用美团“查询已验券信息”或抖音“券状态查询”核对;不能凭这个提示直接补发权益,也不能据此编造撤销凭证。

对顾客而言,超时意味着结果还没确定。页面可显示“正在确认核销结果”,不要立刻提示失败、引导他重新输码:券如果已经核销,重新输码会在预核销阶段被平台拒绝,反而更难解释。

八、先保存成功结果,再完成自己的业务

首次收到明确成功时,保存原 shopId、platform、聚核核销单号 wxdgOrderNo、核销明细 data[] 和顶层 consumeCredential 数组。data[] 每项含商品 dealId、套餐名称 dealTitle、券码 receiptCode 和平台订单号 orderId;wxdgOrderNo 是聚核的账单订单号,orderId 是平台订单 ID,两者不能混用。凭证与 data 明细按下标对应,后续撤销需要使用相应项的原凭证。

void saveFirstSuccess(PreparedRedemption context, JsonNode response) {
    // 保存完整首次响应:wxdgOrderNo、data、consumeCredential,以及上下文中的原请求
    Receipt receipt = business.saveFirstSuccess(context, response.deepCopy());
    if (!business.dealIdsMatch(receipt.id(), context.productId())) {
        business.markNeedsReview(receipt.id()); // data[].dealId 与预核销识别的商品不一致
        return;
    }
    try {
        business.grantOnce(receipt.id(), context.benefitId());
        business.markFulfilled(receipt.id());
    } catch (RuntimeException e) {
        business.enqueueFulfillment(receipt.id()); // 只补处理自己的权益
    }
}

saveFirstSuccess 要保存首次完整响应,并把 data[i] 与 consumeCredential[i] 对应起来,同时保留上下文中的原请求;后续调用不能覆盖掉这份首次记录。美团券的商品是预匹配出来的,核销返回的 dealId 与之不一致时转人工,不发权益。聚核保证的是接口幂等,同一请求不会重复核销;它不会代替你的预约、时长或会员系统完成发放,也不能替代你按券码和自己的业务订单做的去重。

如果核销已明确成功,写入权益时发生本地异常,就根据保存的成功记录补处理本地业务。不要重新核销已成功的券;页面可以分别表达“核销成功”和“权益正在处理”,让顾客知道当前进度。

九、需要撤销时,使用首次成功的原凭证

顾客选错套餐或取消使用时,先按自己的规则检查权益能否撤回。已使用的服务如何处理,由业务规则决定。撤销核销与消费者付款退款要分开处理,调用撤销接口不等于完成退款,钱怎么退,走平台售后流程。平台对撤销还有时限,本系列《团购券核销错了能撤销吗?美团10分钟、抖音1小时的撤销规则与开发要点》有详细说明。

聚核的统一撤销接口支持美团、抖音,传原核销的 shopId、platform 和 consumeCredential 字符串数组,一次 1 到 10 项。它适用于有有效凭证且符合平台规则的记录,不能承诺所有券、所有历史核销都能撤销。撤销不收费,但没有幂等:相同凭证重复请求会再次投递到平台。所以网络异常或结果未知时,先用美团“查询已验券信息”或抖音“券状态查询”核对券状态,再决定是否重新发起撤销。

void cancel(String receiptId) throws InterruptedException {
    Receipt receipt = business.loadCancellableOwned(receiptId); // 校验归属及权益可撤回
    List<String> credentials = business.selectedOriginalCredentials(receipt);
    if (credentials.isEmpty() || credentials.size() > 10) {
        throw new IllegalStateException("Select 1 to 10 original credentials");
    }
    ObjectNode request = json.createObjectNode().put("shopId", receipt.shopId())
            .put("platform", receipt.platform());
    ArrayNode values = request.putArray("consumeCredential");
    credentials.forEach(values::add);
    ApiResponse reply;
    try {
        reply = postJson("ddzh-tuangou-receipt-cancel", request.toString());
    } catch (IOException e) { // 撤销没有幂等:先查券状态,不自动重发
        for (String credential : credentials) business.markCancellationPending(receiptId, credential);
        return;
    }
    String errorCode = reply.errorCode();
    if ("CREDENTIAL_INVALID".equals(errorCode) || "CANCEL_UNSUPPORTED".equals(errorCode)) {
        for (String credential : credentials) {
            business.recordCancellationRejected(receiptId, credential, errorCode); // 整个请求被拒
        }
        return;
    }
    Map<String, JsonNode> items = new HashMap<>();
    if (reply.body.path("data").isArray()) {
        for (JsonNode item : reply.body.path("data")) {
            items.put(item.path("consumeCredential").asText(), item);
        }
    }
    for (String credential : credentials) {
        JsonNode item = items.get(credential);
        String status = item == null ? "unknown" : item.path("status").asText("unknown");
        switch (status) {
            case "succeeded":
                business.saveCancellationSucceeded(receiptId, credential);
                try {
                    business.revokeOnce(receiptId, credential);
                } catch (RuntimeException e) {
                    business.enqueueRevocation(receiptId, credential);
                }
                break;
            case "rejected":
                business.recordCancellationRejected(receiptId, credential, item.path("message").asText());
                break;
            case "unknown":
            default:
                business.markCancellationPending(receiptId, credential);
        }
    }
}

selectedOriginalCredentials 只取已保存的首次成功凭证,按本次选定明细组成字符串数组;无法找到某项响应时,示例会将该项记为待核对。撤销响应要逐项看:succeeded 表示该项成功,rejected 表示拒绝,unknown 表示结果未知。只有全部成功时顶层 success 才是 true、code 为 10000,否则 code 为 10001;部分成功时,即使顶层为 false,也要处理已明确成功的项。凭证无效或记录不支持撤销时,整个请求返回 CREDENTIAL_INVALID 或 CANCEL_UNSUPPORTED。若撤销已成功但本地权益撤回失败,同样留下本地补处理记录。

十、联调按业务结果验收

接口能返回 JSON,还要确认页面、门店和权益串得上。下面是供你的团队执行的联调清单,本文未运行这些示例,也未进行真实券预核销、核销或撤销测试。

检查场景 预期处理
基础配置 核对服务端凭据、小程序后端 HTTPS 和微信请求合法域名配置
门店与授权 业务门店对应正确的聚核门店而不是商户,美团、抖音门店和授权状态分别核对
商品配置 平台、门店、商品映射正确;同名商品不会误发权益
预核销成功 商品已唯一识别,只展示确认页,原 ticketInfo 已保存,尚未发权益
核销明确成功 wxdgOrderNo、明细及凭证保存完整,dealId 已核对,自己的权益只发放一次
平台拒绝 PLATFORM_REJECTED 展示原因,停止自动重试
超时或可重试的 errorCode 复用原五个参数,有间隔、有上限;未确定前不报成功或确定失败
抖音次卡结果未知 不自动重试,查券状态或对账
已核销 ALREADY_CONSUMED 但缺首次结果 停止自动重试,按 wxdgOrderNo 和券状态核对,不直接补发权益
本地履约异常 根据已保存的成功记录补处理自己的业务
撤销与部分成功 按原凭证逐项处理;未知项先查券状态,不重复发撤销

十一、微信小程序接入团购核销常见问题

微信小程序接入美团团购核销怎么做?

本文的服务零售接入流程是:把业务门店映射到无限聚核商户下的具体门店,完成美团门店授权并核对绑定关系;业务后端使用 platform=1 调用无限聚核的预核销接口“输码验券校验”,取得原始 ticketInfo,并用套餐名称和售卖价与已配置的商品预匹配。顾客确认后,再带上同一门店、平台、券码、数量和 ticketInfo 调用“验券”接口完成核销,保存首次成功结果、核对返回的 dealId 后处理自己的权益。

微信小程序接入抖音团购核销怎么做?

先完成无限聚核门店对应的抖音门店授权,并核对抖音商品与自己业务权益的映射。小程序通过自己的业务后端调用无限聚核接口,后端使用 platform=2,按“预核销、顾客确认、核销”的顺序处理,从预核销返回的 ticketData(抖音官方券信息)识别商品,原样保存和回传 ticketInfo。美团与抖音的授权状态分别核对,不能用美团授权代替抖音授权;抖音次卡核销结果未知时不自动重试,先查券状态。

微信小程序团购核销必须接哪些接口?

本文采用的无限聚核 V2 服务零售两阶段流程中,预核销“输码验券校验”与核销“验券”是核心接口。商户、门店和商家授权可以先在无限聚核控制台完成,也可以接创建商户或门店、获取授权链接接口;需要在自己的后台配置套餐权益时接获取团购信息,需要撤回核销时再接撤销核销接口,不是把所有接口都列为必接。

微信小程序接入团购核销,为什么要先预核销?

在无限聚核 V2 服务零售聚合接口中,美团和抖音的新核销请求都要先调用“输码验券校验”,取得同一 shopId、platform、code 对应的 ticketInfo,同时拿到套餐名称、售卖价等确认页需要的信息。预核销用于检查券当前能否使用、准备确认上下文,不代表核销成功,也不发放权益;顾客确认后才核销。核销重试复用原请求,不重新预核销。

团购核销接口超时后怎么重试?

按无限聚核 API 文档的重试规则,只有 errorCode 为 CONSUME_IN_PROGRESS、RESULT_UNKNOWN、SERVICE_UNAVAILABLE、PROVIDER_UNAVAILABLE,或请求超时没拿到响应时,才由业务后端有间隔、有上限地重发首次请求的 shopId、platform、code、num、ticketInfo,不换参数、不重新预核销,同一请求不会被重复核销。PLATFORM_REJECTED 要停止重试;收到 ALREADY_CONSUMED(10500)但缺少首次成功记录时转入核对,不直接补发权益;抖音次卡结果未知时不自动重试,先查券状态或对账。

本文的接入示例调用了无限聚核的哪些接口?

都在 /api/hexiao/v2/ 下:创建商户或门店 store/create、获取美团/抖音授权链接 get/auth/url、获取团购信息 ddzh-tuangou-deal-queryshopdeal、输码验券校验 ddzh-tuangou-receipt-prepare、验券 ddzh-tuangou-receipt-consume 和撤销核销 ddzh-tuangou-receipt-cancel。它们都是无限聚核提供的 V2 服务零售聚合接口,同一套路径用 platform 区分美团(1)和抖音(2)。核销或撤销结果仍不确定时,还可以用无限聚核提供的美团“查询已验券信息”、抖音“券状态查询”原生接口核对券状态。

开始开发时,对照无限聚核 API 文档,按门店映射、授权、预核销、核销、撤销的顺序联调,再把结果接回自己的预约或权益系统。

免费接入 · 按次计费

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

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

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