鉴权

HMAC-SHA256 签名规范

所有 /v1/* 接口必须用 HMAC-SHA256 签名。下面是签名规范、参考实现和常见错误对照。

必须的 Header

Header必填说明
X-Api-Key凭证生成时颁发
X-Timestamp毫秒时间戳。容差 ±5 分钟
X-Service-Codepayments / activation / miniprogram
X-SignatureHMAC-SHA256 hex(见下文算法)

签名算法

pseudo
signingKey = sha256_hex(apiSecret)
payload    = timestamp + lower(serviceCode) + JSON.stringify(body || {})
signature  = hmac_sha256_hex(signingKey, payload)
  • signingKey 不是 apiSecret 本身,是它的 sha256 hex,因为 SMP 服务端只存 hash。
  • serviceCode 大小写不敏感,签名前先 toLowerCase。
  • body 为 null/undefined 时按 {} 序列化。
  • 请求方序列化后的字节序就是签名的字节序,接收方原样转发即可。不要在中间层重新排序对象 key

参考实现

Node.js / TypeScript

ts
import crypto from "crypto";

export function sign(apiSecret: string, serviceCode: string, body: unknown) {
  const key = crypto.createHash("sha256").update(apiSecret).digest("hex");
  const ts = Date.now().toString();
  const payload = ts + serviceCode.toLowerCase() + JSON.stringify(body ?? {});
  const sig = crypto.createHmac("sha256", key).update(payload).digest("hex");
  return { ts, sig };
}

Python

python
import hashlib, hmac, json, time

def sign(api_secret: str, service_code: str, body):
    key = hashlib.sha256(api_secret.encode()).hexdigest()
    ts = str(int(time.time() * 1000))
    payload = ts + service_code.lower() + json.dumps(body or {}, separators=(",", ":"))
    sig = hmac.new(key.encode(), payload.encode(), hashlib.sha256).hexdigest()
    return ts, sig

⚠️ Python 默认 json.dumps 加空格,要用 separators=(",", ":") 与 Node 的 JSON.stringify 字节对齐。

Java

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;

static String[] sign(String apiSecret, String serviceCode, String bodyJson) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] keyBytes = md.digest(apiSecret.getBytes());
    String key = bytesToHex(keyBytes);

    String ts = String.valueOf(System.currentTimeMillis());
    String payload = ts + serviceCode.toLowerCase() + (bodyJson == null ? "{}" : bodyJson);

    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
    String sig = bytesToHex(mac.doFinal(payload.getBytes()));
    return new String[]{ts, sig};
}

常见错误码

codeHTTP原因
SMP_SIGNATURE_INVALID401检查 secret / serviceCode / body 字节
SMP_REQUEST_EXPIRED401时间戳偏差 > 5 分钟
SMP_API_KEY_INACTIVE401credential 已停用 / 过期
SMP_SCOPE_INSUFFICIENT403credential scope 缺该 service
SMP_SERVICE_DISABLED403platform 没开通该 service
SMP_IP_NOT_ALLOWED403IP 白名单拒绝

出向通知(SMP → 你)

SMP 转发支付成功 / 退款 / 小程序审核结果时,会用该 platform credential 的 hash 作为签名 key(即 sha256(apiSecret)),算法与入向一致。接收端用同一 sign() 工具验证即可。详见异步通知章节