Skip to content

上线自检 — v2/selfcheck(握手,必做)

在平台为你的商户号正式启用 v2 协议之前,请务必先用 selfcheck 把整条加密/签名/防重放链路跑通。本页说明它的作用、调用方式与通过后的启用流程。

前置条件:已拿到 4 个凭据(见 密钥与凭据),并已通过 getToken 换取 ANONY-TOKEN。协议细节见 加密与签名协议

作用与调用方式

作用selfcheck 是一个只读的联调探活接口,用来在平台为你正式启用之前,验证你的 SDK 是否已正确实现整套加密/签名/防重放协议。

为什么必做:正式启用是一次性切换——平台为你的商户号开启后,你后续所有业务请求都会立即按本协议校验;若此时 SDK 有任何不一致(如 OAEP 哈希、canonical 串、签名算法),请求会全部失败、业务中断。selfcheck 让你在启用之前就能用真实密钥把完整链路跑通,从而做到"先验证、后切换、零中断"。

它验证什么:服务端会对你的请求完整执行 token 校验 → 时间戳/nonce 防重放 → SHA256 验签 → OAEP 解密,再用同样的 v2 信封把内容原样 echo 回去。因此一次成功的 selfcheck 同时证明了上行(你的加密+签名能被服务端接受)与下行(服务端的响应能被你验签+解密)两个方向都正确。

POST /api/merchant/v2/selfcheck
头:ANONY-APP-ID / ANONY-TOKEN / ANONY-TIMESTAMP / ANONY-NONCE / ANONY-SIGN
体:任意业务 JSON 的密文(会被原样回显在响应的 echo 字段)
  • 该接口只读、不创建任何订单/地址、不改任何状态,可反复调用。
  • 响应是带签名的密文信封,data 解密后形如 { "pong": true, "apiVersion": 2, "serverTime": ..., "echo": <你发送的内容> }
  • 你的 SDK 能成功验签 + 解密并取回 echo,即代表上下行链路均就绪。
  • 自检通过后通知平台,由平台完成正式启用。

响应字段(解密后)

字段类型说明
pongbool固定 true,表示服务端已完整校验并处理你的请求
apiVersionint固定 2,表示本次请求按 v2 协议校验通过
serverTime服务端当前时间
echo你发送的请求内容原样回显

用 serverTime 校时

v2 协议的时间窗是 ±5 分钟,服务器时钟偏差是最常见的失败原因之一。拿到 serverTime 后与本地时钟比对:若偏差接近或超过 5 分钟,先修复 NTP 同步再继续联调,否则业务请求会以 signatureFailed 被拒。

调用示例

selfcheck 的调用方式与其它业务接口完全一致(加密 body + 全套签名头),只是把接口名换成 v2/selfcheck

php
require 'AnonyV2Client.php';
$client = new AnonyV2Client($appId, $serverPublicKeyB64, $merchantPrivateKeyB64);
$token  = $client->getToken($secret);  // 自动 md5 鉴权 + 验签解密取出 token

$res = $client->post('v2/selfcheck', ['ping' => 'hello'], $token);
// $res['data'] 已自动验签+解密
// 期望:pong=true, apiVersion=2, echo 与发送内容一致
var_dump($res['data']['pong'], $res['data']['apiVersion'], $res['data']['echo']);
python
from anony_v2 import AnonyV2Client, auth_header
import requests

c = AnonyV2Client(app_id, server_pub_b64, merchant_priv_b64)

# 先取 token:请求不加密,仅 md5 鉴权;响应仍需验签+解密
tr = requests.post(c.base_url + "getToken",
                   headers={"ANONY-APP-ID": str(app_id), "Authorization": auth_header(app_id, secret)})
tb = tr.json()
token = c.verify_response(tb["data"], tr.headers["ANONY-TIMESTAMP"],
                          tr.headers["ANONY-NONCE"], tr.headers["ANONY-SIGN"])["token"]

req = c.build_request({"ping": "hello"}, token)
r = requests.post(c.base_url + "v2/selfcheck", data=req["body"], headers=req["headers"])
body = r.json()
if body["code"] == 10000:
    data = c.verify_response(body["data"], r.headers["ANONY-TIMESTAMP"],
                             r.headers["ANONY-NONCE"], r.headers["ANONY-SIGN"])
    assert data["pong"] is True and data["apiVersion"] == 2
    print(data["serverTime"], data["echo"])
js
const { AnonyV2Client } = require('./anony_v2');
const c = new AnonyV2Client(appId, serverPubB64, merchantPrivB64);
const token = await c.getToken(secret); // Node ≥18:自动 md5 鉴权 + 验签解密取出 token

const req = c.buildRequest({ ping: 'hello' }, token);
const r = await fetch(c.baseUrl + 'v2/selfcheck', { method: 'POST', body: req.body, headers: req.headers });
const body = await r.json();
if (body.code === 10000) {
  const data = c.verifyResponse(body.data, r.headers.get('anony-timestamp'),
    r.headers.get('anony-nonce'), r.headers.get('anony-sign'));
  console.log(data.pong, data.apiVersion, data.serverTime, data.echo);
}

Go / Java 无封装的 HTTP 调用:用 SDK 的 Encrypt/Sign/Canonical/NewNonce(Go)或 encrypt/sign/canonical/newNonce/buildHeaders(Java)自行构造请求,POST 到 v2/selfcheck,响应用 Verify+Decrypt / verify+decrypt 处理。详见 SDK 使用指南

通过后如何正式启用

自检成功(能验签、能解密、echo 与发送内容一致)后,通知平台,由平台完成正式启用。

实现细节:切换由平台人工完成,商户不能自助切换

  • 你的商户号按哪套协议校验,由平台侧记录的 api_version 决定,只从平台侧读取,请求里无法指定或更改(防降级攻击)。
  • 自检通过后,由平台运维人工把你的 api_version 切换到 2——商户没有任何自助切换入口,请通过对接群/工单联系平台。
  • v2/selfcheck 本身始终按 v2 规则强制校验,与你当前的 api_version 取值无关,因此在正式切换前就可以(也应该)反复调用它验证。

推荐节奏:SDK 跑通 selfcheck → 通知平台切换 → 切换后立即再调一次业务接口(如 merchantInfo)确认正常

自检失败怎么排查

selfcheck 失败时的报错与业务接口一致(如 signatureFailed、解密失败、tokenExpired、IP 白名单 Oops)。排查顺序:

  1. 确认 OAEP 哈希用 SHA-1(头号互通坑,很多语言默认 SHA-256);
  2. 确认 canonical 串用 \n 连接且顺序为 ts, nonce, body
  3. 确认密钥角色:上行用平台公钥加密、商户私钥签名;
  4. 确认服务器时间同步(可用本页响应的 serverTime 比对)。

完整对照表见 错误码疑难排查