入门
English签名与验签
所有请求使用 HMAC-SHA256 签名,HansaPay 的响应与通知也按同样规则验签。
认证方式
所有商户主动调用 HansaPay 系统的接口都必须携带以下请求头:
X-MerchantIDX-TimestampX-NonceX-Sign
签名算法:
HMAC-SHA256- 输出格式:十六进制小写字符串
字符编码与请求格式
- 请求方法:
POST - Content-Type:
application/json - 请求体必须参与签名
- 签名时使用原始 JSON 字节串
- 不要对 JSON 做二次美化、trim、字段重排后再验签
请求签名规则
待签名字符串
HansaPay 系统按以下格式验签:
merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawBody例如:
M123456
1719200000
8f31a2bc9d4e6f70
{"payment_method":"CARD","merchant_order_no":"M202406240001","trans_amount":{"currency":"USD","value":"99.99"},"notify_url":"https://merchant.example.com/callback/payment","trade_info":{"goods_name":"VIP","description":"Monthly subscription"},"card":{"card_number":"4111111111111111","cardholder_name":"JOHN DOE","exp_month":12,"exp_year":2028,"cvc":"123"},"billing_address":{"country":"US","first_name":"John","last_name":"Doe","email":"[email protected]","phone":"15551234567","state":"CA","city":"San Francisco","address":"Market Street","street_number":"1355","postal_code":"94103"},"device_ip":"203.0.113.10","metadata":"biz=member"}签名计算
伪代码:
sign = hex_lower( HMAC_SHA256(secret_key, sign_payload) )其中:
merchantID:HansaPay 系统分配的商户号secret_key:HansaPay 系统分配的商户 API 密钥timestamp:Unix 秒级时间戳nonce:随机字符串,建议 16 到 32 位rawBody:HTTP 原始请求体字符串
Java 示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
public class SignDemo {
public static String hmacSha256Hex(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(keySpec);
byte[] raw = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : raw) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
String merchantId = "M123456";
String timestamp = "1719200000";
String nonce = "8f31a2bc9d4e6f70";
String rawBody = "{\"order_no\":\"O202406240001\"}";
String secret = "your_merchant_secret";
String payload = merchantId + "\n" + timestamp + "\n" + nonce + "\n" + rawBody;
String sign = hmacSha256Hex(payload, secret);
System.out.println(sign);
}
}PHP 示例
<?php
$merchantId = 'M123456';
$timestamp = '1719200000';
$nonce = '8f31a2bc9d4e6f70';
$rawBody = '{"order_no":"O202406240001"}';
$secret = 'your_merchant_secret';
$payload = $merchantId . "\n" . $timestamp . "\n" . $nonce . "\n" . $rawBody;
$sign = hash_hmac('sha256', $payload, $secret);
echo $sign . PHP_EOL;Go 示例
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
merchantID := "M123456"
timestamp := "1719200000"
nonce := "8f31a2bc9d4e6f70"
rawBody := `{"order_no":"O202406240001"}`
secret := "your_merchant_secret"
payload := merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawBody
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
sign := hex.EncodeToString(mac.Sum(nil))
fmt.Println(sign)
}请求头示例
POST /api/v1/payments/query HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-MerchantID: M123456
X-Timestamp: 1719200000
X-Nonce: 8f31a2bc9d4e6f70
X-Sign: 7f7d6e3d2baf9b9bd0e4d8d9a5d2c8d9f7c4e1a2b3c4d5e6f708091011121314HansaPay 系统响应验签
商户也应对 HansaPay 系统的响应验签。响应头使用同样的键:
X-MerchantIDX-TimestampX-NonceX-Sign
验签规则:
merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawResponseBody说明:
- 响应 body 也必须按原始字符串参与验签
- 不要先反序列化再重新序列化后验签
- 如验签失败,应视为响应不可信
401与403鉴权错误不带签名
签名失败排查清单
当 HansaPay 系统返回以下错误时,优先检查签名链路:
401,Missing authentication headers或Merchant ID not found or invalid403,Invalid signature
建议按下面顺序排查:
商户号是否正确
X-MerchantID必须使用 HansaPay 系统分配的商户号- 不要传商户名称、登录账号或内部用户 ID
密钥是否正确
- 确认使用的是商户 API 密钥,不是登录密码
- 确认没有多环境混用
- 测试环境和生产环境密钥通常不同
待签名字符串是否完全一致
HansaPay 系统验签格式固定为:
merchantID + "\n" + timestamp + "\n" + nonce + "\n" + rawBody常见错误:
- 少了换行符
- 使用了
\r\n而不是\n - header 顺序拼错
- body 前后被 trim
- body 不是原始字符串,而是对象重新序列化后的字符串
JSON 字段名是否写错
商户接口使用的是 snake_case,例如:
payment_methodmerchant_order_nonotify_urltrade_info.goods_name
不要混用旧字段名:
paymentMethodmerchantOrderNogoodsName
JSON 是否被二次格式化
签名必须基于实际发出的原始 body。
例如这两段 JSON 业务上等价,但签名结果不同:
{"order_no":"O1","status":"SUCCESS"}{
"order_no": "O1",
"status": "SUCCESS"
}因此:
- 先生成最终请求 body
- 再用这段最终 body 去签名
- 不要签名之后再改空格、缩进或字段顺序
时间戳是否异常
X-Timestamp必须使用秒级 Unix 时间戳- 不要传毫秒
- 不要传格式化时间字符串
正确示例:
1719200000错误示例:
1719200000123
2026-06-24T10:00:00+08:00nonce 是否为空
X-Nonce不能为空- 建议 16 到 32 位随机字符串
HMAC 输出格式是否正确
HansaPay 系统要求:
- 算法:
HMAC-SHA256 - 输出:十六进制小写字符串
不要使用:
- Base64
- 十六进制大写
- MD5
- RSA
Content-Type 是否正确
请使用:
Content-Type: application/json验签失败时建议记录的日志
商户本地建议打印:
merchant_idtimestampnonceraw_bodysign_payloadreceived_signexpected_sign
这样可以最快定位是 header 问题、body 格式问题还是密钥问题。
金额限制错误排查
如果 HansaPay 系统返回:
2006:代收金额不满足通道限制5008:代付金额不满足通道限制
请检查:
trans_amount.value是否低于当前可用通道最小限额trans_amount.value是否高于当前可用通道最大限额- 当前支付方式下,商户已启用通道是否都不接受该金额
- 这类错误与签名无关,通常不需要排查
X-Sign
