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

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

![手机打开自助小程序，经业务后端网络调用无限聚核接口的技术动漫示意](assets/technical-phone-api-v1.png)

*技术场景示意：手机中的自助小程序经业务后端调用无限聚核，凭据由服务端保存。*

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

## 一、先把完整效果串起来

顾客侧需要选店、输码、确认和结果页。后台则要知道：这是谁的券、在哪家门店使用、对应什么套餐，以及成功后发什么权益。尚未确定接口申请路径，可以先看[微信小程序怎么接入美团、抖音团购核销？申请步骤、材料与成本对比](https://www.elys.cn/articles/001-miniapp-redemption/index.html)。

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

![小程序、业务后端和核销接口的调用流程，以及原参数重试的处理分支](assets/01-call-and-redemption-flow.png)

*开发流程示意：先预核销，顾客确认后核销；超时等非业务错误使用原参数重试。*

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

`business` 是接入方的 `BusinessPort` 业务接口，负责权限、门店和商品映射、确认上下文、核销记录及权益操作；`PreparedRedemption`、`Receipt` 是自己的业务对象，只使用代码中所示访问器。`adapter` 的 `String productId(int platform, JsonNode prepareData)` 负责商品识别，`errors` 的 `FailureKind classify(ApiResponse response)` 负责按已核对的接口语义分类，二者也由接入方实现。

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

先分清三个层次：自己的业务门店、聚核主子门店、美团或抖音门店。聚核主门店用于组织一组子门店；本文把每家实际经营的业务门店对应到一个聚核子门店，再核对它关联的平台门店。

**业务调用使用保存下来的聚核子门店 `shopId`。** 别把自己系统的门店 ID、聚核主门店 ID或平台门店 ID混在同一个字段里。

![业务门店、聚核主子门店与美团抖音门店的对应关系](assets/02-store-id-mapping.png)

*门店关系示意：业务门店保存与聚核子门店的映射，平台门店分别核对。*

在[无限聚核开发者后台](https://www.elys.cn/admin/index.html#/stores)的门店管理页，可以看到主门店分组，以及子门店对应的美团、抖音名称、授权状态和授权入口。

![无限聚核真实门店管理页，展示主门店分组、子门店和双平台授权入口](assets/stores-main-child.png)

*真实后台截图，已裁切脱敏：用户名、菜单已裁除，门店 ID和平台账号已遮蔽；本次仅查看界面。*

使用当前控制台准备门店时，**先新建主门店，再在该主门店下添加子门店**。主门店表单填写名称并选择业务类目，本文使用服务零售；子门店表单会提示所属主门店，再填写具体门店名称。

![真实新建主门店表单，包含门店名称和业务类目](assets/create-main-store.png)

*真实表单截图，已裁切脱敏；仅查看，未提交创建。*

![真实新建子门店表单，显示所属主门店和名称输入框](assets/create-child-store.png)

*真实表单截图，已裁切脱敏；仅查看，未提交创建。*

如果要把门店入驻放进自己的 SaaS 后台，可以接 V2 创建门店接口。这里与控制台有一个区别：**API 不传上级时，会创建主门店及首个同名子门店**，返回的 `shopId` 是子门店 ID，`parentShopId` 是主门店 ID。已有主门店时，传 `masterShopId` 添加子门店。

```java
String createStore(String localStoreId, String shopName, String masterShopId)
        throws IOException, InterruptedException {
    business.requireStoreAdmin(localStoreId);
    boolean firstStore = masterShopId == null || masterShopId.isBlank();
    ObjectNode request = json.createObjectNode().put("shopName", shopName);
    if (!firstStore) {
        request.put("masterShopId", masterShopId);
    }
    JsonNode data = postJson("store/create", request.toString()).requireData();
    String shopId = data.path("shopId").asText();
    String parentId = firstStore ? data.path("parentShopId").asText() : masterShopId;
    if (shopId.isBlank() || parentId.isBlank()) {
        throw new IllegalStateException("Missing store identifiers");
    }
    business.saveStoreMapping(localStoreId, shopId, parentId);
    return shopId;
}
```

示例中 `masterShopId` 为空时创建主门店和首个子门店；已有主门店时传入保存的主门店 ID。创建出来的门店还要完成平台授权。建议在映射记录旁保留平台门店的核对结果，方便后续发现“顾客选的是 A 店，实际关联了 B 店”这类配置问题。

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

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

自建入驻流程时，[文档中的“获取美团/抖音授权链接”](https://doc.elys.cn/)接受 `shopId` 和 `platform`；平台值为 `1` 美团、`2` 抖音，成功时 `data` 是授权 URL。

```java
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，保存在自己的服务端。本轮[快速开始文档](https://doc.elys.cn/)给出的 API Host 是 `https://newopen.elys.cn`，请求头示例为 `Authorization: Bearer <key>`。

```java
private final ObjectMapper json = new ObjectMapper();
private final HttpClient http = HttpClient.newHttpClient();
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();
    }
    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(
            "https://newopen.elys.cn/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 文档](https://doc.elys.cn/)中的对应导航。

| 能力 | 接口路径后半段 | 本文如何使用 |
|---|---|---|
| 预核销 | `ddzh-tuangou-receipt-prepare` | 必接，检查具体券并取得核销凭证 |
| 核销 | `ddzh-tuangou-receipt-consume` | 必接，顾客确认使用后执行 |
| 团购列表 | `ddzh-tuangou-deal-queryshopdeal` | 用于展示套餐、配置本地权益映射 |
| 创建门店 | `store/create` | 自建门店入驻后台时接 |
| 授权链接 | `get/auth/url` | 把商家授权整合进自己的后台时接 |
| 撤销核销 | `ddzh-tuangou-receipt-cancel` | 有撤销售后流程时接 |

文档把预核销称为“输码验券校验”，把真正执行核销的接口称为“验券”。开发时可以在自己的封装里明确命名为 `prepare` 和 `consume`，避免在顾客尚未确认时误调后者。

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

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

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

```java
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, selectedDealId, benefitId);
            return;
        }
    }
    throw new IllegalArgumentException("Selected product is not in this page");
}
```

示例把管理员在当前页选中的 `selectedDealId` 与本地 `benefitId` 关联；三小时服务对应哪一个 `benefitId`，由自己的权益系统配置。

当前[“获取团购信息”文档](https://doc.elys.cn/)将 `offset` 定义为页码，`limit` 为每页条数；列表中的 `price`、`marketPrice` 单位是元。

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

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

顾客选好门店、平台并输入券码，后端先找出聚核子门店 `shopId`，以同一组 `shopId`、`platform`、`code` 调用预核销。成功后展示套餐和本地对应权益，等顾客确认。

[“输码验券校验”文档](https://doc.elys.cn/)的统一 `data` 包含 `payAmount`、`shopId`、`ticketName`、`ticketInfo` 和 `ticketData`。其中有三处容易接错：

- `ticketInfo` 是后续核销要原样回传的字符串。不要解析后重组、删字段或替换它。
- `data.shopId` 是套餐或门店的兼容 ID，**不能覆盖自己保存的聚核子门店 ID**。
- `payAmount` 单位是分；展示为元时要转换，不能与商品列表的金额单位混用。

```java
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 <= 0) throw new IllegalArgumentException("Invalid quantity");
    ObjectNode request = json.createObjectNode().put("shopId", shopId)
            .put("platform", platform).put("code", code);
    JsonNode data = postJson("ddzh-tuangou-receipt-prepare", request.toString()).requireData();
    String productId = adapter.productId(platform, 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(), benefitId);
    return json.createObjectNode().put("contextId", contextId)
            .put("ticketName", data.path("ticketName").asText()).put("benefitId", benefitId)
            .put("payAmountYuan", data.path("payAmount").decimalValue().movePointLeft(2));
}
```

这里以 `deepCopy()` 把完整 `data` 的副本交给商品适配器，约定只读分析，并按各平台已核对的返回语义提取可靠商品标识。`ticketData` 为空，不等于整个响应没有可识别信息。**预核销没有统一的 `data.dealId`**；也不能把兼容字段 `data.shopId` 一概当成商品 ID，更不能用它覆盖输入的聚核子门店 ID。`ticketInfo` 只作为不透明凭证原样保存、回传，不拆解它来做商品匹配。仍无法可靠识别商品时，先完善适配与配置，别按套餐名称猜测后发权益。代码中的数量来自本次业务确认，也不能假定统一响应里有可直接读取的数量字段。

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

## 七、确认核销；超时就用原参数重试

顾客点击确认后，小程序提交上下文 ID。后端核对上下文归属，取出已保存的 `shopId`、`platform`、`code`、`num`、`ticketInfo`，调用核销接口。进入成功处理的依据是**顶层 `success` 为布尔值 `true`**，不能只看 HTTP 状态或某个平台内部字段。[核销接口说明](https://doc.elys.cn/)

**核销幂等由平台保证。调用方遇到超时、连接中断等非业务错误，使用老参数重复请求。** 同一次核销的五个参数全部保持不变，包括 `ticketInfo`；不要为了重试再预核销，也不要换券码、门店或数量。

```java
enum FailureKind { REJECTED, CONFIG_ERROR, RETRYABLE, UNKNOWN, REVIEW }

