本文用于说明平台商户回调从 v1明文回调升级到v2加密回调后的接入方式。本文面向商户开发人员,主要说明升级背景、兼容策略、启用准备、请求格式、验签解密方式、真实业务字段和成功响应规则。
本文仅列出平台当前实际回调给商户的业务字段。商户请以本文字段为准进行验签、解密、解析和幂等处理,未列出的字段请不要作为业务依据。
v1 回调采用 GET 明文 Query 参数方式通知商户。该方式接入简单,但交易号、订单号、金额、状态、透传字段等信息会直接出现在 URL 中,容易被浏览器、代理、网关、负载均衡、访问日志等环节记录,存在敏感业务数据泄露风险。v2 加密回调版本。v2 回调会将业务数据加密后放入 Body 的 data 字段,并通过 Header 中的 JWT 帮助商户确认回调来源、商户号、环境、事件 ID、交易号和回调类型。v1 明文回调,商户可在完成开发和联调后自行切换到 v2。如上线后需要回退,也可以切回 v1。| 目标 | 说明 |
|---|---|
| 数据保密 | 回调业务数据不再通过 URL 明文传输,改为 Body 密文传输。 |
| 来源验证 | 商户可通过 Authorization JWT 校验回调是否由平台签发。 |
| 防篡改 | JWT 和加密 payload 可帮助商户发现 Header、Body 被篡改的情况。 |
| 幂等处理 | X-Callback-Event-Id、JWT eventId、JWT jti 可用于商户侧幂等。 |
| 平滑兼容 | 默认保持 v1,商户准备完成后再切换 v2,必要时可回退。 |
| 版本 | 说明 |
|---|---|
v1 | 当前兼容版本,平台按原有 GET 明文 Query 参数方式回调。 |
v2 | 加密回调版本,平台使用 Header JWT 验签,Body 中的 data 为加密后的业务数据。 |
默认回调版本为 v1。切换到v2后,平台后续自动回调、失败重试回调、手动重发回调都会按v2发送。切回v1后立即恢复明文回调。
v2 前,请先完成以下准备:data。POST 请求。Authorization、X-Livemode、X-Callback-Version、X-Callback-Event-Id 等 Header。data 解密、业务 JSON 解析和幂等处理。success。v1 切换为 v2。也可以联系平台管理员在管理后台协助切换。| 操作 | 生效结果 |
|---|---|
v1 切换到 v2 | 后续回调改为 Post JSON 密文回调。 |
v2 切换到 v1 | 后续回调恢复 GET 明文回调。 |
建议商户先在测试环境完成 v2联调,再切换生产环境。
| 项目 | v1 明文回调 | v2 加密回调 |
|---|---|---|
| HTTP Method | GET | POST |
| 请求参数位置 | URL Query | JSON Body |
| 业务字段 | 明文参数 | 解密 data 后获得 |
| Header | t、signature | Authorization、X-Livemode、X-Callback-Version、X-Callback-Event-Id |
| 商户成功响应 | HTTP 200 | HTTP 200 且 Response Body 为 success |
| Header | 必填 | 示例 | 说明 |
|---|---|---|---|
Authorization | 是 | Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.xxx.xxx | 平台签发的 JWT,商户需要验签。 |
Content-Type | 是 | application/json; charset=UTF-8 | 固定 JSON 格式。 |
X-Livemode | 是 | false | 环境标识,true 表示生产模式,false 表示测试模式。 |
X-Callback-Version | 是 | v2 | 回调版本,固定为 v2。 |
X-Callback-Event-Id | 是 | 550e8400-e29b-41d4-a716-446655440000 | 本次回调事件 ID,UUID 格式,可用于幂等。 |
X-Merchant-No。商户号请从 JWT Payload 的 merchantId 中获取。data 一个字段:{
"data": "base64url(header).base64url(encryptedKey).base64url(iv).base64url(cipherText).base64url(tag)"
}参数示例:
{
"data": "eyJ0eXAiOiJQQVlNRU5ULVBBWUxPQUQiLCJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.RnNF-AmwcJvMQPf-_BPibPHkBrrDuuEb7fdXW3pgYUlpvCZVUse-lvt9ULZTO64dt4FNF1T9ROABkdQU_scserQ7RjRnK8lnO53qrnsVLbcUGOXTYq37DiXbiBh0gTeGIG_5admJ5pfepOO4SZ8C3gHXO3z8ZCWExBWZpJYTN7Dr6AoLK-OjPbKoEy1gRLQXvLEZ1A9Bs5s1Pd1o20QCQSk042-DZBmmh-CGy3h7h6TtA-u_zimkcby5K0R3gKt4Ld1BvizKVejDN3FWsFu6VCtS8zmGpFgEF7ahJYwLCf-sxx9xw2TAMcMul-R6mLIZkiF6xo7oS0eoVg9XAzn58w.NO0MBtyBb2Pc75Rd.E-gwR3LVP47KyuDsi87fCRvejhXbPy_vPfQrbpQwxVWIXncw6r0dRfYU4LsKQBJ0OaaWc6_fA8V-gFLcq1xNd8bsYvp9HCI_SVKGncZhFyrSKH50F6bBz4XBW2lLlFX1y019k2yIDbGLMgFVX2l2pLrq69DFcJ_n12c2MSFJc8wB5YbgKIGBydW6dm-dmNKYLviq9Xzj2WHRbkFKvjLBKwpVwMRnIm2YILcAG6BKt-bJI6AbTwokreV13u18E1-d_ACctFfGqDGHhECBYCl-ejJ-Ya5FE10zt-yVkIPxS7nEBOmMS2cI0vua-U0EvRtR1sKShriOWj4oxC3sv4XSsgTkwrVRIhrdiEgzjeuoP0FIf3ulI5F-XbhJd1pb4DKf6FJkHCs-AMj2N5EDsGCFYfo4lU99yg.bU5jl5m9x6cyEXI3frajeQ"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
data | string | 是 | 加密后的业务回调数据。商户需使用商户响应私钥解密。 |
| 项目 | 说明 |
|---|---|
| Payload Header | {"typ":"PAYMENT-PAYLOAD","alg":"RSA-OAEP-256","enc":"A256GCM"} |
| AES Key 加密算法 | RSA-OAEP-SHA256 |
| 内容加密算法 | AES-256-GCM |
| 编码方式 | Base64URL,无 padding |
data 解密后得到业务 JSON。代收和代付的业务字段见下文。Authorization 使用 Bearer <jwt> 格式。{
"typ": "JWT",
"alg": "HS256"
}{
"iss": "gateway",
"aud": "merchant",
"merchantId": "2607249795",
"livemode": false,
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"eventType": "PAYIN_CALLBACK",
"tradeNo": "pay_202607241530001234",
"jti": "550e8400-e29b-41d4-a716-446655440000",
"iat": 1784887800,
"exp": 1784887980
}| Claim | 类型 | 说明 |
|---|---|---|
iss | string | 固定为 gateway。 |
aud | string | 固定为 merchant。 |
merchantId | string | 商户号。 |
livemode | boolean | 环境标识,与 X-Livemode 一致。 |
eventId | string | 回调事件 ID,与 X-Callback-Event-Id 一致。 |
eventType | string | 回调类型,代收为 PAYIN_CALLBACK,代付为 PAYOUT_CALLBACK。 |
tradeNo | string | 平台交易号。 |
jti | string | JWT 唯一编号,当前与 eventId 一致。 |
iat | number | JWT 签发时间,Unix 秒。 |
exp | number | JWT 过期时间,Unix 秒。 |
iss 等于 gateway。aud 等于 merchant。merchantId 与当前商户一致。livemode 与 X-Livemode 一致。eventId、JWT jti、X-Callback-Event-Id 三者一致。eventType 与当前回调接口类型一致。tradeNo 与解密后业务 JSON 中的 tradeNo 一致。v1 时这些字段在 URL Query 中;v2 时这些字段在 data 解密后的 JSON 中。可选字段可能不存在或值为空,商户请做兼容处理。未列出的字段请不要作为业务依据。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merNo | String | 是 | 商户号 |
tradeNo | String | 是 | 流水号 |
orderNo | String | 是 | 站点订单号 |
currency | String | 是 | 交易币种 |
amount | BigDecimal | 是 | 交易金额 |
paymentMethod | String | 否 | 支付方式,存在时返回 |
tradeDate | Number | 否 | 交易时间。V2 解密后通常为毫秒时间戳;V1 为 URL Query 字符串 |
status | Number | 是 | 代收状态。当前异步回调发送终态:0: 订单已创建,未进入支付流程1: 支付中2: 成功3:失败4:已取消5:订单过期 |
code | String | 是 | 状态码 |
message | String | 是 | 状态消息 |
expireTime | Number | 否 | 过期时间;V2 解密后通常为毫秒时间戳;V1 为 URL Query 字符串 |
clientSecret | String | 否 | Web SDK / recurring SDK 场景可能返回 |
metadata | String | 否 | 商户创建交易时传入的透传字段 |
remark | String | 否 | 商户创建交易时传入的备注 |
email | String | 否 | 客户邮箱,存在时返回 |
name | String | 否 | 客户姓名,存在时返回 |
eventType | String | 否 | 订阅或特殊事件场景可能返回 |
subType | Number | 否 | 订阅类型,订阅场景可能返回 |
subscriptionMode | Number | 否 | 订阅周期,订阅场景可能返回 |
subToken | String | 否 | 订阅 Token,订阅场景可能返回 |
当前代收标准异步回调不建议商户依赖以下字段: paymentMethodTypes、redirectUrl、channelCode、channelId。
{
"merNo": "2607249795",
"tradeNo": "pay_202607241530001234",
"orderNo": "M202607240001",
"currency": "USD",
"amount": 10.50,
"paymentMethod": "CASHAPP",
"tradeDate": 1784887801000,
"status": 2,
"code": "succeeded",
"message": "Succeeded",
"expireTime": 1784895001000,
"metadata": "metadata"
}以下字段为平台当前代付标准回调的真实业务字段。 v1时这些字段在 URL Query 中;v2时这些字段在data解密后的 JSON 中。
可选字段可能不存在或值为空,商户请做兼容处理。未列出的字段请不要作为业务依据。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merNo | String | 是 | 商户号 |
tradeNo | String | 是 | 流水号 |
orderNo | String | 是 | 站点订单号 |
currency | String | 是 | 交易币种 |
amount | BigDecimal | 是 | 交易金额 |
paymentMethod | String | 否 | 支付方式,存在时返回 |
completionDate | Number | 否 | 完成时间。V2 解密后通常为毫秒时间戳;V1 为 URL Query 字符串 |
status | Number | 是 | 代付状态。当前异步回调发送终态:0:审核中1:处理中2:处理成功3:处理失败4:已取消 |
code | String | 是 | 状态码 |
message | String | 是 | 状态消息 |
metadata | String | 否 | 商户创建交易时传入的透传字段 |
remark | String | 否 | 商户创建交易时传入的备注 |
{
"merNo": "2607249795",
"tradeNo": "dis_202607241540001234",
"orderNo": "WD202607240001",
"currency": "USD",
"amount": 20.00,
"paymentMethod": "CASHAPP",
"completionDate": 1784888401000,
"status": 2,
"code": "succeeded",
"message": "Succeeded",
"metadata": "metadata"
}如果商户接入的是 SHOPYY 兼容接口,代收回调字段与标准代收字段不同。 v1时字段在 URL Query 中;v2时字段在data解密后的 JSON 中。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merNo | String | 否 | SHOPYY 请求中的商户标识,存在时返回 |
gatewayNo | String | 否 | SHOPYY 商户子账号,存在时返回 |
tradeNo | String | 是 | 流水号 |
orderNo | String | 否 | 站点订单号 |
orderAmount | String | 否 | 订单金额 |
orderCurrency | String | 否 | 订单币种 |
orderStatus | String | 是 | 订单状态:1:成功0:失败-1:处理中 -2:待确认 |
orderInfo | String | 是 | 状态消息 |
billAddress | String | 否 | 账单地址,存在时返回 |
returnType | String | 是 | 返回类型。当前异步回调沿用交易创建时的返回类型 |
redirectUrl | String | 否 | 跳转地址,存在时返回 |
orderErrorCode | String | 是 | 状态码 |
remark | String | 否 | 备注,存在时返回 |
signInfo | String | 是 | SHOPYY 兼容签名字段 |
{
"merNo": "2607249795",
"gatewayNo": "scott01",
"tradeNo": "pay_202607241530001234",
"orderNo": "M202607240001",
"orderAmount": "10.50",
"orderCurrency": "USD",
"orderStatus": "1",
"orderInfo": "Succeeded",
"returnType": "1",
"orderErrorCode": "succeeded",
"signInfo": "<sha256>"
}| Header | 类型 | 说明 |
|---|---|---|
t | string | 当前毫秒时间戳。 |
signature | string | SHA-256 签名。 |
t + tradeNo + orderNo + currency + amount + status + code + messaget + tradeNo + currency + amount + status + code + messagev2 回调时建议按以下流程处理:Authorization、X-Livemode、X-Callback-Version、X-Callback-Event-Id。平台不会额外发送 X-Merchant-No,商户号请从 JWT Payload 的 merchantId 中获取。Authorization 中获取 Bearer <jwt>,使用商户 API 私钥完成 JWT 验签,并校验 iss、aud、merchantId、livemode、eventType、tradeNo、jti、iat、exp 等字段。X-Livemode 与 JWT 中的 livemode 一致,X-Callback-Version 等于 v2,X-Callback-Event-Id 与 JWT 中的 eventId、jti 一致。data 字段,使用商户响应私钥解密,得到平台回调的业务 JSON 明文。tradeNo 与业务 JSON 中的 tradeNo 一致,避免 Header 与 Body 不匹配。eventId 或 tradeNo + status 做幂等处理,避免同一笔回调因重试造成重复处理。success。否则平台会按现有规则继续重试回调。v2 回调后,必须返回:success| HTTP 状态码 | Response Body | 平台处理 |
|---|---|---|
200 | success | 回调成功,平台停止重试。 |
200 | 非 success | 回调失败,平台继续按重试规则发送。 |
非 200 | 任意内容 | 回调失败,平台继续按重试规则发送。 |
| 超时 / 网络异常 | 无 | 回调失败,平台继续按重试规则发送。 |
v2 请求体中只有 data 字段,业务字段需要解密后获取。v2 不额外发送 X-Merchant-No,商户号从 JWT merchantId 获取。data、解密明文、私钥等敏感信息。success,平台会继续按现有重试规则回调。v1 直接通过 URL Query 明文传输,v2 需要先验签并解密 data 后再读取业务字段。success?v2 对成功响应做了更明确的判定。只有商户返回 HTTP 200 且 Response Body 为 success,平台才会认为商户已成功接收并处理回调,并停止重试。success。平台会继续按照现有回调重试规则发送。v1,不会强制影响原有明文回调。商户完成 v2 开发和联调后再切换即可。