Allinks 商户开放 API 对接文档
版本: W8.2 增补 3 更新日期: 2026-09-13 Base URL:
https://<api-host>/open/api/v1
目录
1. 概述
Allinks Open API 为商户提供标准化的 RESTful 接口,覆盖卡片查询与状态管理、申请开卡、资金划转、交易与账单查询、通道头寸查询、KYC 案件查询等核心能力。佣金提现、结算对账、开卡代填、密钥与 Webhook 配置等控制台操作不在本通道范围(2026-09-12 产品裁决),请在商户控制台办理。
核心特性:
- 基于 API Key + HMAC-SHA256 签名认证,附防重放(nonce)、IP 白名单与权限范围(scopes)多重防护
- 全量数据 JSON 格式、UTF-8 编码;金额一律为字符串,杜绝浮点误差
- 写操作强制
Idempotency-Key幂等保护,同 Key 重放返回首次结果 - 19 个 Webhook 异步通知事件,1/5/15 分钟三级重试,投递状态可查
卡片响应仅含卡号后四位 last4,不含完整卡号与 CVV。下文示例中的主机名、密钥与编号均为占位符,请替换为控制台下发的真实值。
2. 接入准备
2.1 获取 API 凭证
在商户控制台的 设置 → API 密钥 模块完成以下步骤:
- 创建 API 密钥,按需配置权限范围(scopes)、IP 白名单与过期时间
- 复制
apiKey与一次性展示的apiSecret - 妥善保存凭证(Secret 仅在创建或轮换时展示一次)
2.2 凭证示例
API Key: ak_0123456789abcdef0123456789abcdef
API Secret: sk_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef重要提示:
apiSecret是签名密钥,请勿泄露。如怀疑泄露,请立即在控制台轮换,旧密钥即刻失效。
2.3 环境要求
- 服务器时钟与 UTC 偏差小于 300 秒
- 出口服务器 IP 已加入密钥的 IP 白名单(如配置)
- 完成后先调用无需签名的
GET /status探活,再调用需签名的GET /cards验证凭证
3. 认证
除 GET /status 外,每个请求必须带齐四个头。
| Header | 必填 | 说明 |
|---|---|---|
X-Api-Key | 是 | 控制台下发的 apiKey |
X-Timestamp | 是 | Unix 秒(不是毫秒),与服务器相差超过 300 秒会被拒绝 |
X-Nonce | 是 | 每个请求唯一;重复使用会被视为重放 |
X-Signature | 是 | 见下方算法,小写 hex |
Idempotency-Key | 卡片写操作、划转必填 | UUID;卡片冻结 / 解冻 / 销卡 / status 与划转必须携带 |
Content-Type | POST JSON 时 | application/json |
Accept | 建议 | application/json |
签名串
METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY| 段 | 规则 |
|---|---|
| METHOD | 大写,如 GET、POST |
| PATH | 不含查询串。/open/api/v1/cards?page=1 的 PATH 仍是 /open/api/v1/cards |
| TIMESTAMP / NONCE | 与请求头同一字符串 |
| BODY | 原始请求体。GET 必须是空字符串,不要传 {} |
HMAC 密钥不是 apiSecret 原文,而是:
hmacKey = SHA-256 的小写 hex(对 apiSecret 的 UTF-8 字节)
signature = HMAC-SHA256(key = hmacKey 的 UTF-8 字节, msg = 签名串) 的小写 hexhmacKey 是 64 位 hex 字符串的字节,不是 SHA-256 的原始 32 字节。
POST 时:用于签名的 BODY 必须与实际发出的字节完全一致(含空格)。同一份字符串既签名又发送。
错签不消耗 nonce,可用同一 nonce 更正后重试。限流 429、scope 不足 403 OPEN_403 同样不消耗 nonce。
权限范围 scopes
创建密钥时可传可选数组 scopes。合法值仅:read、cards、funds、issue(trim 后大小写不敏感,落库小写,去重保序)。省略、null 或空数组 = 全部权限(存量密钥行为不变)。非法值创建失败(VAL_400)。
HMAC 验签且过期/IP 通过后、限流与 nonce 之前按路径闸口:
| 请求 | 所需 scope |
|---|---|
GET / HEAD | read |
POST /cards/{id}/freeze · /unfreeze · /close · /status | cards |
POST /merchant-credits(恰好该路径) | funds |
POST /card-applications(恰好该路径) | issue |
| 其它写方法(未知 POST / PUT / PATCH / DELETE) | 仅空 scopes(全部权限)可通过;显式列表 fail-closed |
GET /status | Filter 跳过,不查 scope |
缺所需 scope 返回 HTTP 403,errorCode=OPEN_403,不消耗 nonce。issue 为申请开卡(POST /card-applications,见 §5.11)所需 scope。显式四值与空数组不等价:未来新增 scope 时,显式列表不会自动获得。
示例(占位符)
import hashlib, hmac, os, time, uuid
api_key = os.environ["ALLINKS_API_KEY"]
api_secret = os.environ["ALLINKS_API_SECRET"]
def sign(method, path, timestamp, nonce, body=""):
canonical = f"{method.upper()}\n{path}\n{timestamp}\n{nonce}\n{body}"
key = hashlib.sha256(api_secret.encode("utf-8")).hexdigest()
return hmac.new(key.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256).hexdigest()
ts = str(int(time.time()))
nonce = str(uuid.uuid4())
headers = {
"X-Api-Key": api_key,
"X-Timestamp": ts,
"X-Nonce": nonce,
"X-Signature": sign("GET", "/open/api/v1/cards", ts, nonce, ""),
"Accept": "application/json",
}curl -sS "https://<api-host>/open/api/v1/cards?page=1&size=20" \
-H "Accept: application/json" \
-H "X-Api-Key: <YOUR_API_KEY>" \
-H "X-Timestamp: <UNIX_SECONDS>" \
-H "X-Nonce: <UNIQUE_NONCE>" \
-H "X-Signature: <HEX_SIGNATURE>"仓库内另有签名小工具 examples/open_api_hmac.py(从环境变量读密钥,无内置密钥)。
4. 请求与响应
成功
HTTP 多为 200。code 为 JSON 数字 0。
{
"code": 0,
"message": "success",
"sceneCode": null,
"data": {},
"timestamp": 1710000000000
}| 字段 | 说明 |
|---|---|
code | JSON 数字。成功为 0;失败为 PRD 业务 int。请用 code === 0 判断成功 |
errorCode | 仅失败出现:原机读串(如 OPEN_401、VAL_400) |
message | 成功为 "success" |
data | 业务体 |
sceneCode | 可忽略 |
timestamp | 响应时刻,epoch 毫秒(与请求头的秒不同) |
金额一律为 string(如 "10.00"),按十进制解析,不要用浮点。卡片 balance 可能多于 2 位小数。
失败
鉴权失败使用 HTTP 401 / 403 / 429;多数业务失败仍是 HTTP 200,看信封 code。
{
"code": 4001,
"errorCode": "CARD_404",
"message": "卡片不存在",
"sceneCode": null,
"data": null,
"timestamp": 1710000000000
}| HTTP | code | errorCode | 含义 |
|---|---|---|---|
| 401 | 1002 | OPEN_401 | 缺头、密钥无效或已停用 |
| 401 | 1002 | OPEN_401_T | 时间戳非法或超出 ±300 秒 |
| 401 | 1002 | OPEN_401_R | nonce 已使用 |
| 403 | 1003 | OPEN_403 | 密钥缺少该路径所需 scope(不消耗 nonce) |
| 403 | 1003 | OPEN_403_S | 签名不一致 |
| 403 | 1003 | OPEN_403_IP | 来源 IP 不在白名单 |
| 403 | 1003 | OPEN_403_EX | 凭证已过期 |
| 429 | 42901 | 42901 | 该密钥超过窗口限额(默认 100 次 / 60 秒) |
| 404 | 2001 | NOT_FOUND | 路径不存在 |
| 200 | 1001 | VAL_400 | 参数错误(含路径 ID 非数字、非法 action、缺 Idempotency-Key) |
| 200 | 4001 | CARD_404 | 卡不存在或不属于本商户 |
| 200 | 2001 | DEP_404 | 充值单不存在 |
| 200 | 2001 | MCC_404 | 划转单不存在或不属于本商户 |
| 200 | 1009 | 1009 | 同 Idempotency-Key 但请求参数不同(幂等键冲突) |
| 200 | 1008 | 1008 | 并发冲突(写操作处理中或头寸版本冲突) |
| 200 | 2004 | CARD_409 | 卡片状态不允许该操作 |
| 200 | 1003 | AUTH_403 | 无权访问该资源(如跨商户查询持卡人/KYC) |
| 200 | 3406 / 3407 | 3406 / 3407 | 划转额度不足 / 目标持卡人不满足条件 |
| 200 | 9000 | CH_404 / KYC_404 / TRF_404 / WH_404 / PROD_404 / ACC_404 / POS_404 / CHN_502 / SYSTEM_ERROR | 资源不存在(持卡人 / KYC 案件 / 交易 / Webhook 端点 / 卡产品 / 账户 / 头寸)或通道异常、未分类错误 |
数据隔离:访问其他商户资源时原则上返回「不存在」,不暴露资源是否真实存在。例外:GET /cardholders/{id} 与 GET /kyc/{id} 跨商户访问返回 AUTH_403(code 1003)。
分页与命名
查询与 JSON 均为 camelCase。划转请求同时接受 cardholderId 与 cardholder_id(W8.0 起值为 string,兼容历史 number 入参)。
分页总表(W8.0 起列表信封统一为 data = {total, page, size, items};W8.1 起补齐 /card-statements、/finance/positions):
| 接口 | 查询参数 | 默认 | data |
|---|---|---|---|
GET /cards | page size status | page=1,size=50(1–100) | total page size items |
GET /cards/{id}/transactions | type page size | page=1,size=50 | total page size items |
GET /transactions | type page size | page=1,size=50 | total page size items |
GET /card-statements | page size | page=1,size=50 | total page size items(W8.1 起为对象,原为裸数组) |
GET /orders(/deposits) | page size status from to | page=1,size=50 | total page size items |
GET /merchant-credits | status cardholderId page size | page=1,size=50 | total page size items |
GET /cardholders | currency page size | page=1,size=50 | total page size items |
GET /kyc | status page size | page=1,size=50 | total page size items |
GET /webhooks | page size | page=1,size=50 | total page size items |
GET /webhooks/{id}/deliveries | page size | page=1,size=50 | total page size items |
GET /finance/positions | page size | page=1,size=50 | total page size items(W8.1 起增 page size 字段) |
夹紧语义:page 小于 1 按 1 处理;size 小于 1 按 1、大于 100 按 100 处理;page 超出总页数时 items 为空数组但 total 仍为全集条数。
ID 类型总表(W8.0 起所有资源 ID 在 JSON 中一律为 string):
| ID | 出现位置 | JSON 类型 | 说明 |
|---|---|---|---|
cardId | 卡片列表/详情/冻结 | string | 路径 {id} 为数字串 |
cardholderId | 卡片、持卡人、划转、KYC | string | 划转请求体契约 string(兼容历史 number 入参) |
userId | 持卡人列表行 | string | 即持卡人 ID |
merchantId | 划转详情 | string | 主体取自凭证,不在 body 传 |
creditId | 划转单 | string | 路径 {id} 为数字串 |
depositId / depositNo | 充值单 | string | /orders/{id} 两者皆可 |
endpointId / subscriberId | Webhook endpoint | string | 路径 {id} 为数字串 |
deliveryId | Webhook 投递 | string | — |
caseId / caseNo | KYC 案件 | string | 路径 {id} 为数字串 |
id(交易 ID) | 卡交易 | string | GET /transactions/{id} 路径使用该数字串 |
路径 {id} 通常为数字串(如 /cards/123);非数字返回 VAL_400(code 1001)。例外:GET /deposits/{id}(/orders/{id})另接受充值单编号 depositNo;GET /transactions/{id} 另接受业务参考号与渠道流水号。历史版本中 cardholderId(卡片/划转上下文)、endpointId、deliveryId、subscriberId 曾为 JSON number,W8.0 起统一 string。
5. 接口
以下 curl 均省略四个签名头,实际请求必须带上。
5.1 探活
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 探活 | GET /status | 服务状态与能力目录(免签名) |
接口说明:服务探活与能力目录接口说明:服务探活与能力目录。无需签名,可用于连通性检测与凭证配置前的环境确认。
请求
curl -sS https://<api-host>/open/api/v1/status请求参数:无。
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
module | string | 服务模块名 |
status | string | 服务状态,UP 为可用 |
ga | string | 当前可用版本号(对接文档版本以此为准) |
auth | string | 认证方式(HMAC-SHA256) |
capabilities | array | 当前全部可用端点清单(以实时返回为准,请勿硬编码) |
scopes | array | 权限范围合法值(read / cards / funds / issue) |
scopesSemantics | string | scopes 语义说明 |
响应示例
{
"code": 0,
"message": "success",
"data": {
"module": "allinks-merchant-api",
"status": "UP",
"ga": "W8.2",
"auth": "HMAC-SHA256",
"capabilities": [
"GET /open/api/v1/status",
"GET /open/api/v1/cards",
"GET /open/api/v1/cards/{id}",
"GET /open/api/v1/cards/{id}/balance",
"GET /open/api/v1/cards/{id}/transactions",
"POST /open/api/v1/cards/{id}/freeze",
"POST /open/api/v1/cards/{id}/unfreeze",
"POST /open/api/v1/cards/{id}/close",
"POST /open/api/v1/card-applications",
"GET /open/api/v1/card-applications/{applicationId}",
"GET /open/api/v1/cardholders",
"GET /open/api/v1/cardholders/{id}",
"GET /open/api/v1/transactions",
"GET /open/api/v1/transactions/{id}",
"GET /open/api/v1/card-statements",
"GET /open/api/v1/orders",
"GET /open/api/v1/orders/{id}",
"GET /open/api/v1/deposits",
"GET /open/api/v1/deposits/{id}",
"GET /open/api/v1/finance/positions",
"POST /open/api/v1/merchant-credits",
"GET /open/api/v1/merchant-credits",
"GET /open/api/v1/merchant-credits/{id}",
"GET /open/api/v1/kyc",
"GET /open/api/v1/kyc/{id}",
"GET /open/api/v1/webhooks",
"GET /open/api/v1/webhooks/{id}",
"GET /open/api/v1/webhooks/{id}/deliveries"
],
"scopes": ["read", "cards", "funds", "issue"],
"scopesSemantics": "空或省略 scopes 表示全部权限;显式列表按最小权限闸口"
}
}业务规则
| 规则 | 说明 |
|---|---|
| 免签名 | 本接口不校验签名与 scope,可用于连通性检测 |
| 能力目录 | capabilities 以实时返回为准(上例为当前时点快照,共 28 条);后续版本可能增删,请勿硬编码 |
5.2 卡片列表
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 列表 | GET /cards | 分页查询本商户卡片 |
| 详情 | GET /cards/{id} | 按卡片 ID 查询单张卡的详情 |
接口说明:分页查询本商户名下卡片接口说明:分页查询本商户名下卡片;详情接口按卡片 ID 返回单张卡(字段与列表项一致)。
请求
curl -sS "https://<api-host>/open/api/v1/cards?page=1&size=20&status=ACTIVE"请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
status | query | 否 | 按卡片状态筛选(值域见附录 A.1) |
page / size | query | 否 | 分页,默认 1 / 50(size 上限 100) |
{id} | path | 详情必填 | 卡片 ID(数字串) |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
total / page / size / items | — | 分页信封(见 §4) |
items[].cardId | string | 卡片 ID |
items[].status | string | 卡片状态(值域见附录 A.1) |
items[].cardType | string | 卡类型:VIRTUAL / PHYSICAL |
items[].cardholderId | string | 持卡人 ID |
items[].last4 | string | 卡号后四位 |
items[].balance | string | 卡余额 |
items[].currency | string | 币种 |
响应示例
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 20,
"items": [
{
"cardId": "1234567890123456789",
"status": "ACTIVE",
"cardType": "VIRTUAL",
"cardholderId": "10001",
"last4": "1234",
"balance": "0.00",
"currency": "USD"
}
]
}
}业务规则
| 规则 | 说明 |
|---|---|
| 租户隔离 | 仅返回本商户名下卡片;访问他商户卡片按不存在处理(CARD_404) |
| 脱敏 | 仅返回卡号后四位 last4,不含完整卡号与 CVV |
| 分页 | 夹紧语义见 §4;total 为筛选后真实总数 |
5.3 卡片操作
卡片状态操作的接口总览:
| 操作 | 方法与路径 | 说明 |
|---|---|---|
| 冻结 | POST /cards/{id}/freeze | 暂停卡片交易,可解冻恢复 |
| 解冻 | POST /cards/{id}/unfreeze | 恢复已冻结卡片 |
| 销卡 | POST /cards/{id}/close | 注销卡片,不可逆 |
| 余额 | GET /cards/{id}/balance | 查询卡余额(响应与卡片详情相同) |
| 单卡交易 | GET /cards/{id}/transactions | 查询单卡交易流水(分页) |
| 状态变更(兼容) | POST /cards/{id}/status?action= | 兼容端点,建议使用上述专用路径 |
以下三个写操作均需 scope cards、须携带 Idempotency-Key,请求体与响应结构一致。
5.3.1 冻结 / 解冻 / 销卡
接口说明:变更卡片状态。冻结后卡片不可交易,解冻立即恢复;销卡为终态操作,不可逆。
请求
curl -sS -X POST "https://<api-host>/open/api/v1/cards/1234567890/freeze" \
-H "Idempotency-Key: <uuid>" \
-H "Content-Type: application/json" \
-d '{"reason": "merchant-request"}'请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | string | 否 | 操作原因(便于对账与审计,如 merchant-request) |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
cardId | string | 卡片 ID |
previousStatus | string | 变更前状态 |
newStatus | string | 变更后状态(值域见附录 A.1) |
effectiveAt | string | 生效时间,ISO-8601 UTC |
响应示例
{
"code": 0,
"message": "success",
"data": {
"cardId": "1234567890123456789",
"previousStatus": "ACTIVE",
"newStatus": "FROZEN",
"effectiveAt": "2026-01-15T08:00:00Z"
}
}业务规则
| 规则 | 说明 |
|---|---|
| 幂等 | 缺 Idempotency-Key 返回 VAL_400;同 Key 同参重放返回首次结果,不重复执行;同 Key 异参返回 1009;失败请求不占用 Key;同一 Key 的请求处理中返回 1008 |
| 冻结 | 仅 ACTIVE 状态可冻结;其他状态(含重复冻结)返回 CARD_409 |
| 解冻 | 仅 FROZEN 状态可解冻;存在欠缴时返回 CARD_409 |
| 销卡 | 需卡余额与冻结额均为 0 方可销卡,否则返回 CARD_409;已销卡重放按成功无副作用返回;销卡为终态操作,不可逆 |
| 通道失败 | 上游通道处理失败返回 errorCode=CHN_502 |
5.3.2 余额查询
GET /cards/{id}/balance,响应 data 与卡片详情相同(字段见 §5.2)。
5.3.3 单卡交易
接口说明:查询单卡的交易流水(授权与清算合并为一条生命周期记录),分页返回。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
{id} | path | 是 | 卡片 ID(数字串) |
type | query | 否 | 筛选 AUTH(授权)/ CLEARING(清算) |
page | query | 否 | 页码,默认 1 |
size | query | 否 | 每页条数,默认 50(1–100) |
响应参数(data):分页信封 {total, page, size, items},行项字段与 GET /transactions 一致(见 §5.5)。
业务规则:total 为该卡真实交易条数(单卡直达数据源,不受商户交易量上限影响)。
5.3.4 状态变更(兼容端点)
POST /cards/{id}/status?action= 为兼容端点,不在 capabilities 目录中。仅允许 action=FREEZE / UNFREEZE,其他值返回 VAL_400;同样须携带 Idempotency-Key。新开发请使用 /freeze、/unfreeze、/close。
5.4 持卡人
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 列表 | GET /cardholders | 分页查询本商户持卡人(脱敏) |
| 详情 | GET /cardholders/{id} | 按持卡人 ID 查询详情(含持卡与余额摘要) |
接口说明:分页查询本商户名下持卡人接口说明:分页查询本商户名下持卡人(脱敏输出,不含完整证件号码与卡号);详情接口按持卡人 ID 返回单个对象。
请求
curl -sS "https://<api-host>/open/api/v1/cardholders?currency=USD&page=1&size=50"请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
currency | query | 否 | 按币种筛选 |
page / size | query | 否 | 分页,默认 1 / 50 |
{id} | path | 详情必填 | 持卡人 ID(数字串) |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
total / page / size / items | — | 分页信封(见 §4) |
items[].userId | string | 持卡人 ID |
items[].displayName | string | 姓名(脱敏) |
items[].mobileMask | string | 手机号(脱敏) |
items[].kycStatus | string | 持卡人 KYC 等级(L0 / L1 / L2 / L3;未认证为 NONE) |
items[].registeredAt | string | 注册时间 |
items[].balance | string | 可用余额 |
items[].currency | string | 币种 |
items[].status | string | 持卡人状态(如 ACTIVE) |
响应示例
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"userId": "10001",
"displayName": "张*三",
"mobileMask": "138****0000",
"kycStatus": "L2",
"registeredAt": "2026-01-15T08:00:00",
"balance": "10.00",
"currency": "USD",
"status": "ACTIVE"
}
]
}
}详情响应参数(GET /cardholders/{id},data)
| 字段 | 类型 | 说明 |
|---|---|---|
cardholderId / cardholderNo | string | 持卡人 ID / 业务编号 |
merchantId | string | 归属商户 ID |
displayName / phoneMasked / emailMasked | string | 姓名 / 手机 / 邮箱(均脱敏) |
status | string | 持卡人状态(如 ACTIVE) |
kycLevel | string | KYC 等级(L0–L3) |
kycStatus | string | 最新 KYC 案件状态(无案件时为 NONE) |
cardCount | number | 名下卡片数量 |
panMaskedFirst6 / panMaskedLast4 | string \ | null |
balance / currency | string | 可用余额 / 币种 |
jurisdiction | string | 归属司法辖区 |
statusReason / lastStatusAt | string \ | null |
cards | array | 名下卡片掩码摘要列表 |
业务规则
| 规则 | 说明 |
|---|---|
| 租户隔离 | 仅可查询本商户名下持卡人;跨商户访问返回 errorCode=AUTH_403 |
| 不存在 | 持卡人不存在返回 errorCode=CH_404 |
| 脱敏 | 姓名、手机、邮箱与卡号一律掩码输出,不含证件原文 |
5.5 卡交易与账单
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 交易列表 | GET /transactions | 分页查询卡交易流水(授权、清算等) |
| 交易详情 | GET /transactions/{id} | 按交易 ID / 流水号查询单笔交易 |
| 账单摘要 | GET /card-statements | 月度账单摘要(按账期聚合) |
接口说明:分页查询本商户名下的卡交易流水接口说明:分页查询本商户名下的卡交易流水(授权、清算等)与月度账单摘要;交易详情按交易 ID 返回单笔记录。
请求
curl -sS "https://<api-host>/open/api/v1/transactions?type=AUTH&page=1&size=20"请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | query | 否 | 筛选 AUTH(授权)/ CLEARING(清算) |
page / size | query | 否 | 分页,默认 1 / 50 |
{id} | path | 详情必填 | 交易 ID(数字串) |
响应参数(data.items[] 交易行项)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 交易 ID |
type | string | 类型:AUTH(授权)/ CLEARING(清算) |
status | string | 交易状态 |
amount | string | 金额 |
currency | string | 币种 |
createdAt | string | 交易时间 |
refNo | string | 业务参考号 |
remark | string \ | null |
creditId | string \ | null |
cardId | string | 卡片 ID |
panLast4 | string | 卡号后四位 |
merchantId | string | 商户 ID |
mcc | string \ | null |
channelId | string \ | null |
authCode | string \ | null |
declineCode | string \ | null |
channelTxId / channelClearId | string \ | null |
authAmount | string \ | null |
clearAmount | string \ | null |
clearApplied | boolean \ | null |
shortfallAmount | string \ | null |
authResult | string \ | null |
authAt / clearAt | string \ | null |
业务规则
| 规则 | 说明 |
|---|---|
| 行项口径 | 授权与清算合并为一条生命周期记录,状态随清算推进更新 |
| 脱敏 | 仅含卡号后四位 panLast4 |
| 详情查询 | GET /transactions/{id} 的 {id} 支持交易 ID 或业务参考号(refNo / 渠道流水号);不存在返回 errorCode=TRF_404 |
| 月度账单 | GET /card-statements 返回账期摘要;账单行明细请经商户控制台下载 |
月度账单摘要 GET /card-statements?page=1&size=50 —— 响应参数(data.items[])
| 字段 | 类型 | 说明 |
|---|---|---|
statementId | string | 账单 ID(即账期 period,如 2026-08) |
period | string | 账期 |
txCount | number | 当期交易笔数 |
amount | string | 当期交易金额合计 |
currency | string | 币种 |
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{ "statementId": "2026-08", "period": "2026-08", "txCount": 2, "amount": "20.00", "currency": "USD" }
]
}
}5.6 用户充值单
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 列表 | GET /deposits | 分页查询持卡人充值单(GET /orders 为同义路径) |
| 详情 | GET /deposits/{id} | 按充值单 ID 或编号(depositNo)查询 |
接口说明:分页查询持卡人充值单接口说明:分页查询持卡人充值单(用户入金记录,非卡消费流水);详情按充值单 ID 或编号返回单条记录。
请求
curl -sS "https://<api-host>/open/api/v1/deposits?status=CREDITED&page=1&size=50"请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
status | query | 否 | 按充值状态筛选 |
from / to | query | 否 | 时间范围,YYYY-MM-DD 或本地日期时间;日期型 to 按次日 0 点不含 |
page / size | query | 否 | 分页,默认 1 / 50 |
{id} | path | 详情必填 | 充值单 ID 或 depositNo |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
items[].depositId | string | 充值单 ID |
items[].depositNo | string | 充值单编号 |
items[].userMask | string | 持卡人标识(脱敏) |
items[].amount | string | 充值金额 |
items[].currency | string | 币种 |
items[].status | string | 充值状态(值域见附录 A.8) |
items[].txid | string \ | null |
items[].method | string | 充值方式(如 CRYPTO) |
items[].createdAt | string | 创建时间 |
items[].exceptionCode | string \ | null |
响应示例
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"depositId": "10001",
"depositNo": "DEP10001",
"userMask": "u1***9",
"amount": "10.00",
"currency": "USD",
"status": "CREDITED",
"txid": "onchain-or-channel-ref",
"method": "CRYPTO",
"createdAt": "2026-01-15T08:00:00",
"exceptionCode": null
}
]
}
}业务规则
| 规则 | 说明 |
|---|---|
| 路径别名 | /orders 与 /deposits 为同一业务(兼容保留),新开发请使用 /deposits |
| 存在性 | 详情不存在时返回 errorCode=DEP_404 |
| 租户隔离 | 仅返回本商户名下持卡人的充值单 |
5.7 通道头寸
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 头寸查询 | GET /finance/positions | 分页查询通道头寸的会计分科余额 |
接口说明:分页查询商户通道头寸接口说明:分页查询商户通道头寸的会计分科余额。
请求
curl -sS "https://<api-host>/open/api/v1/finance/positions?page=1&size=50"请求参数:page / size,分页,默认 1 / 50。
响应参数(data.items[])
| 字段 | 类型 | 说明 |
|---|---|---|
currency | string | 币种 |
balance | string | 头寸总额 |
held | string | 冻结中金额 |
reserved | string | 预留金额 |
safetyBuffer | string | 安全垫金额 |
availableForReturn | string | 可调回金额 |
transferHold | string | 划转冻结金额(待复核的划转占用) |
availableForCredit | string | 可划转金额(划转接口的额度依据) |
warningStatus | string | 水位预警状态:OK / WARN / CRITICAL |
updatedAt | string | 更新时间 |
响应示例
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"currency": "USD",
"balance": "10000.00",
"held": "0.00",
"reserved": "0.00",
"safetyBuffer": "0.00",
"availableForReturn": "10000.00",
"transferHold": "0.00",
"availableForCredit": "10000.00",
"warningStatus": "OK",
"updatedAt": "2026-01-15T08:00:00"
}
]
}
}业务规则
| 规则 | 说明 |
|---|---|
| 分科管理 | 通道头寸与持卡人资金、佣金分科核算,互不混同;本接口仅反映头寸科目 |
| 额度依据 | availableForCredit 为划转接口(§5.10)的可划转额度单一真源 |
5.8 KYC 案件
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 列表 | GET /kyc | 分页查询本商户持卡人的 KYC 案件(脱敏) |
| 详情 | GET /kyc/{id} | 按案件 ID 查询单个案件 |
接口说明:分页查询本商户持卡人的 KYC 案件接口说明:分页查询本商户持卡人的 KYC 案件(脱敏输出);详情按案件 ID 返回单个案件。
请求
curl -sS "https://<api-host>/open/api/v1/kyc?status=PENDING_MERCHANT&page=1&size=50"请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
status | query | 否 | 按案件状态筛选(值域见附录 A.7);空或 ALL 为全量 |
page / size | query | 否 | 分页,默认 1 / 50 |
{id} | path | 详情必填 | 案件 ID(数字串) |
响应参数(data.items[])
| 字段 | 类型 | 说明 |
|---|---|---|
caseId | string | 案件 ID |
caseNo | string | 案件编号 |
cardholderId | string | 持卡人 ID |
merchantId | string | 商户 ID |
targetLevel | string | 目标认证等级(见附录 A.4) |
status | string | 案件状态 |
fullName | string | 姓名(脱敏) |
resubmitCount | number | 补件次数 |
resubmitFields | array | 需补件字段清单 |
publicRejectReason | string \ | null |
idFrontFileId / idBackFileId | string \ | null |
submittedAt / reviewedAt | string \ | null |
响应示例
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"caseId": "5001",
"caseNo": "KYC5001",
"cardholderId": "10001",
"merchantId": "1001",
"targetLevel": "L2",
"status": "PENDING_MERCHANT",
"fullName": "张*三",
"resubmitCount": 0,
"resubmitFields": [],
"publicRejectReason": null,
"idFrontFileId": "f-001",
"idBackFileId": "f-002",
"submittedAt": "2026-01-15T08:00:00",
"reviewedAt": null
}
]
}
}业务规则
| 规则 | 说明 |
|---|---|
| 只读 | KYC 档案的创建与提交经商户门户办理(代填入口),本通道仅提供查询 |
| 脱敏 | 姓名脱敏输出(如 张*三);不返回证件影像内容与审核内部字段 |
| 存在性 | 跨商户或不存在按 errorCode=AUTH_403 / KYC_404 处理,均不返回案件内容 |
| 结果通知 | 审核结果同步推送 kyc.approved / kyc.rejected / kyc.resubmit_required(见 §6.7) |
5.9 Webhook 查询
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 端点列表 | GET /webhooks | 分页查询 Webhook 端点配置(只读,不含密钥) |
| 端点详情 | GET /webhooks/{id} | 查询单个端点配置 |
| 投递记录 | GET /webhooks/{id}/deliveries | 分页查询投递记录(含重试信息) |
接口说明:查询 Webhook 端点配置接口说明:查询 Webhook 端点配置(只读,响应不含签名密钥)与投递记录;用于自助排查回调送达情况。
请求
curl -sS "https://<api-host>/open/api/v1/webhooks?page=1&size=50"
curl -sS "https://<api-host>/open/api/v1/webhooks/971322588/deliveries?page=1&size=50"请求参数:page / size,分页,默认 1 / 50;{id} 为端点 ID(数字串)。
响应参数 —— 端点(GET /webhooks、GET /webhooks/{id})
| 字段 | 类型 | 说明 |
|---|---|---|
endpointId | string | 端点 ID |
subscriberType | string | 订阅主体类型(如 MERCHANT) |
subscriberId | string | 订阅主体 ID |
url | string | 回调地址 |
events | array | 已订阅事件名列表(见 §6.7) |
status | string | 端点状态:ACTIVE / PAUSED / DISABLED(见附录 A.6) |
consecutiveFailures | number | 连续失败次数 |
createdAt / updatedAt | string | 创建 / 更新时间 |
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"endpointId": "971322588",
"subscriberType": "MERCHANT",
"subscriberId": "1001",
"url": "https://example.com/hook",
"events": ["rate.merchant_changed"],
"status": "ACTIVE",
"consecutiveFailures": 0,
"createdAt": "2026-01-15T08:00:00",
"updatedAt": "2026-01-15T08:00:00"
}
]
}
}响应参数 —— 投递记录(GET /webhooks/{id}/deliveries)
| 字段 | 类型 | 说明 |
|---|---|---|
deliveryId | string | 投递 ID |
eventId | string | 事件 ID |
eventType | string | 事件名 |
endpointId | string | 端点 ID |
status | string | 投递状态(见附录 A.6) |
attemptCount | number | 已尝试次数 |
retryCount | number | 已重试次数 |
nextAttemptAt | string \ | null |
lastHttpStatus | number \ | null |
lastError | string \ | null |
retryDelayMinutesUsed | array | 已使用的重试间隔(分钟) |
createdAt / succeededAt | string \ | null |
{
"code": 0,
"message": "success",
"data": {
"total": 1,
"page": 1,
"size": 50,
"items": [
{
"deliveryId": "9001",
"eventId": "evt-001",
"eventType": "rate.merchant_changed",
"endpointId": "971322588",
"status": "SUCCEEDED",
"attemptCount": 1,
"retryCount": 0,
"nextAttemptAt": null,
"lastHttpStatus": 200,
"lastError": null,
"retryDelayMinutesUsed": [],
"createdAt": "2026-01-15T08:05:00",
"succeededAt": "2026-01-15T08:05:01"
}
]
}
}业务规则
| 规则 | 说明 |
|---|---|
| 只读 | 本通道仅提供查询;端点的创建、修改、删除、测试与失败重发请在商户控制台完成(见 §6.1) |
| 不含密钥 | 响应永不返回端点签名密钥 |
| 排查路径 | 投递失败原因与重试计划请结合投递记录与 §6.6 重试机制排查 |
5.10 划转
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 发起划转 | POST /merchant-credits | 商户通道头寸划入持卡人账户 |
| 划转列表 | GET /merchant-credits | 分页查询划转单 |
| 划转详情 | GET /merchant-credits/{id} | 按划转单号查询(提交后轮询用) |
接口说明:将商户通道头寸划入本商户持卡人账户接口说明:将商户通道头寸划入本商户持卡人账户(仅此方向,无逆向接口)。签名认证即二次验证,无需门户动态口令。大额划转进入人工复核(详见业务规则)。
请求
curl -sS -X POST "https://<api-host>/open/api/v1/merchant-credits" \
-H "Idempotency-Key: <uuid>" \
-H "Content-Type: application/json" \
-d '{"cardholderId":"10001","amount":"100.00","currency":"USD"}'- 须携带
Idempotency-Key;主体取自凭证,请勿在请求体传入商户标识
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cardholderId | string | 是 | 目标持卡人 ID(须为本商户名下)。亦接受 cardholder_id,值为 string |
amount | string | 是 | 划转金额,大于 0,最多 2 位小数 |
currency | string | 否 | 币种,缺省 USD;其他币种不支持 |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
creditId | string | 划转单号(幂等重试返回同一单号) |
status | string | 划转状态:PENDING_REVIEW 复核中 / CREDITED 已入账 / FAILED_ROLLED_BACK 失败已冲回 / REJECTED 已驳回 |
amount | string | 划转金额 |
currency | string | 币种 |
transferHold | string | 当前冻结头寸金额(复核中大于 0,终态为 0) |
accountVersion | number | 头寸版本号(并发冲突重试时使用) |
idempotent | boolean | 是否为幂等重试命中(true 表示返回首次结果,未执行新划转) |
availableForCredit | string | 当前可划转额度 |
业务规则
| 规则 | 说明 |
|---|---|
| 幂等 | 同 Key 同参重放返回首次结果;同 Key 异参返回 1009;缺 Key 返回 VAL_400;Idempotency-Key 最长 64 字符 |
| 额度 | 划转金额不得超过可划转额度(见 §5.7 availableForCredit),不足返回 3406 |
| 大额复核 | 单笔超过 $50,000 或「商户×持卡人」24 小时滚动累计超过 $50,000 时进入 PENDING_REVIEW 并冻结对应头寸;复核期间不可撤销,审核完成后更新为 CREDITED 或 REJECTED |
| 目标校验 | 持卡人须为本商户名下、KYC 达标且账户状态正常,否则返回 3407 |
| 到账通知 | 入账后平台向持卡人发送站内信;本操作不产生 Webhook 通知(transfer.completed 事件对应账户↔卡拨付,与划转无关) |
查询
划转列表 GET /merchant-credits —— 请求参数:status、cardholderId、page / size(默认 1 / 50);响应参数(data.items[])
| 字段 | 类型 | 说明 |
|---|---|---|
creditId | string | 划转单号 |
cardholderMask | string | 持卡人标识(脱敏) |
amount | string | 划转金额 |
currency | string | 币种 |
status | string | 划转状态 |
transferHold | string | 冻结头寸金额 |
reviewReason | string \ | null |
createdAt | string | 创建时间 |
completedAt | string \ | null |
划转详情 GET /merchant-credits/{id} —— 用于提交后轮询;他商户或不存在均为 errorCode=MCC_404;响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
creditId / status / amount / currency / transferHold | string | 同提交响应 |
accountVersion | number | 头寸版本号 |
cardholderId | string | 持卡人 ID |
reviewReason | string \ | null |
reviewedBy / reviewedAt | string \ | null |
reviewDeadline / escalationAt | string \ | null |
createdAt / updatedAt | string | 创建 / 更新时间 |
availableForCredit | string | 当前可划转额度 |
timeline | array | 划转状态时间线(状态与时间) |
错误码
| HTTP | code | errorCode | 说明 |
|---|---|---|---|
| 200 | 1001 | VAL_400 | 参数错误(缺 Idempotency-Key、金额格式非法等) |
| 200 | 1009 | 1009 | 同 Idempotency-Key 但参数不一致 |
| 200 | 1008 | 1008 | 并发冲突(头寸版本冲突,请按 accountVersion 重试) |
| 200 | 3406 | 3406 | 可划转额度不足 |
| 200 | 3407 | 3407 | 目标持卡人不满足划转条件 |
| 200 | 2001 | MCC_404 | 划转单不存在或不属于本商户 |
5.11 申请开卡
接口
| 接口 | 方法与路径 | 说明 |
|---|---|---|
| 提交申请 | POST /card-applications | 为持卡人申请开卡,返回申请单号 |
| 查询状态 | GET /card-applications/{applicationId} | 按申请单号轮询状态 |
接口说明接口说明
为商户名下持卡人申请开卡。提交后平台异步受理并返回申请单号;商户可凭申请单号轮询状态,开卡成功后平台推送 card.issued 通知(见 §6.8)。
支持范围:虚拟卡产品(产品目录中 cardType=VIRTUAL 的产品)。实体卡请在商户门户办理。
请求
curl -sS -X POST "https://<api-host>/open/api/v1/card-applications" -H "Idempotency-Key: <uuid>" -d '{"cardholder_id":"10001","product_id":"880100"}'- 所需 scope:
issue;须携带Idempotency-Key - 主体取自凭证,请勿在请求体传入商户标识
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cardholder_id | string | 是 | 持卡人 ID。须为本商户名下、且 KYC 等级满足开卡要求的持卡人 |
product_id | string | 是 | 卡产品 ID(虚拟卡产品)。产品目录请经商户门户「卡片产品」获取 |
响应参数(data)
| 字段 | 类型 | 说明 |
|---|---|---|
applicationId | string | 申请单号(数字串),轮询凭据 |
applicationNo | string | 申请单业务编号(OCA- 前缀) |
status | string | 申请状态:PENDING 已受理 / PROCESSING 处理中 / APPROVED 开卡成功 / FAILED 开卡失败 |
cardId | string \ | null |
failReason | string \ | null |
createdAt | string | 申请受理时间,ISO-8601 UTC |
响应示例
{
"code": 0,
"message": "success",
"data": {
"applicationId": "9900001",
"applicationNo": "OCA-9900001",
"status": "APPROVED",
"cardId": "123456",
"failReason": null,
"createdAt": "2026-09-12T08:00:00Z"
}
}查询申请状态
GET /open/api/v1/card-applications/{applicationId},data 结构与提交响应一致。他商户或不存在的申请单按不存在处理(errorCode=CARD_404)。
业务规则
| 规则 | 说明 |
|---|---|
| 幂等 | 同 Idempotency-Key 同参数重放返回首次受理结果,不重复受理;同 Key 参数不同返回 1009;缺 Key 返回 VAL_400 |
| 受理与终态 | 提交后平台同步执行开卡;提交响应通常即为终态(APPROVED / FAILED),也可经查询接口轮询 |
| 校验失败 | 参数、持卡人资格或产品不符合要求时,不产生申请单、不发生费用 |
| 执行失败 | 申请单进入 FAILED 终态并附 failReason,可换用新的 Idempotency-Key 重新提交;若提交时出现系统级异常,请按错误响应指引稍后查询,勿直接以原 Key 重试 |
| 开卡费用 | 按产品资费自商户通道资金中扣收 |
| 资金入卡 | 开卡后如需注资,请经划转接口(§5.10)或商户门户办理 |
错误码
| HTTP | code | errorCode | 说明 |
|---|---|---|---|
| 200 | 1001 | VAL_400 | 参数错误(缺 Idempotency-Key、产品已下架、非虚拟卡产品等) |
| 200 | 1009 | 1009 | 同 Idempotency-Key 但参数不一致 |
| 200 | 1008 | 1008 | 同一 Idempotency-Key 的申请正在处理中,请稍后查询 |
| 200 | 9000 | PROD_404 | 卡产品不存在 |
| 200 | 3301 | 3301 | 持卡人档案不存在或 KYC 等级不足 |
| 200 | 1003 | AUTH_403 | 持卡人不属于本商户 |
6. Webhook 异步通知
平台在业务事件发生后,通过 HTTP POST(JSON) 主动通知商户服务端。通知为单向推送:商户无需(也不能)通过本通道向平台发起请求;查询类需求请使用 §5.9 的 Webhook 查询接口。
| 项目 | 约定 |
|---|---|
| 方向 | 平台 → 商户(商户服务端接收) |
| 方法与编码 | POST,Content-Type: application/json; charset=UTF-8 |
| 触发时机 | 业务事务提交后异步发出(AFTER_COMMIT);通知失败不影响业务主流程 |
| 触发延迟 | 通常秒级;受重试计划影响见 §6.6 |
| 接收地址 | 商户在控制台为每个 endpoint 配置的 HTTPS URL(≤2048 字符) |
| 签名密钥 | 商户创建 endpoint 时自行填写的 secret(≥16 字符),请妥善保管 |
| 超时 | 连接与读取均 5 秒 |
| 成功判定 | 商户返回 HTTP 2xx 即视为投递成功;其余状态码或超时均判失败并进入重试 |
6.1 订阅管理
Webhook endpoint 的创建、修改、删除、测试与一键重发在商户控制台完成(设置中心 → Webhook;对应门户接口 /merchant/api/v1/webhooks),开放 API 侧仅提供只读查询(§5.9)。规则:
| 规则 | 说明 |
|---|---|
| URL | 必须 HTTPS,长度 ≤2048 字符 |
| secret | 必填,≥16 字符;仅创建时展示一次,遗忘须更换 |
| 事件订阅 | 按 §6.8 事件目录勾选;同一事件最多配置 3 个不同 URL |
| 试投递 | 控制台「测试」按钮:向该 endpoint 推送一条 data={"ping":true,"test":true,"endpoint_id":…} 的连通性通知,id 形如 evt_test_<雪花ID> |
| 一键重发 | 仅对终态为 FAILED / ABORTED 的投递生效;重发不重置已尝试次数 |
6.2 通知请求头
| Header | 说明 |
|---|---|
Content-Type | application/json; charset=UTF-8 |
X-Signature | 通知签名,HMAC-SHA256 小写 hex(64 位),见 §6.3 |
X-Timestamp | 通知发起时刻的 Unix 秒级时间戳 |
X-Event | 事件名,与报文 event 字段一致(如 deposit.credited) |
X-Event-Id | 事件 ID,与报文 id 字段一致 |
注意注意:请求头
X-Timestamp为秒;报文内created_at为 ISO-8601 UTC(带Z后缀)。两者口径不同,请勿混用。
6.3 通知报文与签名验证
通知报文信封(固定字段顺序):
{
"id": "evt_17_3f9c2a1b8d4e",
"event": "deposit.credited",
"created_at": "2026-09-13T01:23:45.123Z",
"merchant_id": "MCH_100",
"data": { }
}| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 事件 ID,全局唯一,格式 evt_<序列>_<12位随机串>;幂等去重键 |
event | string | 事件名,见 §6.8 目录 |
created_at | string | 事件创建时刻,ISO-8601 UTC(yyyy-MM-dd'T'HH:mm:ss.SSS'Z') |
merchant_id | string | 商户标识,MCH_ 前缀 + 商户数字 ID |
data | object | 事件业务数据,逐事件定义见 §6.9 |
签名算法:
X-Signature = HMAC-SHA256(key = endpoint secret 的 UTF-8 字节,
msg = 完整原始请求 Body)- 签名串是原始 Body 字节——请勿将 Body 反序列化后再重新序列化参与验签(字段顺序或空白变化会导致验签失败);
- 签名不包含任何 Header(含
X-Timestamp); - 输出为 64 位小写 hex。
验签步骤(建议顺序):
- 读取
X-Timestamp(秒),校验与当前时间偏差 ≤ 5 分钟,超出直接拒绝(防重放); - 读取原始请求 Body(字节级,不做任何重新序列化);
- 以 endpoint
secret为密钥计算HMAC-SHA256(secret, body); - 与
X-Signature比对(建议常量时间比较,防时序攻击); - 验签通过后按
id幂等去重(同一id可能因重试收到多次,见 §6.6),再处理业务。
验签示例(Python):
import hmac, hashlib, time
def verify(body: bytes, signature: str, secret: str, ts_header: str) -> bool:
if abs(time.time() - int(ts_header)) > 300: # 1. 时间窗(秒)
return False
expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature) # 2+3+4. 原始 Body + 常量时间比较验签示例(Java):
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
static boolean verify(byte[] rawBody, String signature, String secret, String tsHeader) throws Exception {
if (Math.abs(Instant.now().getEpochSecond() - Long.parseLong(tsHeader)) > 300) return false;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal(rawBody);
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02x", b));
return java.security.MessageDigest.isEqual(
hex.toString().getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}6.4 响应要求与幂等处理
- 商户处理完成后应返回任意 2xx(如
200);非 2xx 或 5 秒内未响应视为失败,触发重试; - 平台不解析商户响应体;
- 同一事件可能因重试送达多次:请以报文
id(或X-Event-Id)做幂等键,先查重再落库; - 建议先快速落库/入队再异步处理业务,避免复杂逻辑超出 5 秒超时。
6.5 数据安全与脱敏
平台通知报文永不包含完整卡号(PAN)、CVV、密码等敏感要素;卡相关事件仅返回卡号后四位 last4。商户也不应在接收日志中记录多余敏感字段。若报文因故包含键名含 pan / cvv / card_number / password 的字段,平台侧会在组装时自动剔除。
6.6 重试机制与 endpoint 状态
| 尝试 | 时机 |
|---|---|
| 首次 | 事件发生后即时 |
| 第 1 次重试 | 失败后 1 分钟 |
| 第 2 次重试 | 再失败后 5 分钟 |
| 第 3 次重试 | 再失败后 15 分钟 |
- 共计最多 4 次尝试(首次 + 3 次重试);全部失败后该投递标记为
FAILED,不再自动重试; - 某条投递重试耗尽仍失败,所属 endpoint 会被置为
PAUSED(暂停自动投递,避免持续打挂商户端);商户可在控制台修复后对该投递一键重发; - 同一 endpoint 连续失败累计达 10 次会被自动置为
DISABLED(禁用);任一次成功即清零连续失败计数; - 投递状态值:
PENDING(待首投)/SUCCESS/RETRYING(等待重试)/FAILED(重试耗尽)/ABORTED(endpoint 已暂停或禁用,不再投递); - 排查入口:控制台投递记录(含每次尝试的 HTTP 状态码与错误摘要),或开放 API
GET /webhooks/{id}/deliveries。
6.7 事件目录
共 19 个事件。持卡人交易、KYC、提现类事件仅向商户主体订阅开放;settlement.posted、position.*、rate.merchant_changed 另对代理主体开放。
| 事件 | 事件名 | 触发时机 | data 关键字段 |
|---|---|---|---|
| 充值入账成功 | deposit.credited | 持卡人充值入账成功 | amount currency deposit_id cardholder_id from_in_transit |
| 充值入账异常 | deposit.exception | 充值入账异常 | amount currency deposit_id cardholder_id exception_code reverse_in_transit |
| 账户划拨完成 | transfer.completed | 账户→卡划拨成功 | amount currency transfer_id account_id card_id last4 |
| 开卡成功 | card.issued | 开卡成功 | card_id cardholder_id product_id last4 status |
| 卡片状态变更 | card.status_changed | 卡片状态变更 | card_id action previous_status new_status reason_type reason_detail last4 |
| 交易授权成功 | transaction.authorized | 交易授权成功 | amount currency auth_id card_id channel_tx_id last4 |
| 交易授权被拒 | transaction.declined | 交易授权被拒 | amount currency auth_id card_id channel_tx_id decline_code last4 |
| 交易清算完成 | transaction.cleared | 交易清算完成 | amount currency clearing_id card_id channel_clear_id channel_tx_id applied_amount shortfall last4 |
| 交易退款过账 | transaction.refunded | 强制退款/赔付过账 | refund_id fund_subject amount currency card_id cardholder_id |
| KYC 审核通过 | kyc.approved | KYC 审核通过 | case_id cardholder_id kyc_level |
| KYC 审核驳回 | kyc.rejected | KYC 审核驳回 | case_id cardholder_id reason |
| KYC 补件重提 | kyc.resubmit_required | KYC 要求补件重提 | case_id cardholder_id resubmit_fields |
| 提现申请受理 | withdrawal.submitted | 提现申请已受理 | amount currency withdrawal_id withdrawal_no subject_type subject_id |
| 提现打款成功 | withdrawal.completed | 提现打款成功 | amount currency withdrawal_id withdrawal_no result bank_reference subject_type subject_id |
| 提现打款失败 | withdrawal.rejected | 提现打款失败/退票 | 同 withdrawal.completed |
| 佣金结算过账 | settlement.posted | 商户佣金 T+1 结算过账 | amount currency entry_id batch_id settlement_kind |
| 头寸充值入账 | position.topup_credited | 通道头寸充值入账 | amount currency topup_id |
| 头寸调回完成 | position.return_completed | 通道头寸调回完成 | amount currency return_id bank_reference |
| 商户费率变更 | rate.merchant_changed | 商户费率变更 | merchant_id product_id rate_version |
6.8 逐事件通知报文
以下 data 字段与平台实际推送一致。未标注「条件返回」的字段每次通知必返;金额一律为 string;资源 ID 在通知报文中为 JSON number(与开放 API 响应的 string 口径不同,请分别处理)。
充值入账成功 deposit.credited
持卡人充值(链上或通道)入账成功后推送。
{
"id": "evt_17_3f9c2a1b8d4e",
"event": "deposit.credited",
"created_at": "2026-09-13T01:23:45.123Z",
"merchant_id": "MCH_100",
"data": {
"amount": "10.50000000",
"currency": "USD",
"deposit_id": 10001,
"cardholder_id": 1001,
"from_in_transit": false
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount | string | 是 | 入账金额(十进制字符串) |
currency | string | 是 | 币种,ISO 3 位 |
deposit_id | number | 是 | 充值单 ID |
cardholder_id | number | 是 | 持卡人 ID |
from_in_transit | boolean | 是 | true=由在途转可用(此前已发过检测通知);false=直接入可用 |
充值入账异常 deposit.exception
充值入账异常(如链上金额与订单不符、低于起充金额等)后推送。异常处理后商户可在控制台按指引处置。
{
"id": "evt_18_a1b2c3d4e5f6",
"event": "deposit.exception",
"created_at": "2026-09-13T01:24:10.456Z",
"merchant_id": "MCH_100",
"data": {
"amount": "5.00000000",
"currency": "USD",
"deposit_id": 10002,
"cardholder_id": 1001,
"exception_code": "BELOW_MIN",
"reverse_in_transit": false
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 金额 / 币种 |
deposit_id | number | 是 | 充值单 ID |
cardholder_id | number | 是 | 持卡人 ID |
exception_code | string | 是 | 异常码,值域见附录 A.5 |
reverse_in_transit | boolean | 是 | 在途金额是否已冲回 |
账户划拨完成 transfer.completed
账户→卡拨付(ACCOUNT_TO_CARD)成功后推送。
{
"id": "evt_21_b2c3d4e5f6a1",
"event": "transfer.completed",
"created_at": "2026-09-13T01:30:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "50.00000000",
"currency": "USD",
"transfer_id": 3001,
"account_id": 2001,
"card_id": 123456,
"last4": "1234"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 拨付金额 / 币种 |
transfer_id | number | 是 | 拨付单 ID |
account_id | number | 是 | 资金账户 ID |
card_id | number | 是 | 卡 ID |
last4 | string | 条件 | 卡号后四位 |
开卡成功 card.issued
开卡成功(门户开卡或开放 API 申请开卡通过)后推送。
{
"id": "evt_22_c3d4e5f6a1b2",
"event": "card.issued",
"created_at": "2026-09-13T01:32:00.000Z",
"merchant_id": "MCH_100",
"data": {
"card_id": 123456,
"cardholder_id": 1001,
"product_id": 880100,
"last4": "1234",
"status": "ACTIVE"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
card_id | number | 是 | 卡 ID |
cardholder_id | number | 是 | 持卡人 ID |
product_id | number | 是 | 卡产品 ID |
last4 | string | 条件 | 卡号后四位 |
status | string | 条件 | 发卡后状态,值域见附录 A.1(虚拟卡通常 ACTIVE) |
卡片状态变更 card.status_changed
卡片状态变更(冻结、解冻、销卡、激活、挂失等)后推送。
{
"id": "evt_23_d4e5f6a1b2c3",
"event": "card.status_changed",
"created_at": "2026-09-13T01:35:00.000Z",
"merchant_id": "MCH_100",
"data": {
"card_id": 123456,
"action": "FREEZE",
"previous_status": "ACTIVE",
"new_status": "FROZEN",
"reason_type": "USER_REQUEST",
"reason_detail": "merchant-request",
"last4": "1234"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
card_id | number | 是 | 卡 ID |
action | string | 条件 | 变更动作,值域见附录 A.2 |
previous_status / new_status | string | 条件 | 变更前 / 后状态,值域见附录 A.1 |
reason_type | string | 条件 | 原因分类,值域见附录 A.3 |
reason_detail | string | 条件 | 原因补充说明 |
last4 | string | 条件 | 卡号后四位 |
交易授权成功 transaction.authorized
交易授权成功后推送。
{
"id": "evt_24_e5f6a1b2c3d4",
"event": "transaction.authorized",
"created_at": "2026-09-13T02:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "12.34000000",
"currency": "USD",
"auth_id": 4001,
"card_id": 123456,
"channel_tx_id": "CHN-AUTH-889",
"last4": "1234"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 授权金额 / 币种 |
auth_id | number | 是 | 授权记录 ID |
card_id | number | 是 | 卡 ID |
channel_tx_id | string | 是 | 渠道交易流水号 |
last4 | string | 条件 | 卡号后四位 |
交易授权被拒 transaction.declined
交易授权被拒后推送。
{
"id": "evt_25_f6a1b2c3d4e5",
"event": "transaction.declined",
"created_at": "2026-09-13T02:01:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "99.00000000",
"currency": "USD",
"auth_id": 4002,
"card_id": 123456,
"channel_tx_id": "CHN-AUTH-890",
"decline_code": "INSUFFICIENT_FUNDS",
"last4": "1234"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
同 transaction.authorized | — | — | — |
decline_code | string | 条件 | 拒绝原因码(稳定字符串,可供商户分类处理) |
交易清算完成 transaction.cleared
交易清算完成后推送。amount 为清算金额,applied_amount 为实际入账金额;二者不等(shortfall=true)即为短账场景。
{
"id": "evt_26_a1b2c3d4e5f7",
"event": "transaction.cleared",
"created_at": "2026-09-13T03:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "12.34000000",
"currency": "USD",
"clearing_id": 5001,
"card_id": 123456,
"channel_clear_id": "CHN-CLR-77",
"channel_tx_id": "CHN-AUTH-889",
"applied_amount": "12.34000000",
"shortfall": false,
"last4": "1234"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 清算金额 / 币种 |
clearing_id | number | 是 | 清算记录 ID |
card_id | number | 是 | 卡 ID |
channel_clear_id | string | 是 | 渠道清算流水号 |
channel_tx_id | string | 是 | 关联的渠道授权流水号 |
applied_amount | string | 条件 | 实际入账金额(短账等场景返回) |
shortfall | boolean | 条件 | 是否短账 |
last4 | string | 条件 | 卡号后四位 |
交易退款过账 transaction.refunded
强制退款 / 赔付过账后推送(平台侧资金调整,非消费冲正)。
{
"id": "evt_27_b2c3d4e5f6a8",
"event": "transaction.refunded",
"created_at": "2026-09-13T03:10:00.000Z",
"merchant_id": "MCH_100",
"data": {
"refund_id": 6001,
"fund_subject": "PLATFORM_OPEX",
"amount": "12.00",
"currency": "USD",
"card_id": null,
"cardholder_id": 1001
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
refund_id | number | 是 | 退款过账记录 ID |
fund_subject | string | 是 | 资金科目(平台内部分账标识) |
amount / currency | string | 是 | 退款金额 / 币种 |
card_id | number | 条件 | 关联卡 ID(可能为 null) |
cardholder_id | number | 是 | 持卡人 ID |
KYC 审核结果通知 kyc.approved / kyc.rejected / kyc.resubmit_required
持卡人 KYC 审核结果通知,三个事件同构:
{
"id": "evt_28_c3d4e5f6a1b9",
"event": "kyc.approved",
"created_at": "2026-09-13T04:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"case_id": 5001,
"cardholder_id": 1001,
"kyc_level": "L2"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
case_id | number | 是 | KYC 案件 ID |
cardholder_id | number | 是 | 持卡人 ID |
kyc_level | string | approved 条件 | 通过的等级,L0 / L1 / L2 / L3 |
reason | string | rejected 条件 | 驳回原因(面向商户的可读摘要) |
resubmit_fields | string | resubmit_required 条件 | 需补件的字段清单(序列化为字符串形式) |
提现申请受理 withdrawal.submitted
提现申请受理后推送(商户 / 持卡人主体)。
{
"id": "evt_29_d4e5f6a1b2c0",
"event": "withdrawal.submitted",
"created_at": "2026-09-13T05:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "500.00000000",
"currency": "USD",
"withdrawal_id": 7001,
"withdrawal_no": "WDR-M-7001",
"subject_type": "MERCHANT",
"subject_id": 100
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 提现金额 / 币种 |
withdrawal_id | number | 是 | 提现单 ID |
withdrawal_no | string | 是 | 提现单号(WDR-M-/WDR-A-/USDT-RETURN- 前缀 + ID) |
subject_type | string | 是 | 主体类型,CARDHOLDER / MERCHANT / AGENT |
subject_id | number | 是 | 主体 ID |
提现打款结果通知 withdrawal.completed / withdrawal.rejected
提现打款结果通知,两个事件 data 同构,result 与事件名对应(SUCCESS/COMPLETED → completed;FAILED/RETURNED → rejected):
{
"id": "evt_30_e5f6a1b2c3d1",
"event": "withdrawal.completed",
"created_at": "2026-09-13T06:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "500.00000000",
"currency": "USD",
"withdrawal_id": 7001,
"withdrawal_no": "WDR-M-7001",
"result": "SUCCESS",
"bank_reference": "BK-20260913-001",
"subject_type": "MERCHANT",
"subject_id": 100
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
同 withdrawal.submitted 公共字段 | — | 是 | — |
result | string | 是 | 打款结果:SUCCESS / RETURNED / FAILED |
bank_reference | string | 条件 | 银行/渠道参考号 |
佣金结算过账 settlement.posted
商户佣金 T+1 结算过账后推送。
{
"id": "evt_31_f6a1b2c3d4e2",
"event": "settlement.posted",
"created_at": "2026-09-14T01:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "88.00000000",
"currency": "USD",
"entry_id": 8001,
"batch_id": "STL-20260913",
"settlement_kind": "COMMISSION_ENTRY"
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 结算金额 / 币种 |
entry_id | number | 是 | 结算分录 ID |
batch_id | string | 是 | 结算批次号 |
settlement_kind | string | 是 | 结算类型,当前恒为 COMMISSION_ENTRY |
头寸资金通知 position.topup_credited / position.return_completed
通道头寸充值入账 / 调回完成后推送,两个事件 data 同构:
{
"id": "evt_32_a1b2c3d4e5f3",
"event": "position.topup_credited",
"created_at": "2026-09-13T07:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"amount": "10000.00000000",
"currency": "USD",
"topup_id": 9001
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
amount / currency | string | 是 | 金额 / 币种 |
topup_id / return_id | number | 是 | 充值单 / 调回单 ID(字段名随事件不同) |
bank_reference | string | return_completed 条件 | 银行/渠道参考号 |
商户费率变更 rate.merchant_changed
商户费率变更后推送。
{
"id": "evt_33_b2c3d4e5f6a4",
"event": "rate.merchant_changed",
"created_at": "2026-09-13T08:00:00.000Z",
"merchant_id": "MCH_100",
"data": {
"merchant_id": 100,
"product_id": 880100,
"rate_version": 3
}
}| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
merchant_id | number | 是 | 商户 ID |
product_id | number | 是 | 卡产品 ID |
rate_version | number | 是 | 变更后的费率版本号(递增) |
7. 常见问题
签名一直 403? GET 的 BODY 不要写成 {};PATH 不要带 ?;时间戳用秒;HMAC 的 key 用 hex 字符串的字节。
timestamp 和 X-Timestamp 差三个数量级? 请求头是秒,响应当中是毫秒。
/orders 是刷卡流水吗? 不是。这是用户充值单。卡交易明细不在 Open API。
可以取完整卡号吗? 不可以。只有 last4。
冻卡要不要 Idempotency-Key? 要。卡片冻结 / 解冻 / 销卡 / status、划转与申请开卡都必须带;同 Key 同参返回首次结果,同 Key 异参报 1009。
开卡申请 POST /card-applications 返回 status=FAILED 是出错了吗? 不是。code=0 表示本次调用成功;data.status=FAILED 是申请单终态(failReason 为失败原因码)。可修复原因(如头寸不足)后换用新的 Idempotency-Key 重新提交。若遇到系统级错误响应(非 200 业务体),请勿直接以原 Key 重试——先以 GET /card-applications/{applicationId} 查询确认状态。
W8.0 升级后旧客户端要改什么? 若旧客户端把 ID 按 number 解析,需改为按 string 处理(见 §4 ID 类型总表);调用 cardholders / kyc / webhooks 列表的客户端需适配新的 {total, page, size, items} 信封。请求体中账户/持卡人 ID 传历史 number 仍可被接受,但建议尽快改为 string。
W8.1 升级后旧客户端要改什么? GET /card-statements 由裸数组改为分页对象(破坏性):原 data[0].period 需改为 data.items[0].period,并适配 total / page / size。GET /finance/positions 与 GET /reconciliations 为增字段:原读 total / items 的代码无需改动即可继续工作,建议逐步补上 page / size 回显与翻页参数。GET /cards 与 GET /cards/{id}/transactions 的响应契约不变(/cards 数据来源下沉数据库分页,透明)。
W8.2 升级后旧客户端要改什么? 无破坏性变更,无需改动。GET /cards/{id}/transactions 修复了数据完整性缺陷(历史版本在商户交易量超过 1 万条时会静默截断单卡交易列表;W8.2 起返回完整真实条数,响应契约与行项字段不变)。/orders 为 /deposits 的同义别名,新开发请使用 /deposits;/orders 保留为兼容别名,不设下线日期。
W8.2 增补(2026-09-12)升级后旧客户端要改什么? 若有提现 / 对账集成:相关端点已移除(HTTP 404),请迁移至商户控制台(门户)操作。若集成卡片写操作:freeze / unfreeze / close / status 从本增补起必须携带 Idempotency-Key(缺头 VAL_400),并为每次业务操作生成唯一 Key。其余接口契约不变。
收不到 Webhook 通知,如何排查? 按顺序检查:① 控制台确认 endpoint 状态为 ACTIVE(PAUSED/DISABLED 时自动投递已停止,见 §6.6);② 投递记录中查看最近投递的 HTTP 状态码与错误摘要(控制台或 GET /webhooks/{id}/deliveries);③ 确认接收地址为公网可达的 HTTPS 且未拦截平台出口 IP;④ 确认商户端在 5 秒内返回了 2xx(复杂业务建议先落库后处理);⑤ 修复后用控制台「测试」发一条连通性通知,再对失败投递一键重发。
Webhook 验签一直失败? 高频原因:① 把 Body 反序列化后重新序列化再验签——必须用原始字节(字段顺序会变);② 时间窗单位搞错——X-Timestamp 是秒,窗口 ±300 秒;③ 密钥用错——是 endpoint 的 secret 原文(UTF-8 字节),不是 API Secret,也不是任何哈希;④ 忽略大小写——签名为小写 hex,比较时请勿改变大小写。
同一事件收到了多次通知,怎么办? 属正常重试行为(非 2xx 或超时会触发,见 §6.6)。请以报文 id(或 X-Event-Id)为幂等键先查重再处理;重复通知重复处理业务属商户侧缺陷。
通知报文里的 ID 是 number,开放 API 响应里却是 string? 两端口径不同:Webhook data 内资源 ID 为 JSON number(如 "card_id": 123456);开放 API 响应 ID 一律 string(§4 ID 类型总表)。分别按各自口径解析,勿混用。
沙箱 / 测试环境怎么联调 Webhook? 当前开放通道无独立沙箱环境。可在控制台对 endpoint 使用「测试」按钮发送连通性通知;事件级联调建议在低风险真实环境小金额进行。
8. 附录
A.1 卡片状态(status)
| 值 | 含义 |
|---|---|
DRAFT | 草稿(未提交) |
ISSUING | 开卡中 |
ACTIVE | 已激活可用 |
FROZEN | 已冻结 |
SUSPENDED | 已暂停 |
LOST | 已挂失 |
EXPIRING | 临期 |
CLOSED | 已销户 |
IN_STOCK | 在库(实体卡库存) |
PENDING_ACTIVATION | 待激活(实体卡发卡后) |
A.2 卡片状态变更动作(action)
| 值 | 含义 |
|---|---|
FREEZE / UNFREEZE | 冻结 / 解冻 |
CLOSE | 销卡 |
SUSPEND / RESUME | 暂停 / 恢复 |
LOST | 挂失 |
MARK_EXPIRING | 标记临期 |
RENEW | 换卡续期 |
ACTIVATE | 激活 |
ALLOCATE | 发放(出库至持卡人) |
RETURN_STOCK | 退回库存 |
A.3 状态变更原因分类(reason_type)
| 值 | 含义 |
|---|---|
FRAUD_SUSPICION | 欺诈嫌疑 |
USER_REQUEST | 用户请求 |
EXPIRED | 到期 |
COMPLIANCE | 合规要求 |
OTHER | 其他 |
A.4 KYC 等级(kyc_level)
| 值 | 含义 |
|---|---|
L0 | 基础(未认证) |
L1 | 一级认证 |
L2 | 二级认证 |
L3 | 三级认证 |
A.5 充值异常码(exception_code)
| 值 | 含义 |
|---|---|
USER_UNMATCHED | 付款人与订单不匹配 |
ADDRESS_MISMATCH | 充值地址与订单不匹配 |
BELOW_MIN | 低于最低充值金额 |
ASSET_NOT_SUPPORTED | 币种(资产)不支持 |
NETWORK_NOT_SUPPORTED | 链(网络)不支持 |
CHANNEL_APPLY_FAIL_<state> | 通道申请失败(<state> 为渠道状态码) |
OTHER | 其他异常 |
A.7 KYC 案件状态(status)
| 值 | 含义 |
|---|---|
DRAFT | 草稿(未提交) |
SUBMITTED | 已提交 |
AUTO_REVIEW | 自动审核中 |
PENDING_MERCHANT | 待商户审核 |
PENDING_PLATFORM | 待平台审核 |
PENDING_RESUBMIT | 待补充材料 |
PENDING_LIVENESS / LIVENESS_PASSED | 活体检测中 / 已通过 |
APPROVED / REJECTED | 审核通过 / 驳回 |
EXPIRED / CANCELLED | 已过期 / 已取消 |
A.8 充值单状态(status)
| 值 | 含义 |
|---|---|
DETECTED | 已检测到入金(在途) |
PENDING_REVIEW | 待人工审核 |
CREDITED | 已入账 |
EXCEPTION | 异常(附异常码,见 A.5) |
IGNORED | 已忽略(重复/迟到入金等) |
REJECTED | 已驳回 |
A.6 Webhook 投递与 endpoint 状态
| 投递状态 | 含义 |
|---|---|
PENDING | 待首次投递 |
SUCCESS | 投递成功(收到 2xx) |
RETRYING | 投递失败,按 1m/5m/15m 计划等待重试 |
FAILED | 重试耗尽(共 4 次尝试)仍失败 |
ABORTED | endpoint 已暂停 / 禁用,放弃投递 |
| endpoint 状态 | 含义 |
|---|---|
ACTIVE | 正常投递 |
PAUSED | 有投递重试耗尽后被暂停(自动投递停止) |
DISABLED | 连续失败达 10 次被禁用(或人工删除) |
9. 变更记录
W8.2 增补 4(2026-09-13,文档审查修正)
仅文档修正,无接口契约变更(按逐端点审查结果修正文档与实现不一致处):
| 修正 | 说明 |
|---|---|
| §5.11 错误码表更正 | 机读码更正为 PROD_404 / 3301 / AUTH_403(1003)/ 1008;受理与终态表述与实际执行模型对齐 |
| §5.8 KYC | 状态示例与筛选值更正(值域见新增附录 A.7);证件影像字段注明仅详情返回 |
| §5.10 划转 | 补列表与详情响应字段表;更正通知说明(本操作不产生 Webhook);披露大额复核阈值 $50,000;补幂等键长度约束 |
| §5.4 持卡人 | kycStatus 字段语义更正为 KYC 等级(L0–L3/NONE);补详情响应字段表 |
| §5.3 卡片操作 | 补冻结 / 解冻 / 销卡前置条件与 CARD_409、CHN_502、1008 语义 |
| §5.5 卡交易 | 补交易行项完整字段表;详情支持参考号查询 |
| §4 通用约定 | 路径 ID 规则补充例外;租户隔离例外修正;错误码总表补全 |
| 附录 | 新增 A.7 KYC 状态、A.8 充值单状态值域 |
W8.2 增补 3(2026-09-13,文档专业化与 Webhook 章节补全)
仅文档增强,无代码契约变更(端点、参数、错误码、capabilities 均不变):
| 变化 | 说明 |
|---|---|
| 新增 §6 Webhook 异步通知 | 通知机制总览、报文信封、签名验证(含 Python/Java 示例)、响应要求、重试与 endpoint 状态机、脱敏说明;19 个事件逐事件 payload 示例与字段表(与平台实际推送对齐)。 |
| 新增 §8 附录 | 卡片状态 / 变更动作 / 原因分类 / KYC 等级 / 充值异常码 / 投递与 endpoint 状态等枚举值域表。 |
| 全文专业化重构 | 对齐支付行业对接文档规范:补充文档信息表、目录、「必返」字段标注、枚举值域引用、FAQ 扩充(收不到通知 / 验签失败 / 重复通知等 5 条)。 |
W8.2 增补 2(2026-09-12,API-FUND-019 申请开卡)
新增能力(非破坏)
| 变化 | 说明 |
|---|---|
新增 POST /card-applications(scope issue) | 商户开放通道申请开卡:提交后异步受理,可经申请单号轮询状态;Idempotency-Key 必填;当前支持虚拟卡产品。见 §5.11。 |
新增 GET /card-applications/{applicationId}(scope read) | 开卡申请单轮询;他商户按不存在处理(CARD_404)。 |
issue scope 落地 | POST /card-applications 为 issue 的消费点;无 issue 的显式 scopes 密钥调用返回 403 OPEN_403。 |
/status capabilities +2 | 快照增至 28 条(示例已同步)。 |
W8.2 增补(2026-09-12,产品裁决)
能力收敛(破坏性)
| 变化 | 说明 |
|---|---|
移除 GET/POST /withdrawals、GET /withdrawals/{id}、GET /reconciliations、GET /reconciliations/{statementId}/download | 开放 API 不再提供提现与对账能力(含 /status capabilities 同步裁剪),调用返回 HTTP 404 NOT_FOUND。提现 / 对账请使用商户控制台(门户)。 |
卡片写操作强制 Idempotency-Key | POST /cards/{id}/freeze · /unfreeze · /close · /status 缺头返回 VAL_400;同 Key 同参返回首次结果;同 Key 异参返回 1009。 |
非破坏变更
POST /merchant-credits的Idempotency-Key注解口径改为必填(与既有服务端强制校验对齐,缺头VAL_400,行为语义不变)。GET /orders(/deposits)数据源下沉数据库分页,total为筛选后真实总数;响应契约与分页夹紧语义不变。
W8.2(2026-09-10)
无对外契约破坏性变更。
数据完整性修复
| 接口 | 变化 |
|---|---|
GET /cards/{id}/transactions | 改为单卡直达数据源:授权按卡 ID、清算按商户 ID 索引查询后合并,分页在应用层完成。修复历史缺陷:旧实现按商户交易全集(上限 10,000 条)拉取后内存按卡过滤,商户交易量超过 1 万时单卡交易会静默缺失。W8.2 起 total 为该卡真实交易条数,响应契约({total, page, size, items})与行项字段不变。 |
内部改进(契约不变)
GET /transactions(及导出 / 月度账单等商户侧卡交易聚合)数据源收窄:授权按卡集合(idx_card_ctime)、清算按商户(idx_merchant_time)索引查询,消除全表扫描;排序与合并口径不变。GET /reconciliations的issues商户过滤由表现层下沉至应用层(total口径不变)。/status的ga字段 W8.1 → W8.2;capabilities 快照不变(31 条,以实时返回为准)。
别名指引
/orders为/deposits的同义别名,新开发请使用/deposits(语义更明确);/orders保留为兼容别名,不设 sunset。
W8.1(2026-09-10)
破坏性变更(列表信封)
| 接口 | W8.0 及以前 | W8.1 起 |
|---|---|---|
GET /card-statements | 裸数组 data: [...] | {total, page, size, items},query 增 page / size |
非破坏变更(增字段 / 增参数)
| 接口 | 变化 |
|---|---|
GET /finance/positions | data 由 {total, items} 增补为 {total, page, size, items};query 增 page / size |
GET /reconciliations | statements 与 issues 两块各自由 {total, items} 增补为 {total, page, size, items};query 增 page / size(同时作用于两块) |
无契约变化的内部改进
GET /cards分页下沉数据库:生产仓储按merchantId+status等 SQL 层过滤、计数与取页(原为拉取上限 10,000 条全集内存切片,超限时静默截断);响应契约({total, page, size, items})与行项字段不变。/status的ga字段 W8.0 → W8.1;capabilities 快照不变(31 条,以实时返回为准)。
W8.0(2026-09-09)
破坏性变更(出参 ID number → string)
| 资源 | 字段 | 变化 |
|---|---|---|
GET /cards · GET /cards/{id} | cardholderId | number → string |
GET /merchant-credits/{id} | cardholderId | number → string |
GET /webhooks · GET /webhooks/{id} | endpointId subscriberId | number → string |
GET /webhooks/{id}/deliveries | deliveryId endpointId | number → string |
GET /reconciliations/{statementId}/download | merchantId | number → string |
GET /reconciliations 的 issues.items[*] | merchantId | number → string |
破坏性变更(列表信封)
| 接口 | W7.1 及以前 | W8.0 起 |
|---|---|---|
GET /cardholders | {merchantId, total, items}(无 page 参数) | {total, page, size, items},query 增 page / size(merchantId 字段移除) |
GET /kyc | 裸数组 data: [...] | {total, page, size, items},query 增 page / size |
GET /webhooks | 裸数组 data: [...] | {total, page, size, items},query 增 page / size |
GET /webhooks/{id}/deliveries | 裸数组 data: [...] | {total, page, size, items},query 增 page / size |
请求体契约变化(非破坏,双向兼容)
| 接口 | 字段 | 契约 | 兼容性 |
|---|---|---|---|
POST /merchant-credits | cardholderId(含 cardholder_id 别名) | string | 历史 number 入参仍可解析 |
POST /withdrawals | commissionAccountId payoutAccountId | string | 历史 number 入参仍可解析 |
非破坏改进
GET /cards响应体类型化(JSON 形态不变,仍为{total, page, size, items})。- 路径
{id}非数字统一返回VAL_400(code 1001),错误消息不回显原始输入。 POST /merchant-credits请求体校验(@Valid)与提现申请对齐;缺字段返回VAL_400而非延迟到业务层报错。/status的ga字段 W7.1 → W8.0;capabilities 快照更新为 31 条(以实时返回为准)。