Z Zise Developers

沙盒

沙盒是一个独立的商户主体,不是生产库上的一个开关。


https://api-sandbox.zise.com

你的沙盒 Key 与 Live Key 是两把,各自解析到各自的主体。跨环境凭据一律拒

返回专门的码 environment_mismatch(不是笼统的「凭据无效」)。

隔离到什么程度

隔离
会员、订单、账本✅ 独立主体,Live 查不到沙盒建的会员
Webhook 投递✅ 投递行带环境,沙盒事件不会推到 Live 端点
业务线开关与三段成本跟随母体
上游服务方打真实上游,但可以按需注入故障(见下)

上游故障注入

沙盒里跑的是与生产逐字相同的业务代码,所以默认情况下每一单都打真实上游。

真实上游不会配合你在想要的时刻超时 —— 而「上游超时」恰恰是最该被测、

也最容易写错的一档。

/v1/sandbox/upstream-behavior 让你把某一家上游预置成指定结果:


curl -X POST https://api-sandbox.zise.com/v1/sandbox/upstream-behavior \
  -H 'x-auth-token: Bearer <沙盒令牌>' \
  -d '{"upstream":"qrpay","behavior":"timeout"}'
behavior模拟的是你应当观察到
success正常(缺省)打真实上游
timeout上游一个字节都没回订单落结果不明,钱继续锁着
unknown上游回 5xx / 幂等中间件故障timeout 完全相同的处置
reject上游明确拒绝这一笔订单判死、冻结释放
unknown_status上游回了个我方不认识的状态订单挂起并触发我方告警

upstreamremittance(汇款)· card(发卡)· qrpay(扫码付)·

chain(链上存提)。它是业务线,不是某一家具体的服务方 —— 我方在每条线上

接的是谁、有几家、什么时候换,都不是契约的一部分,所以你的联调脚本不会因为

我方换服务方而失效。注入 1 小时后自动回到 success —— 一个忘了复位的注入

会让你下一次联调拿到一堆莫名其妙的失败。

四件请务必知道的事:

1. 注入的是上游的响应,不是订单状态。 我方拿到这个响应之后,照生产上

逐字相同的分类、重试、留痕、状态机去处置它。所以你在沙盒里看到的订单状态

与 Webhook,就是同一件事在生产上会有的样子。

2. 只有「动钱的那一跳」被替换。 同一家上游的报价 / 解码 / 查询等只读调用

照常打真实上游。这是有意的:扫码付一次支付里 createpay(报价)在前、

paynotify(动钱)在后 —— 全部替换的话订单会在锁钱之前就干净地失败,

而你真正要测的「钱可能已经放行了」那一支永远走不到。

3. timeoutunknown 落到同一类处置,这不是重复。 两者在你的日志里

长得完全不同(一个没有响应、一个有 5xx),而处置必须相同:都属于

「结果不明」,都不许当成失败去退款。分成两档正是为了让你验证这一点。

4. unknown_status 回的是哨兵值 SANDBOX_UNKNOWN_STATUS 它模拟

「上游悄悄加了一个枚举值」—— 我方对它的纪律是挂起 + 告警,

绝不 default 成「处理中」。你的状态机也不该。

⚠ 这两个端点在 live 上是 404(不是 403):一条「按请求改上游行为」的

路径在生产上根本不该存在。用 Live Key 调它不会有任何效果。

最该注的一档

timeout / unknown。它们通向结果不明:请求可能已经到达上游、钱可能已经

付出去了,只是回执没回来。把这一档当成失败去退款/解冻,就是既退款又付款

在真实环境里这条路径一年也未必遇到一次 —— 于是它常常是整套接入里唯一

没被测过就上线的分支。请至少在沙盒里走一遍。

一键重置

POST /v1/sandbox/reset 清掉沙盒的调用日志与投递日志。

⚠ 它不种任何固件 —— 重置之后你需要自己重新建会员、上报入金。

不要指望它给你一套现成的测试数据。

