核销服务由无限聚核提供,美团、抖音团购核销一站式接入。
搜「美团核销 Java示例」时,结果里常混着不确定的 SDK 帖、过期字段说明和「直接 msconsume」半截示例。开发真正卡的是:走自研开放平台还是接第三方统一接口?prepare → consume → 撤销怎么串?超时与未知态怎么分支?
一句话回答:自研走美团开放平台「验券准备 → 执行验券 → 撤销」;接第三方则用同一套「预核销 → 核销 → 撤销」,platform=1表示美团。出站请求用Authorization: Bearer <API Key>;成功只认顶层success=true;失败看errorCode;业务侧按券码 + 本地订单去重(聚合核销无需调方传幂等键)。下文用 Java 11+ JDKHttpClient+ Jackson 写可运行示意,不是官方完整 Java SDK 类库。业态以V2 服务零售聚合为主;到店餐饮请改用餐饮路由,勿照搬本文路径。依据无限聚核公开 API 与接入 FAQ 口径整理,查阅日期 2026-10-20,字段以当前公开文档为准。
- 先选路径。自研管商家 token;聚合用具体门店
shopId+platform=1; - 出站鉴权是 Bearer。API Key 只放服务端,不要臆造「官方美团核销 Java SDK」类名去 import;
- 幂等分两层。接口避免重复核成多笔;业务仍要自己去重并保存首次成功响应;
- prepare 不改券状态。真正扣券在 consume,
ticketInfo必须整段原文回传; - 撤销用 consumeCredential。与消费者退款是两件事;撤销无幂等。
示意图:字段与规则摘自无限聚核公开 API(2026-10-20 查阅),非平台后台截图。
一、两条路先选对
| 路径 | 你做什么 | 适用 |
|---|---|---|
| 自研:美团开放平台 | 办服务商/自研资质,管商家 token,对接准备/验券/撤销与查询;要抖音再单独接一套 | 已有多商户能力、要原生能力尽量全开 |
| 第三方统一接口 | 建门店、商家授权、业务后端调统一预核销/核销/撤销;平台差异由服务商消化 | 要同时核美团+抖音、缩短上线 |
两条路都不要「跳过准备直接核」。自研侧常见准备/执行接口族名如receipt.prepare、对应验券(到店餐饮文档里还会见到msprepare/msconsume等,属另一路由);聚合侧服务零售对应ddzh-tuangou-receipt-prepare→ddzh-tuangou-receipt-consume。
自研时请对照美团开放平台当前文档与官方示例(常见有 Go 等),不要假设存在一套完整的「官方美团核销 Java SDK 类名」可直接 import;多数团队用 HTTP + JSON 自封装。下文示意走聚合路径(platform=1)。
二、最小闭环:prepare → consume → cancel
- 预核销(输码验券校验)
POST …/ddzh-tuangou-receipt-prepare
传shopId、platform=1、code。成功后拿到套餐展示信息与不透明ticketInfo。不改变券状态,不发权益。 - 核销(验券)
POST …/ddzh-tuangou-receipt-consume
顾客确认后,原样回传同一组shopId、platform、code、num、ticketInfo。成功看success=true,保存wxdgOrderNo、data[]、consumeCredential[]。 - 撤销
POST …/ddzh-tuangou-receipt-cancel
传原shopId、platform与consumeCredential数组(1–10 项)。美团侧撤销窗口常见约 10 分钟(以平台当前说明为准)。撤销与消费者退款是两件事。
shopId必须是聚合侧具体门店ID,不是商户 ID、不是你自己系统门店主键、不是美团平台门店码。API Host 与完整 URL以当前公开 API 文档为准,下文只写路径后半段。
三、HTTP 封装与出站鉴权
示意省略部分 import;apiHost、apiKey只放服务端配置。出站统一:
Authorization: Bearer <API Key>
需要接收授权结果 Webhook 时,入站验签用当前 API Key 对timestamp + "." + rawBody做 HMAC-SHA256,结果为小写十六进制并加v1=前缀;必须用原始 Body 字节,不能先解析再序列化。验签细节以公开文档「Webhook」章节为准,本文重点仍是核销主链路。
privatefinalObjectMapperjson=newObjectMapper();privatefinalHttpClienthttp=HttpClient.newHttpClient();privateStringapiHost;// 从服务端配置注入,以当前公开文档为准privateStringapiKey;staticfinalclassApiResponse{finalinthttpStatus;finalJsonNodebody;ApiResponse(inthttpStatus,JsonNodebody){this.httpStatus=httpStatus;this.body=body;}booleanisSuccess(){returnbody.path("success").isBoolean()&&body.path("success").booleanValue();}StringerrorCode(){returnbody.path("errorCode").asText("");}}ApiResponsepostJson(Stringpath,StringrequestJson)throwsIOException,InterruptedException{HttpRequestrequest=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());JsonNodebody;try{body=json.readTree(response.body());}catch(JsonProcessingExceptione){body=NullNode.getInstance();}returnnewApiResponse(response.statusCode(),body==null?NullNode.getInstance():body);}也可用 OkHttp 等价替换;关键是不要假装有官方完整 Java SDK,并固定超时、日志脱敏、traceId落库。
四、预核销:保存 ticketInfo
请求体关键字段:
{"shopId":123,"platform":1,"code":"顾客输入的美团券码"}ObjectNodeprepare(longshopId,Stringcode,intnum)throwsException{if(num<1||num>10)thrownewIllegalArgumentException("num 1–10");ObjectNodereq=json.createObjectNode().put("shopId",shopId).put("platform",1).put("code",code);ApiResponsereply=postJson("ddzh-tuangou-receipt-prepare",req.toString());if(!reply.isSuccess())thrownewIllegalStateException("prepare failed: "+reply.errorCode());JsonNodedata=reply.body.path("data");StringticketInfo=data.path("ticketInfo").asText("");if(ticketInfo.isEmpty())thrownewIllegalStateException("missing ticketInfo");// 美团预核销常用 ticketName + dealPrice(元)与已配置商品预匹配;无统一商品 ID// ticketInfo 当不透明串整段保存,禁止解析重组ObjectNodeconsumeReq=req.deepCopy().put("num",num).put("ticketInfo",ticketInfo);// 业务侧:落 pending / 确认单,保存 consumeReq 原文与 prepare datareturnjson.createObjectNode().put("ticketName",data.path("ticketName").asText()).put("payAmountFen",data.path("payAmount").asLong()).set("consumeRequest",consumeReq);}注意:payAmount/actualPayAmount多为分,dealPrice多为元;响应里没有门店 ID,后续始终用你保存的shopId。prepare 成功不等于已核销。
五、核销:原参数 + errorCode 分支
staticfinalSet<String>RETRYABLE=Set.of("CONSUME_IN_PROGRESS","RESULT_UNKNOWN","SERVICE_UNAVAILABLE","PROVIDER_UNAVAILABLE");voidconsume(StringpendingConsumeRequestJson)throwsInterruptedException{// 发请求前业务侧写 pending / 加锁:门店+平台+券码+本地订单finalintmaxAttempts=3;JsonNodelast=NullNode.getInstance();for(inti=0;i<maxAttempts;i++){if(i>0)Thread.sleep(1000L*i);ApiResponsereply;try{reply=postJson("ddzh-tuangou-receipt-consume",pendingConsumeRequestJson);}catch(IOExceptione){continue;// 超时/断连:原样重试}last=reply.body;if(reply.isSuccess()){// 保存首次完整响应:wxdgOrderNo、data[]、consumeCredential[]return;}Stringec=reply.errorCode();if("ALREADY_CONSUMED".equals(ec)){// code 常见 10500;保存 wxdgOrderNo,停重试,转核对;勿补发、勿编造凭证return;}if(!RETRYABLE.contains(ec)){// PLATFORM_REJECTED、参数/授权等:停,展示原因return;}}// 仍未知:标记 unknown,查已验券信息或对账}核销请求体示例:
{"shopId":123,"platform":1,"code":"同一券码","num":1,"ticketInfo":"预核销返回的原值,整段回传"}幂等键怎么理解
公开 FAQ 口径:当前聚合核销无需调方传idempotencyKey(旧客户端传入也会被忽略)。接口侧保证同一业务请求不会核成多笔;不保证重复调用返回同一份首次成功明细。业务系统必须:
- 按「门店 + 平台 + 券码 + 本地订单」去重加锁;
- 保存首次完整成功响应,并按
data[i]与consumeCredential[i]下标对应; - 收到
ALREADY_CONSUMED(常见code=10500)时用wxdgOrderNo核对,而不是再点一次当补偿。
六、撤销
voidcancel(longshopId,List<String>credentials)throwsException{if(credentials.isEmpty()||credentials.size()>10){thrownewIllegalArgumentException("consumeCredential 1–10");}ObjectNodereq=json.createObjectNode().put("shopId",shopId).put("platform",1);ArrayNodearr=req.putArray("consumeCredential");credentials.forEach(arr::add);ApiResponsereply=postJson("ddzh-tuangou-receipt-cancel",req.toString());// 整笔拒绝:CREDENTIAL_INVALID / CANCEL_UNSUPPORTED 等// 到达平台后看 data[].status:succeeded / rejected / unknown// 撤销无幂等:未知时先查券状态,勿盲目重发}请求体示例:
{"shopId":123,"platform":1,"consumeCredential":["核销成功时保存的凭证"]}七、上线前验收
- 预核销成功只进确认页,未发权益;
- 核销
success=true后wxdgOrderNo、明细、凭证齐全,权益只发一次; - 超时用原请求原参数有限重试,不重新预核销、不换
ticketInfo; PLATFORM_REJECTED停;ALREADY_CONSUMED核对;- 撤销在时限内可回退本地权益;未知项先查已验券信息。
八、与自研美团路径对照
| 环节 | 自研美团开放平台 | 聚合(platform=1,服务零售) |
|---|---|---|
| 准备 | receipt.prepare 等(以当期文档为准) | ddzh-tuangou-receipt-prepare |
| 执行 | 对应验券接口 | ddzh-tuangou-receipt-consume |
| 撤销 | 以开放平台当期字段为准 | consumeCredential 数组 |
| 门店 | 商家 token+门店绑定 | 具体门店 shopId |
| 成功判断 | 以开放平台当期说明为准 | success 布尔值 |
| Java | HTTP/自封装;勿臆造 SDK 类名 | HttpClient+Jackson 示意 |
到店餐饮是另一套接口族,本文不展开;餐厅请另查餐饮路由文档,勿把上表服务零售路径直接套过去。
常见问题
美团核销的 Java 示例要自己算请求签名吗?
出站调用无限聚核 V2 聚合接口时,请求头使用Authorization: Bearer <API Key>即可,调用方不必再拼美团开放平台那套出站sign,也不要臆造官方完整 Java SDK 类名。若配置了 Webhook,入站才需要按公开文档用 API Key 做 HMAC-SHA256 验签。
prepare 和 consume 可以合并成一次调用吗?
不可以。每次新的核销前都要先调输码验券校验确认券仍可用,再把返回的ticketInfo原文交给验券接口。跳过 prepare 或篡改ticketInfo会导致核销失败或凭证非法。
接口说幂等,为什么业务还要按券码去重?
接口幂等保证同一业务请求不会核成多笔,但重复请求命中已成功订单时只返回 10500 提示和wxdgOrderNo,不会再次给出首次成功明细与撤销凭证。权益发放必须以本地保存的首次成功记录为准,并按券码与业务订单去重。聚合核销无需调方传idempotencyKey。
失败后怎样安全重试?
仅当errorCode属于CONSUME_IN_PROGRESS、RESULT_UNKNOWN、SERVICE_UNAVAILABLE、PROVIDER_UNAVAILABLE,或请求超时未拿到响应时,使用完全相同的原 JSON 有间隔、有上限地重试。PLATFORM_REJECTED与ALREADY_CONSUMED停止自动重试。
本文和「小程序接入 Java 实战」怎么分工?
小程序全流程接入(授权、商品映射、确认页、权益)见开发实战里的小程序集成文;本文专打检索意图「美团核销 Java示例」,聚焦服务零售下鉴权、幂等与 prepare → consume → cancel。两篇互补,不要互相改写成一篇。
小结
美团核销用 Java 写,关键不是找一个「官方 SDK jar」,而是把Bearer 鉴权、业务幂等、prepare → consume → cancel钉死。自研跟美团开放文档;接聚合则统一三步,ticketInfo整段回传,成功只认success,幂等靠业务去重而不是调方传幂等键。示例未对真实券编译联调,上线前请对照当前公开 API 文档自行验收。