Z Zise Developers 简体中文

Sandbox: inject an upstream response behavior

POST /v1/sandbox/upstream-behavior scope: merchant:read
Merchant account Requires x-idempotency-key

After configuration, the upstream's money-moving call returns the selected behavior. We then apply the exact production classification, retry, audit, and state-machine handling. The order states and webhooks observed in the sandbox therefore match what that situation would produce in production.

Five behaviors: success / reject / timeout / unknown / unknown_status.

  • timeout: the upstream returned no bytes.
  • unknown: the upstream returned 5xx or its idempotency middleware failed.
  • reject: the upstream explicitly rejected the transaction; the order fails and holds are released.
  • unknown_status: the upstream returned a status outside our enum (sentinel SANDBOX_UNKNOWN_STATUS); the order is suspended and an internal alert is raised.

timeout and unknown receive the same handling; this is not redundant. They look very different in logs, but both are unknown outcomes: money may already have been sent. Neither may be treated as a definite failure followed by refund/release; one wrong decision creates both a payment and a refund. Separate scenarios let you verify that rule.

⚠ Only the money-moving call is replaced. Read-only quote / decode / query calls to the same upstream still reach the real upstream. This is deliberate: QR payments call createpay for pricing before paynotify moves money. Replacing all calls would produce a clean failure before funds were locked, never exercising the branch you actually need to test.

Injection automatically returns to success after 1 hour (expires_in: 3600). An injection left enabled would otherwise cause unexplained failures in the next integration session.

Injection is isolated by merchant and upstream, so sandbox merchants do not affect one another. This endpoint returns 404 in live mode.

FieldTypeRequiredDescription
x-idempotency-key string Required UUID v4

Request Body

FieldTypeRequiredDescription
upstream "remittance" | "card" | "qrpay" | "chain" Required Business line, case-insensitive. Unrecognized values return invalid_request. remittance: remittances · card: card issuing · qrpay: QR payments · chain: on-chain deposits and withdrawals.
behavior "success" | "reject" | "timeout" | "unknown" | "unknown_status" Required Behavior to inject. unknown means an unknown outcome, not failure. unknown_status means the upstream returned a status outside our enum.

Response

200Injected
{
  "ok": true,
  "upstream": "qrpay",
  "behavior": "unknown",
  "expires_in": 3600
}
400invalid_request: body is not JSON, or upstream/behavior is outside the enum.
404Not in the sandbox environment.
Request
curl -X POST 'https://api.zinfra.vip/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.zinfra.vip/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"
  }),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/sandbox/upstream-behavior",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "upstream": "qrpay",
      "behavior": "unknown"
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/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)
// Decode amount fields as string, not float64.
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zinfra.vip/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();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/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);
// Use bcmath / strings for amounts, not floatval.
200
{
  "ok": true,
  "upstream": "qrpay",
  "behavior": "unknown",
  "expires_in": 3600
}