建议的联调顺序

1. 换令牌,确认签名串拼对了(错误码是 invalid_signature 而不是 401)

2. POST /v1/members 建一个会员

3. POST /v1/deposits 上报一笔入金 —— 这是唯一能凭空造出余额的口

4. GET /v1/balances 确认八个桶里 available 涨了

5. 跑一条最简单的业务线:POST /v1/exchange/quotesPOST /v1/exchange/orders

6. 配一个 webhook 端点,确认收到 exchange.order.executed验签通过

7. 用 /v1/sandbox/upstream-behavior 注一次 timeout,把你的「结果不明」

分支真的跑一遍;跑完记得复位成 success(或等一小时自动复位)

8. 接了发卡的话:开一张卡、充一笔钱,然后用

POST /v1/sandbox/cards/{id}/simulate 把卡交易推过来 ——

至少跑 auth_ok(消费)、refund(退款)、decline_insufficient(拒付)

三档,再用 repeat: 3 打一轮 chargeback 把卡冻掉

第 6 步不要跳。签名验不过是接入期最常见的一类工单,而它在你自己的环境里

比在我方这边好排查得多。

第 7 步更不要跳。它是这份清单里唯一一步「模拟坏天气」—— 前六步都在验证

一切顺利时你的代码对不对,而真正会把钱弄丢的是不顺利的那几分钟。

卡交易:上游推过来的那一半

第 8 步单独说一句,因为它与前七步的性质不同。

上游故障注入替换的是我方外呼那一跳。而卡消费、退款、撤销、结算、拒付

是上游推给我方的 —— 这条路径上一次外呼都没有,所以注入在这里是

空操作。加上沙盒卡不可能真的刷出一笔消费(没有收单侧),

结果是:你处理卡交易的那一整段代码在上线前一行都没被执行过


# 先看有哪些场景,每一档预期会发生什么
curl https://api-sandbox.zise.com/v1/sandbox/card-scenarios \
  -H 'x-auth-token: Bearer <沙盒令牌>'

# 推一笔 12.99 的消费过来
curl -X POST https://api-sandbox.zise.com/v1/sandbox/cards/crd_<卡 id>/simulate \
  -H 'x-auth-token: Bearer <沙盒令牌>' \
  -H 'x-on-behalf-of: <你的会员号>' \
  -H 'x-idempotency-key: <uuid>' \
  -d '{"scenario":"auth_ok","amount":"12.99","merchant_name":"NETFLIX.COM"}'

载荷会走与生产逐字相同的那条管线(收件箱去重 → 归一化 → 方向判定 →

只有 succeed 才过账 → 账本 → 额度 → 风控计数 → 罚金/冻卡),

跳过的只有验签。

观察结果的地方不是 webhook。 卡消费不外发事件 ——

每笔消费一条会把事件流变成纯粹的调用量放大器,而事件体只带 id 与状态,

你拿到也只能回查。请看这三处:

看哪里看什么
GET /v1/cards/{id}/transactions新增的明细行。必须同时读 direction —— amount 是绝对值,只看金额的话一笔退款与一笔消费长得一模一样
GET /v1/cards/{id}balancelimits 的变化
card.status.updated 事件连打 chargeback 越过阈值把卡冻掉时你会真的收到它。这是这条线上唯一推给你的事件

响应里的 auth_code 是你在流水里认回这一笔的字段。

三件请务必知道的事:

1. repeat 每一笔都换一个交易号。 同号会被收件箱去重挡掉,

而那时你看到的「只记了一次」会被误读成风控没生效。

2. txn_id 会被加上商户前缀再用。 交易号的去重索引是全库唯一的,

允许你自由指定等于允许你占掉别人的号,而被占掉的那一笔会被静默判成重复。

3. 卡内余额不够时你收到的是 insufficient_balance,不是一笔成功授权。

那是对的:真实上游在这一档发的是拒付。要测那一支请用

decline_insufficient 场景。

⚠ 这两个端点同样在 live 上是 404