Z Zise Developers 简体中文

Decode a payment QR code: read-only, no order or fund hold

POST /v1/qrpay/decode scope: qrpay:write
On behalf of a member · x-on-behalf-of required

Decode the original scanned payload to obtain recipient name, currency, and amount. Read-only: no order, hold, or limit reservation, and therefore no idempotency key required.

⚠ amount_editable is the source of truth for whether the user must enter an amount, based on whether the upstream requires one. Do not infer it from amount == "": that is only a consequence of the same condition, and an upstream placeholder amount for static codes could silently reverse your inference.

⚠ We populate isKYCUser from the member’s actual level; you neither need nor can supply it. The upstream interprets an empty value as verified, so omitting it would falsely attest that an unverified user is verified. This is a compliance issue, not merely a parameter issue.

⚠ Nonempty required_payer_fields means the upstream requires additional payer information with the order. Collect these fields and send them in the payer object to both POST /v1/qrpay/quotes and POST /v1/qrpay/payments, using exactly the supplied keys, for example {"payer": {"custName": "CHAN TAI MAN", "legalId": "A1234567"}}.

Allowed keys: custName · custFirstName · custLastName · custNationCode · gender · mobile · email · legalNationCode · legalType · legalId. Unknown keys are discarded, never forwarded, to prevent an injection channel to the upstream signed with our credentials.

⚠ The upstream validates missing fields; we do not add another payment-time gate. Payment does not decode again merely for validation, which would require another outbound call. Guessing required fields could reject orders the upstream accepts. Use the list returned at this step.

⚠ Decode failures and payment failures are different. No funds have moved here; a network failure is an actual failure and is rejected directly, with no uncertain financial outcome.

Prerequisites

  • QR payments enabled for you and at least one usable upstream available
FieldTypeRequiredDescription
x-on-behalf-of string Required Member scanning the code. Required; the upstream KYC flag uses this member’s actual level.

Request Body

FieldTypeRequiredDescription
code_value string Required Original QR payload: send exactly what was scanned, without preprocessing.

Response

200currency / amount / min_amount / max_amount refer to acquiring-side fiat, the currency ultimately paid to the receiving merchant, not the member’s debit asset. Ask POST /v1/qrpay/quotes for the member debit; do not calculate it from an exchange rate yourself. The debit includes upstream quote + our cost protection + premium + fee, which you cannot reproduce. An empty amount means a custom-amount code; provide the amount at payment.
{
  "payee": "Bangkok Coffee Co.",
  "currency": "THB",
  "amount": "",
  "amount_editable": true,
  "min_amount": "1.00",
  "max_amount": "50000.00",
  "required_payer_fields": []
}
400qr_code_invalid: undecodable code, unsupported standard, or upstream rejection, all sharing one response. · service_unavailable: no usable upstream now. · member_context_required · member_not_found
403insufficient_scope: this key lacks qrpay:write
Request
curl -X POST 'https://api.zinfra.vip/v1/qrpay/decode' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'content-type: application/json' \
  -d '{
    "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
  }'
const res = await fetch("https://api.zinfra.vip/v1/qrpay/decode", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
    "x-on-behalf-of": "$MEMBER_ID",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
  }),
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.post(
    "https://api.zinfra.vip/v1/qrpay/decode",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-on-behalf-of": "$MEMBER_ID",
        "content-type": "application/json",
    },
    json={
      "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zinfra.vip/v1/qrpay/decode",
    strings.NewReader(`{
  "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-on-behalf-of", "$MEMBER_ID")
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/qrpay/decode"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-on-behalf-of", "$MEMBER_ID")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
}
"""))
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/qrpay/decode');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-on-behalf-of: $MEMBER_ID',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "code_value": "00020101021229300012D156000000000510A93FO3230Q..."
}
JSON,
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
200
{
  "payee": "Bangkok Coffee Co.",
  "currency": "THB",
  "amount": "",
  "amount_editable": true,
  "min_amount": "1.00",
  "max_amount": "50000.00",
  "required_payer_fields": []
}