认证与签名
平台上有三种身份,用的凭证各不相同。先把它们分开,后面就不会串。
| 场景 | 凭证 | 放在哪 |
|---|---|---|
| 开放接口(你的服务端 → 平台) | API KEY | 请求头 X-Pay-Api-Key |
| 商户后台(你的运营 → 后台页面) | 账号密码换来的 JWT | 请求头 x-token |
| 回调(平台 → 你的服务端) | 回调签名 | 请求头 X-Pay-Signature |
开放接口:API KEY 认证
/api/pay/open/* 下的接口只认一个头:
POST {API_BASE}/api/pay/open/createOrder
X-Pay-Api-Key: <你的 API KEY>
Content-Type: application/json关于这个 KEY 的几条硬规矩:
- 它同时是回调签名的密钥。 拿到它的人可以伪造"你的订单已付款"的回调。所以它只放在你的服务端, 不进浏览器、不进 App 包体、不进代码仓库。
- 认证失败不区分原因。 没带 KEY、KEY 不对、商户不存在、旧 KEY 过了轮换过渡期 —— 返回同一个
401和同一句话。 这是刻意的:区分原因等于告诉探测者"这个 KEY 存在"。 - 商户身份只来自 KEY。 请求里就算带了
merchantId之类的字段,服务端也没有入口能读到它。 - 平台不会在日志里记录 KEY 的明文,只会保留末几位用于定位。
中间件顺序
开放接口的调用会依次经过三段中间件,顺序不可调换:
① 按 IP 兜底限流 → ② API KEY 认证 → ③ 按商户限流- ① 发生在认证之前,此时还没有商户身份,只能按来源 IP 挡掉"拿脚本刷 KEY"的洪水。
- ② 通过之后,上下文里才有商户。
- ③ 按商户再挡一道。一个商家可以换一批 IP 集中刷,按 IP 的闸门对它无效, 而受害的是它自己的订单表。
下单是低频写操作,查询是商家对账的主要手段(轮询补回调),所以查询的额度比下单宽。 具体档位由平台配置,触发限流时返回 429。
跨域(CORS)
开放接口允许跨域:响应会带 Access-Control-Allow-Origin: *,允许的请求头里包含 X-Pay-Api-Key。
之所以可以放开,是因为它不用 Cookie,凭证是请求头里显式的 KEY —— 浏览器不会自动带上它, 第三方站点拿不到这个 KEY,也就构造不出一个"已认证"的请求。相应地,平台不会返回 Access-Control-Allow-Credentials。
KEY 仍然必须放在服务端
"接口允许跨域"不等于"可以把 KEY 放进浏览器"。跨域是给"你的服务端"这类非浏览器调用方的便利, 以及给嵌在你站点里的前端组件到你自己的后端取数据的通路。 把 KEY 写进前端代码,任何访客都能直接从源码里把它读走。
商户后台:登录拿 JWT
商户后台与商户侧接口要用登录态:
POST {API_BASE}/api/base/login
Content-Type: application/json
{
"username": "your-account",
"password": "your-password"
}成功后返回一个 JWT,有效期 7 天。后续商户侧请求把它放在 x-token 头里:
GET {API_BASE}/api/pay/getMyProfile
x-token: <登录返回的 JWT>常见的商户侧接口:
| 接口 | 用途 |
|---|---|
GET /api/pay/getMyProfile | 返回 merchantNo、collectName、脱敏后的 apiKey、cashierBaseUrl |
GET /api/pay/getMyChains | 平台已授权给你的链与代币 |
POST /api/pay/saveMyAddress | 新增/修改收款地址(chainId / address / label) |
POST /api/pay/setActiveAddress | 把某个地址设为启用 |
merchantNo 与 cashierBaseUrl 从 getMyProfile 取
收款台的 URL 形状是 {cashierBaseUrl}/pay/cashier/m/{merchantNo}。 cashierBaseUrl 是平台级参数(收银页是买家看到的门面,域名由平台定), 从 getMyProfile 读回来即可,不要自己硬编一个域名。
尚未开放
免登录进入商户后台尚未上线。 商户后台仍需要账号密码登录; 任何声称可以免登录直达后台的入口都不要使用。
回调:签名算法
平台回调你的服务端时,会用你的 API KEY 作为密钥做 HMAC-SHA256:
signature = HMAC-SHA256(key = apiKey, message = timestamp + body)message是字符串拼接:X-Pay-Timestamp头的值直接接上原始报文正文, 不是相加、也不是从 JSON 里取字段。- 结果是小写十六进制串,放在
X-Pay-Signature头里。
校验时的两条要求:
- 用原始字节比对。 先把报文解析成对象再重新序列化,字段顺序或空格一变,签名就对不上了。
- 用常数时间比较(Go 的
hmac.Equal、Node 的crypto.timingSafeEqual)。 逐字节短路的比较,耗时差异可以被用来逐位猜出签名。
完整的回调处理、重试与幂等见回调通知与验签。
密钥轮换
KEY 泄漏时你会需要换一把新的。轮换后有一段过渡期,在这个窗口里新旧两把 KEY 都能用, 这样你线上正在跑的旧 KEY 不会在平台一按"轮换"的瞬间全量中断,你可以在自己的节奏里改配置。
- 过渡期内使用旧 KEY 是允许的,但平台会记一条提醒。
- 过渡期结束后,旧 KEY 一律失效。
- 轮换之后记得同步更新回调验签用的密钥 —— 它是同一把 KEY。
KEY 的领取与重置
注册完成后,登录商户后台在「支付收款 → 支付设置」里点「查看」即可看到当前 KEY 的明文 (平台不会把 KEY 写进邮件;每次查看都会记一条审计)。要换 KEY 在同一页点「重置」—— 新旧 KEY 有一段过渡期,方便你按自己的节奏切换。