签名算法
算法描述
参与签名的字段:
apiPath:请求 URI 的 path 部分(必须),不含 host、不含 query string,也不含/crypto-payment前缀,如/payin/v1/createPaymentbody:请求体的原始 JSON 字符串(必须),与发送给 Paydify 的 body 完全一致x-api-key:同appId(必须)x-api-timestamp:当前时间戳,单位毫秒(必须)
注意:URL 中的 query string 参数不参与签名,请确保签名内容只包含上述 4 个字段。因此请勿通过 query string 传递敏感业务参数,敏感参数应置于已签名的请求体
body中。
按照以下步骤实现:
- 将以上 4 个字段的名称和值放入
contentMap,按key字典序升序排序 - 对
contentMap进行 JSON 序列化,得到content字符串 - 使用
HMAC-SHA256算法对content字符串以apiSecret作为密钥计算签名 - 对签名结果做
Base64编码,得到最终的签名字符串
如以下示例:
curl -H 'x-api-key: key' \ -H 'x-api-timestamp: 1744636844000' \ -H 'x-api-signature: 0xb7Kxxxx=' \ 'https://{HOST}/path/to/pay' \ -d '{"data":"test"}'
排序后 JSON 序列化的 contentMap 如下(注意 body 是原始字符串而不是对象):
{ "apiPath": "/path/to/pay", "body": "{\"data\":\"test\"}", "x-api-key": "key", "x-api-timestamp": "1744636844000" }
对这个 JSON 字符串以 apiSecret 为密钥做 HMAC-SHA256,再 Base64 编码,即为签名值。
代码实现
后端实现示例
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"sort"
)
func GenSign(appId, apiSecret, pathUrl, payload string, tm int64) string {
// 解析 URL
path := ""
contentMap := make(map[string]string)
if u, err := url.Parse(pathUrl); err == nil {
path = u.Path
for k, v := range u.Query() {
if len(v) > 0 {
contentMap[k] = v[0]
}
}
}
// 构建待签名的 map
contentMap["x-api-key"] = appId
contentMap["x-api-timestamp"] = fmt.Sprintf("%d", tm) // 毫秒
contentMap["apiPath"] = path
contentMap["body"] = payload
// 获取所有 keys 并排序
keys := make([]string, 0, len(contentMap))
for k := range contentMap {
keys = append(keys, k)
}
sort.Strings(keys)
// 创建有序的 map
orderedMap := make(map[string]string)
for _, k := range keys {
orderedMap[k] = contentMap[k]
}
// JSON 序列化
content, _ := json.Marshal(orderedMap)
// 计算 HMAC
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write(content)
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// 使用示例
GenSign("A123456", "ABC123", "/path/to/pay?param1=test1¶m2=test2", "{"data":"test"}", time.Now().UnixMilli())Postman Pre-request Script
用于在 Postman 中自动生成 Payin / Payout 签名,将以下脚本粘贴到 Pre-request Script 标签页:
const appId = 'YOUR_APP_ID'; const apiSecret = 'YOUR_API_SECRET'; const ts = Date.now(); pm.request.headers.upsert({ key: 'x-api-key', value: appId }); pm.request.headers.upsert({ key: 'x-api-timestamp', value: String(ts) }); const path = pm.request.url.getPath(); const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : ''; const bodyForSign = rawBody.replace(/\r?\n/g, ''); const ordered = { apiPath: path, body: bodyForSign, 'x-api-key': appId, 'x-api-timestamp': String(ts), }; const content = JSON.stringify(ordered); console.log('Signature content:', ordered); const signature = CryptoJS.HmacSHA256(content, apiSecret).toString( CryptoJS.enc.Base64 ); pm.request.headers.upsert({ key: 'x-api-signature', value: signature }); console.log('Generated signature:', signature);
扫码付(QR Pay)与融合接口(Fusion Pay)走
X-DAPI-*请求头,签名规则不同,详见 扫码付签名算法。