SignatureValidator.buildDataToSign() 完全一致。📖 相关文档:签名失败时请参考 错误码文档 中的"签名相关错误码 (2000-2099)"章节。
X-Client-Id:客户/应用 ID(推荐)X-Biz-Merchant-Id:业务方商户 ID(推荐)X-Timestamp:毫秒级时间戳(13 位),与服务端误差不超过 5 分钟X-Nonce:随机字符串,长度 ≤ 128,只能使用一次(服务端缓存 5 分钟防重)X-Signature:签名结果(小写 16 进制字符串)X-App-Id:旧版客户/应用 ID(等同 X-Client-Id)X-Merchant-Id:旧版业务方商户 ID(等同 X-Biz-Merchant-Id)服务端读取优先级: X-Client-Id>X-App-Id;X-Biz-Merchant-Id>X-Merchant-Id。
\n 表示换行符,必须是 LF):request.getRequestURI())X-TimestampX-NonceX-Client-Id 或 X-App-Id)X-Biz-Merchant-Id 或 X-Merchant-Id)k1=v1&k2=v2)METHOD\n
/request/path\n
TIMESTAMP\n
NONCE\n
CLIENT_ID\n
BIZ_MERCHANT_ID\n
BODY_MD5_OR_EMPTY\n
QUERY_STRING(可选,无则不追加)null / "" / 全空白(只含空格、换行等),服务端视为 空请求体,第 7 行为空行k1=v1&k2=v2(中间用 & 连接)apiSecretdataToSignsignature = HMAC_SHA256(apiSecret, dataToSign)X-Signaturenonce+timestamp 只能使用一次GET /api/v1/partner-transfer/batch/status?outBatchNo=BATCH123 为例:GET/api/v1/partner-transfer/batch/statusoutBatchNo=BATCH1231730987654321a1b2c3d4e5f6g7h8wx1234567890M1234567890GET
/api/v1/partner-transfer/batch/status
1730987654321
a1b2c3d4e5f6g7h8
wx1234567890
M1234567890
outBatchNo=BATCH123POST /api/v1/partner-transfer/batch/create 为例:POST/api/v1/partner-transfer/batch/create{"subMchid":"1900000109","outBatchNo":"BATCH20250101","batchName":"测试","batchRemark":"备注","totalAmount":100,"totalNum":1,"transferDetailList":[{"outDetailNo":"D001","transferAmount":100,"transferRemark":"转账","openid":"oXXXX"}]}1730987654321a1b2c3d4e5f6g7h8wx1234567890M1234567890MD5(原始 body 字符串) = "e4d909c290d0fb1ca068ffaddf22cbd0" // 示例值POST
/api/v1/partner-transfer/batch/create
1730987654321
a1b2c3d4e5f6g7h8
wx1234567890
M1234567890
e4d909c290d0fb1ca068ffaddf22cbd0⚠️ 重要:POST 请求必须设置 Content-Type: application/json,且 Body MD5 计算的是发送的原始字符串(UTF-8 编码),不要格式化或压缩 JSON。
说明:示例使用 commons-codec。注意 空 query 不追加、空 body 仍有结尾换行。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>| 场景 | 排查建议 |
|---|---|
| 签名不匹配 | 优先核对:Path 是否只包含 URI、换行规则、query 是否按 key 排序且忽略空值、body 是否完全一致(含空白) |
| 时间戳错误 | 确认 13 位毫秒时间戳;同步机器时间 |
| Nonce 重复 | 每次请求生成新的随机串(≤ 128) |
| query 参与不一致 | 确认签名时参与了所有非空 query 参数 |
| body 参与不一致 | body 参与 MD5 的字符串必须与实际发送的完全一致(包含空格/换行) |
| IP 不允许 | 确认客户侧出口 IP 在平台配置白名单内 |
| 权限错误 | 确认 clientId + bizMerchantId 已开通对应 API 权限 |
dataToSign,与服务端日志对比。| 问题 | 正确做法 |
|---|---|
Windows 换行符 \r\n | 必须使用 LF(\n),不能用 CRLF |
| Body 为空时漏掉换行 | 第 7 行必须有换行,即使 body 为空也要 sb.append("\n") |
| Query 为空时多加换行 | 无 query 参数时不追加第 8 行,不要多加换行 |
| JSON 格式化 | Body MD5 计算必须用原始字符串,不能格式化/美化 JSON |
| 错误码 | 含义 | 排查方向 |
|---|---|---|
SIGNATURE_MISSING | 缺少签名头 | 检查 Header 是否完整 |
SIGNATURE_INVALID | 签名不匹配 | 打印 dataToSign 对比 |
TIMESTAMP_INVALID | 时间戳过期 | 同步服务器时间 |
NONCE_DUPLICATE | 随机数重复 | 每次请求生成新 nonce |
IP_NOT_ALLOWED | IP 不在白名单 | 联系管理员添加 IP |
📖 完整错误码列表请参考 错误码文档