跳转到内容

认证与签名

查看 .md

Invoice、Transfer 和 Query 使用账户级 API Key + Ed25519 请求签名。API Key 用于定位凭证,Ed25519 用于证明请求来自持有私钥的商户系统。

Header 必填 说明
X-API-ID 是 账户级 API ID
X-API-Key 是 账户级 API Key
X-Timestamp 是 Unix 秒级时间戳,默认允许 ±60 秒偏差
X-Signature 是 Ed25519 签名的标准 Base64 编码
X-Idempotency-Key 或 Idempotency-Key 写请求二选一 两者同时发送时必须相等,否则返回 400 INVALID_REQUEST
v1
{api_id}
{merchant_account_id}
{METHOD}
{escaped_path}
{canonical_query}
{timestamp}
{idempotency_key}
{sha256(body)}

字段之间使用单个 \n,末尾不额外追加换行。METHOD 使用大写;body hash 使用原始发送字节计算,不要先解析再序列化。

  • escaped_path 必须与 HTTP 请求实际发送的 path 完全一致,不含 query。
  • canonical_query 的生成规则见下表;没有 query 时使用空字符串。
  • 空 body 也要计算 SHA-256,不要省略最后一行。
  • {idempotency_key} 使用 X-Idempotency-Key 的值;未发送该 Header 时才使用 Idempotency-Key。两者都没有的只读请求使用空字符串。Header 值两端空白会被忽略。
  • 每次重试可刷新 timestamp 和签名,但必须沿用原幂等键与完全相同的业务 payload。
项 规则
api_id X-API-ID 的值,去除两端空白
merchant_account_id 凭证所属商户账户的 UUID(小写、带连字符),由 PSP 在签发凭证时提供
METHOD 大写,如 POST
escaped_path 请求 URL 中已转义的 path,按实际发送的值,不要重新编码
canonical_query 先对 query 做 URL 解码,同名参数的值升序排列,再按参数名升序拼成 key=value&key=value(按 UTF-8 字节序比较);键和值重新做标准 query 转义:保留 A-Z a-z 0-9 - _ . ~,空格写 +,其余字节写大写 %XX(例如 * 写 %2A,/ 写 %2F)。无值参数 flag 视为 flag=,空片段(&&)忽略;含非法 % 转义(如 %ZZ、结尾的 %、不完整的 %2)或未转义分号 ; 的 query 会被拒绝并返回 400 INVALID_REQUEST,需要分号时请写成 %3B
timestamp X-Timestamp 的十进制字符串,与 Header 完全一致
sha256(body) 对实际发送的原始 body 字节计算 SHA-256,输出 64 位小写十六进制;空 body 为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
签名 对整个 payload 的 UTF-8 字节做 Ed25519 签名,结果用标准 Base64(带 = 填充);服务端也接受无填充的 Base64
公钥 上传给 PSP 的是 Ed25519 公钥原始 32 字节的标准 Base64

Invoice、Transfer 和 Query 使用同一种 payload 格式;服务之间的隔离靠 Host 与凭证类型(见服务与 Host),payload 中没有 service 字段。body 以实际发送的字节为准(JSON 通常是 UTF-8),不要在签名后再改动。

用下面的固定输入验证你的实现。向量由 PSP 的参考签名工具生成,并用独立实现交叉验证。

  • 私钥种子(32 字节,Base64):AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=
  • 对应公钥(Base64):A6EHv/POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg=
  • api_id:api_demo;merchant_account_id:00000000-0000-7000-8000-000000000001;timestamp:1767225600

向量 1:POST,带 body 与幂等键

body(共一行,无尾部换行):

{"payment_method":"pix","rail_variant":"pix_qr","amount":10000,"currency":"BRL","merchant_order_id":"ORDER_20260827_001"}

幂等键 invoice:create:ORDER_20260827_001,待签名 payload:

v1
api_demo
00000000-0000-7000-8000-000000000001
POST
/api/v1/payments/intents
1767225600
invoice:create:ORDER_20260827_001
b78e856a2e4eb24adf9807614503946dd9fc51d2403259858c7666d46c6ffeb7

期望签名:

pHBYN6FdiNc1as4N/doyvJO4n9jXfm4cIIcXcnS2N/mOWxxwwNJsoW2AmrjqtnZeOwgiHpfRXKfaZNy5ipXWAA==

向量 2:GET,带 query,无 body、无幂等键

实际发送的 URL 为 /api/v1/payments/intents?status=pending&limit=20&status=paid,规范化后的 canonical_query 是 limit=20&status=paid&status=pending。待签名 payload(第 8 行为空串):

v1
api_demo
00000000-0000-7000-8000-000000000001
GET
/api/v1/payments/intents
limit=20&status=paid&status=pending
1767225600
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

期望签名:

cih5j8cxplQmgoxQjvWiitnV7dJKguV23HLCxbqENcP5W+WMc9Gl48unNTmXUQGFyBCrpXgG29lrDhlhbyYmCA==
  • 私钥放入 KMS、HSM 或受控 Secret Manager,不要写入源码、镜像或日志。
  • API Key、签名、完整请求体和敏感付款信息不得作为常规日志字段。
  • 分环境、分商户账户、分 credential kind 管理密钥。
  • 轮换时保留短暂双公钥窗口,并先验证新密钥再撤销旧密钥。