void consume(String contextId) throws InterruptedException {
    PreparedRedemption context = business.loadOwnedPending(contextId);
    if (business.hasSuccess(context.id())) return;
    final String requestJson = context.requestJson(); // 循环前固定，逐字复用
    ApiResponse last = null;
    for (int attempt = 0; attempt < 3; attempt++) {
        if (attempt > 0) Thread.sleep(500L * attempt); // 本例的有限串行退避
        ApiResponse reply;
        try {
            reply = postJson("ddzh-tuangou-receipt-consume", requestJson);
        } catch (IOException e) { // 包含 HttpTimeoutException；不捕获本地履约异常
            continue;
        }
        last = reply;
        if (reply.isSuccess()) {
            saveFirstSuccess(context, reply.body); // 在网络重试的 catch 之外
            return;
        }
        if (reply.body.path("code").asInt() == 10500) {
            business.markPending(context.id(), reply.body.deepCopy());
            return; // 缺少首次成功记录时，不凭已核销提示发权益
        }
        switch (errors.classify(reply)) {
            case REJECTED:
            case CONFIG_ERROR:
                business.stopAttempt(context.id(), reply.body.deepCopy());
                return;
            case RETRYABLE:
            case UNKNOWN:
                break;
            default:
                business.markPending(context.id(), reply.body.deepCopy());
                return;
        }
    }
    business.markPending(context.id(), last == null ? NullNode.getInstance() : last.body.deepCopy());
}
```

上述重试上限、间隔与客户端超时是本例的配置值。`InterruptedException` 向上传播，调用方可终止等待；首次成功的保存和权益处理不在网络异常的 `catch` 内，本地业务异常不会触发这一循环再次核销。

`ErrorPolicy` 将明确业务拒绝、配置错误分别归为 `REJECTED`、`CONFIG_ERROR`；可重试技术失败与已识别的结果未知归为 `RETRYABLE`、`UNKNOWN`，无法识别的响应归为 `REVIEW`。这组枚举是自己的分类，不是聚核响应字段。`success=false` 不能一概当成业务拒绝。当前结果未知分支会使用 `code=10001` 并提示原请求重试，但参数、鉴权等错误也可能使用这个码，不能写成“10001 就重试”。分类函数应依据接口的具体语义实现；拿不准的结果进入待核对，不靠中文文案模糊匹配自动发权益。

还有一个边界：**幂等不等于每次重复请求都返回同一份成功数据。** 当前已成功请求的重试可能返回 `success=false`、`code=10500`，并清空 `data`、`consumeCredential`、`platformResult`。如果首次响应丢失，后来只收到已核销提示，应停止自动核销重试、核对原记录；不能凭这个提示直接补发权益，也不能据此编造撤销凭证。

对顾客而言，超时意味着结果还没确定。页面可显示“正在确认核销结果”，避免立刻提示失败并诱导他重新开始一笔核销。

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

首次收到明确成功时，保存原 `shopId`、`platform`、关联号、核销明细和顶层 `consumeCredential` 数组。凭证与 `data` 明细按下标对应，后续撤销需要使用相应项的原凭证。[成功响应与凭证说明](https://doc.elys.cn/)

```java
void saveFirstSuccess(PreparedRedemption context, JsonNode response) {
    // 保存完整首次响应：data、consumeCredential、关联号，以及上下文中的原请求
    Receipt receipt = business.saveFirstSuccess(context, response.deepCopy());
    try {
        business.grantOnce(receipt.id(), context.benefitId());
        business.markFulfilled(receipt.id());
    } catch (RuntimeException e) {
        business.enqueueFulfillment(receipt.id()); // 只补处理自己的权益
    }
}
```

`saveFirstSuccess` 要保存首次完整响应，并把 `data[i]` 与 `consumeCredential[i]` 对应起来，同时保留上下文中的原请求；后续调用不能覆盖掉这份首次记录。这里的权益控制属于自己的业务设计。平台保证核销幂等，并不会代替你的预约、时长或会员系统完成发放。

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

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

顾客选错套餐或取消使用时，先按自己的规则检查权益能否撤回。已使用的服务如何处理，由业务规则决定。**撤销核销与消费者付款退款要分开处理**，调用撤销接口不等于完成退款。

统一撤销支持美团、抖音，传原核销的 `shopId`、`platform` 和 `consumeCredential` 字符串数组。它适用于有有效凭证且符合平台规则的记录，不能承诺所有券、所有历史核销都能撤销。[“撤销核销”接口说明](https://doc.elys.cn/)

```java
void cancel(String receiptId) throws InterruptedException {
    Receipt receipt = business.loadCancellableOwned(receiptId); // 校验归属及权益可撤回
    List<String> credentials = business.selectedOriginalCredentials(receipt);
    if (credentials.isEmpty()) throw new IllegalStateException("No 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;
    }
    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`；部分成功时，即使顶层为 `false`，也要处理已明确成功的项。若撤销已成功但本地权益撤回失败，同样留下本地补处理记录。

## 十、联调按业务结果验收

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

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

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

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

本文的服务零售接入流程是：将业务门店映射到无限聚核子门店，完成美团门店授权并核对绑定关系；业务后端使用 `platform=1` 调用预核销，取得原始 `ticketInfo`。顾客确认后，再带上同一门店、平台、券码和核销数量完成核销，保存首次成功结果后处理自己的权益。具体请求见[预核销示例](#section-6)和[核销示例](#section-7)。

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

先完成无限聚核子门店对应的抖音门店授权，并核对抖音商品与自己业务权益的映射。小程序通过自己的业务后端调用接口，后端使用 `platform=2`，按“预核销、顾客确认、核销”的顺序处理，原样保存和回传 `ticketInfo`。美团与抖音的门店及授权状态分别核对，不能用美团授权代替抖音授权；步骤见[门店授权](#section-3)和[商品映射](#section-5)。

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

本文采用的无限聚核 V2 服务零售两阶段流程中，预核销与核销是核心接口。门店创建和商家授权可先在控制台完成，也可按业务需要接入相应接口；需要在自己的后台配置套餐权益时接团购商品列表，需要撤回核销时再接撤销接口。具体取舍见[接口清单](#section-4)，不是把所有接口都列为必接。

### 微信小程序接入团购核销，为什么要先预核销？

在本文使用的无限聚核 V2 服务零售聚合接口中，美团和抖音的新核销请求都要先调用“输码验券校验”，取得同一 `shopId`、`platform`、`code` 对应的 `ticketInfo`。预核销用于检查和准备确认上下文，不代表核销成功，也不发放权益；顾客确认后才核销。核销超时后的重试复用原请求，不重新预核销，见[预核销流程](#section-6)。

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

核销幂等由平台保证。超时等非业务错误、可重试技术失败或已识别的结果未知，由业务后端有间隔、有上限地重复首次请求的 `shopId`、`platform`、`code`、`num`、`ticketInfo`，不换参数。明确业务拒绝应停止自动重试；若仅收到已核销提示但缺少首次成功记录，转入核对，不直接补发权益。处理边界见[原参数重试示例](#section-7)。

开始开发时，打开[无限聚核 API 文档](https://doc.elys.cn/)，按门店映射、授权、预核销、核销、撤销的顺序联调，再把结果接回自己的预约或权益系统。
