变更日志
每条都标注了「是否需要你动手」。 标「需要」的,不做会在某一天出问题;
标「不需要」的是纯增量,你不改代码也不会坏。
我方对枚举值的承诺:新增状态值会提前在这里登记。所以你的 default
分支应当按「未知」处理并告警,而不是假装认识它 —— 见各端点页的状态说明。
这份变更日志从 2026-08-12 开始维护。在此之前的变更没有逐条记录。
| 日期 | 类别 | 变更 | 要动手吗 |
|---|---|---|---|
2026-08-13 | 变更 | network 统一成机读码;GET /v1/assets 补出网络清单;quote_id 不再被静默忽略 | 是 |
2026-08-13 | 变更 | 卡两条事件的 data.id 改成真实单号;previous_status 删除 | 是 |
2026-08-13 | 变更 | 沙盒的上游故障注入真的生效了 | 否 |
2026-08-12 | 变更 | 业务拒绝不再以 500 返回 | 是 |
2026-08-12 | 变更 | 四个一调用就 500 的端点修好了 | 否 |
2026-08-12 | 变更 | 汇款详情不再回显内部状态,金额格式与列表统一 | 是 |
2026-08-12 | 变更 | GET /v1/merchant/custody 随行下发 ledger_scale | 否 |
2026-08-12 | 变更 | 89 个端点补齐参数、请求体、响应与示例 | 否 |
2026-08-12 | 变更 | 错误码目录公开,共 42 个对外码 | 是 |
2026-08-12 | 变更 | 事件目录公开,共 20 条 | 是 |
2026-08-12 | 变更 | 分页信封统一为 { data, next_cursor, has_more } | 否 |
2026-08-12 | 变更 | 业务端点开始限流 | 是 |
2026-08-12 | 变更 | Webhook 投递改为按端点扇出 | 否 |
2026-08-12 | 变更 | 沙盒事件与生产隔离 | 否 |
network 统一成机读码;GET /v1/assets 补出网络清单;quote_id 不再被静默忽略
需要你动手需要你动手(第 ① 条是破坏性变更)。
① GET /v1/merchant/deposit-addresses 的 network 改成机读码。
此前它出的是展示名(BNB Smart Chain (BEP20)),而同一资源的
POST 收的是机读码(BSC)—— 把 GET 回给你的值原样回传给 POST
必然 asset_not_allowed。这是一个自相矛盾的契约,现在两边同口径:
network 一律是机读码,展示名改走新增的 network_name。
如果你的代码里存过这个字段并拿它做过展示,请改读 network_name;
如果你曾经为此硬编码过一张映射表,可以删掉了。
② 本页此前把 BSC 的机读码写成 BEP20 —— 那个值不存在。
正确值是 BSC;BEP20 只出现在展示名里。照旧文档传 BEP20 会被拒。
③ GET /v1/assets 现在逐资产给出 networks[]。
这是机读码的唯一权威清单,也补上了此前完全拿不到的那些数:
逐网络的 deposit_enabled / withdraw_enabled(不可用的也列出并给原因)、
min_deposit / confirmations_required / memo_required、
提现侧的 min_withdraw / max_withdraw / withdraw_fee_* /
withdraw_cooling_hours / withdraw_eta_minutes。
请不要再把网络清单写死在代码里 —— 加币种是我方后台加一行、不发版,
写死的那份不会跟着变。
④ 会员兑换的锁价现在是真的了 —— quote_id 可用。
POST /v1/exchange/quotes 带 lock: true 拿回 quote_id,
POST /v1/exchange/orders 带上它就按那份报价成交;过期一律拒,
绝不回落成按新价成交。quote_id 就是这一单的订单号,
成交请求超时时直接 GET /v1/exchange/orders/{quote_id} 就能查成没成。
⚠ 锁价要显式索取(lock 缺省 false)。两条理由:这个端点会被当成
「给界面显示一个数」高频调用,无条件建单会把你的订单清单灌满从未成交的
quoted 行;而一份锁定的报价对你就是一个 quote_ttl_sec 秒的免费期权,
所以要就显式要,每一次都是一行可数、可审计的记录。
⚠ 锁的是价,不是授权 —— 成交那一刻照样跑全部闸(账户处置、方向停用、
强认证、限额、余额)。没锁价时 quote_ttl_sec 仍然只是参考值。
背景(这条修的是我方的一次前后矛盾):接口注释一度写着「想锁价的商户……
下单时再传 quote_id」,而 quotes 从来不返回 id、orders 也从来不读它 ——
照那句话写的代码以为自己锁了价,实际按成交那一刻的新价成交,
差额自己吃且全程零提示。中途曾短暂改成「传了当场拒
(quote_lock_unsupported)」,现在能力补齐,那条码在会员兑换上不再出现;
它今天唯一的适用处是商户自兑换(POST /v1/merchant/conversions,
那条线还没有锁价)。
data.id 改成真实单号;previous_status 删除
需要你动手需要你动手(两处都很小,但不改会在某一天出问题):
① card.application.approved 与 card.status.updated 的 data.id。
此前它们发的是会员内部 id,而 data.object 写着 card_application /
card —— 拿它回查必然 404。现在发真实单号:申请单号(回查
GET /v1/cards/applications/cap_<id>)与卡 id(回查 GET /v1/cards/crd_<id>)。
如果你的 handler 里写过「卡这两条的 id 是会员 id」的特例,请拆掉。
这两条同时开始带真实 status_version —— 卡状态会连着来两条
(freezing → frozen),请按版本号向前合并。
② 事件体里不再有 previous_status。 它一直恒为空串(一个生产者都没有)。
如果你写过 if (data.previous_status === …),那条分支从来没有成立过;
请改用你自己库里的当前值。我方不会补上真值 —— 同一次状态变更重复到达是
常态,第二次到达时「上一个状态」已经等于新状态,一个错的值比没有更危险。
顺带:其余 7 条 status_version 恒为 0 的事件不是待办,
每一条的原因已逐条写进事件目录与
Webhook 指南。对它们请继续按「到达时间 + 回查确认」处理。
POST /v1/sandbox/upstream-behavior 此前只把值写进去、四家上游客户端
一个都不读它(2026-08-12 那条里登记过)。现在注入会真的替换掉该上游
动钱那一跳的响应,我方随即照生产上逐字相同的逻辑处置它。
于是这三条平时无法按需触发的路径可以在沙盒里跑了:
timeout / unknown(→ 结果不明,钱继续锁着)、
reject(→ 判死并释放冻结)、
unknown_status(→ 挂起并告警,不 default 成「处理中」)。
不需要你动手,但强烈建议加一步联调:注一次 timeout,把你的
「结果不明」分支真的跑一遍 —— 那是整套接入里最容易只写不测的分支,
而它写错的后果是既退款又付款。详见 沙盒。
⚠ 同一家上游的只读调用(报价、解码、查询)不受注入影响,照常打真实上游;
这两个端点在 live 上仍然是 404。
「余额不足」「报价过期」「该方向未启用」「地址校验和不对」这类合法的业务拒绝
此前走对外码目录的缺省分支,以 500 api_error 返回。现在逐条有明确的 4xx code。
需要你动手:如果你为此写过「对 500 也重试」的绕行代码,请拆掉并改成按 code 分支。
新增对外码 amount_out_of_range(金额超出产品区间)——
它与 limit_exceeded 的处置相反:那条是额度用完了要等,这条改个数就能过。
同时删掉了目录里 21 个零发射点的死键,并加了守卫
(内部码没登记、或登记了却没人发,提交期就红)。
GET /v1/cards/{id} · GET /v1/cards/{id}/transactions ·
GET /v1/earn/products/{id} · POST /v1/cards/{id}/replacements。
卡消费流水新增 direction 出参 —— amount 是绝对值,
只看金额的话一笔退款与一笔消费长得一模一样。
GET /v1/remittances/{id} 此前可能返回 pending_merchant_funds
(列表与下单口都折叠成 pending),且 source_amount 是未插小数点的
定点整数串 —— 同一笔单在两个端点上差 10^ledger_scale 倍。
需要你动手:如果你为「详情与列表金额对不上」写过换算,请拆掉。
现在两边都是十进制串并随行下发 ledger_scale。
GET /v1/merchant/custody 随行下发 ledger_scale
不需要动手对账用的两个数是定点整数串,此前不下发位数,而同一条线的
/v1/merchant/statements 一直下发 —— 商户在一个端点上能正确定标、
在另一个上只能猜。null 表示该资产已从目录下架(对账表会留历史行)。
此前 spec 里只有「方法 + 路径 + 一句话 + scope」,没有任何字段声明。
现在每个端点一页,含参数表、请求体字段、响应示例、三种语言的调用样例。
接口行为没有变化,变的只是文档。
新增 /errors 页,逐条写明含义与你该怎么处置(该重试的、
不能重试的、需要人介入的)。
需要你动手:如果你的实现里有「按 HTTP 状态码分支」的重试逻辑,
请改成按 code 分支。几个反直觉的:service_unavailable、
limit_exceeded、asset_not_allowed、state_invalid 都是 400 不是 5xx。
新增 /events 页,含事件体示例与投递语义。
需要你动手:目录里逐条标注了 data.id 的真实形态与
status_version 是否可用 —— 有 9 条事件的 status_version 恒为 0。
如果你写了「version 不大于当前值就跳过」的合并器,这 9 条只有第一条会生效。
在我方修好之前,请对这几条改用「按到达时间 + 回查确认」。
{ data, next_cursor, has_more }
不需要动手静态清单端点(如 GET /v1/transfers/recipient-types)也返回同一个信封,
你的通用翻页器不需要为它们开特例。
⚠ 一处例外尚未收口:GET /v1/balances 仍返回 { balances: [...] }。
端点页上已标注,修好后会在这里登记。
按商户 × 桶计:入金上报 60/分钟、建会员 300/分钟、其余写 120/分钟、
读 1200/分钟。超出返回 429 + Retry-After。
需要你动手:把 429 当正常路径处理并按 Retry-After 退避。
收到 429 不代表你被封了。详见 限流。
此前「一个 2xx 就算全成功」:配了三个端点、一个通两个 500 时,
那两个永远不会重试而记录显示已送达。现在一行 = 一个(事件, 端点),
各自退避;新增 no_subscriber 一档(零行看起来像「还没有事件」而不是「坏了」)。
投递行带环境,订阅端点查询带环境谓词。沙盒端点不再收到生产事件。
⚠ 上游故障注入器当时是空操作(注入之后仍打真实上游)。
已于 2026-08-13 修好(见本页最新那一条),详见 沙盒。