沙盒 · 预置某家上游的返回行为
商户自身
需
x-idempotency-key
设过之后,该上游动钱的那一跳会按你选的那一档返回,
我方随即照生产上逐字相同的分类、重试、留痕与状态机去处置它。
所以你在沙盒里看到的订单状态与 Webhook,就是同一件事在生产上的样子。
五档:success / reject / timeout / unknown / unknown_status。
timeout—— 上游一个字节都没回;unknown—— 上游回了 5xx / 幂等中间件故障;reject—— 上游明确拒绝这一笔(订单判死、冻结释放);unknown_status—— 上游回了个我方枚举里没有的状态
(哨兵值 SANDBOX_UNKNOWN_STATUS),订单挂起并触发我方告警。
timeout 与 unknown 落到同一类处置,这不是重复。 两者在你的日志里
长得完全不同,而处置必须相同:都属于结果不明(钱可能已经付出去了),
都不许当成失败去退款/解冻 —— 判错一次就是「既退款又付款」。
分成两档正是为了让你把这一点验证到。
⚠ 只有动钱的那一跳被替换。 同一家上游的报价 / 解码 / 查询等只读调用
照常打真实上游。这是有意的:扫码付一次支付里 createpay(报价)在前、
paynotify(动钱)在后 —— 全部替换的话订单会在锁钱之前就干净地失败,
而你真正要测的那一支永远走不到。
注入 1 小时后自动回到 success(expires_in: 3600)—— 一个忘了
复位的注入会让你下一次联调拿到一堆莫名其妙的失败。
注入按 (商户, 上游) 隔离,两个沙盒商户之间互不影响;
live 上这个端点是 404。
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-idempotency-key |
string | 必填 | UUID v4 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
upstream |
"remittance" | "card" | "qrpay" | "chain" | 必填 | 哪一条业务线。大小写不敏感;认不出回 invalid_request。
remittance 汇款 · card 发卡 · qrpay 扫码付 ·
chain 链上存提。 |
behavior |
"success" | "reject" | "timeout" | "unknown" | "unknown_status" | 必填 | 预置成什么。unknown = 结果不明(不是失败);
unknown_status = 上游回了一个我方枚举里没有的状态。 |
响应
200已注入
{
"ok": true,
"upstream": "qrpay",
"behavior": "unknown",
"expires_in": 3600
}400
invalid_request body 非 JSON / upstream 或 behavior 不在枚举内404不在沙盒环境
请求
curl -X POST 'https://api.zise.com/v1/sandbox/upstream-behavior' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
-H 'content-type: application/json' \
-d '{
"upstream": "qrpay",
"behavior": "unknown"
}'const res = await fetch("https://api.zise.com/v1/sandbox/upstream-behavior", {
method: "POST",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
body: JSON.stringify({
"upstream": "qrpay",
"behavior": "unknown"
}),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();import requests
res = requests.post(
"https://api.zise.com/v1/sandbox/upstream-behavior",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-idempotency-key": "$IDEMPOTENCY_KEY",
"content-type": "application/json",
},
json={
"upstream": "qrpay",
"behavior": "unknown"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()req, _ := http.NewRequest("POST", "https://api.zise.com/v1/sandbox/upstream-behavior",
strings.NewReader(`{
"upstream": "qrpay",
"behavior": "unknown"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 金额字段用 string 接,不要 float64HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zise.com/v1/sandbox/upstream-behavior"))
.header("x-auth-token", "Bearer $TOKEN")
.header("x-idempotency-key", "$IDEMPOTENCY_KEY")
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"upstream": "qrpay",
"behavior": "unknown"
}
"""))
.build();
// 金额字段用 String / BigDecimal,不要 double$ch = curl_init('https://api.zise.com/v1/sandbox/upstream-behavior');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-auth-token: Bearer $TOKEN',
'x-idempotency-key: $IDEMPOTENCY_KEY',
'content-type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"upstream": "qrpay",
"behavior": "unknown"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
200
{
"ok": true,
"upstream": "qrpay",
"behavior": "unknown",
"expires_in": 3600
}