报价(收银台确认页的数字;建上游待支付单,不锁钱)
x-on-behalf-of
解码只告诉你「要付 250 THB」,这一条告诉你会员的数字资产会被扣掉多少。
没有它就做不出确认页 —— 只能直接调 POST /v1/qrpay/payments,
让用户在钱已经锁掉之后才第一次看见金额。
⚠⚠ 这一步在上游侧已经建了一笔待支付交易,不是免费的只读试算
(POST /v1/remit/quotes 才是)。所以:
- 同一个(会员 +
code_value+currency+amount)在 60 秒内
复用同一笔上游单,不重复外呼;
- 按会员限流 30 次 / 60 秒(与商户级的总量闸叠加,两层都要过);
- 用的是
qrpay:write而不是:read—— 它有上游副作用。
把报价当轮询用会在上游堆出一串永远不会被支付的待支付单,
并把你自己的会员限进 429。一次结账调一次。
⚠ customer_total 是预告不是承诺。 POST /v1/qrpay/payments
会自己重报一次价再锁钱:上游报价带有效期,而用户会在你的确认页上
停留。拿旧报价锁钱的后果是上游按新价结算、我方按旧价扣款。
成交金额一律以 payments 的响应为准,两个数字可能差几个最小单位。
⚠ expires_at 是上游给的报价有效期(空串 = 上游这次没给)。
命中 60 秒备忘时它可能已经临近甚至过去 —— 按这个字段判断,
不要假设「刚拿到的报价一定新鲜」。
⚠ 扣款币种不由你指定。 chosen 是按该会员自己的支付优先级挑出来的
那一个,options 是这次上游报得出价、我方也上架了的全部币种。
options[].asset 恰好就是 payments 的 preferred_asset 的合法取值集
—— 传一个不在这个集合里的资产,payments 会直接拒,不会静默换一个。
⚠ sufficient 是会员的余额够不够付这一单,不是你的预付账户。
这里不下发余额数字:那是 GET /v1/balances(balances:read)的事,
塞进来等于让一把只有 qrpay:write 的 Key 顺带拿到余额读权限。
⚠ 这里也不下发「你的预付够不够」 —— 那个布尔可以被二分反推出我方
给你的批发价,而 /v1/merchant/lines 正是为了不出批发价才那么写的。
你的预付余额在 GET /v1/merchant/balances。
⚠ required_payer_fields 非空的码在开放 API 上走不通(见
POST /v1/qrpay/decode)—— 这一条同样不收付款人字段。
前置条件
- 扫码付这条线已对你开启,且我方至少有一个可用上游
- 这张码的码制我方支持(见
GET /v1/qrpay/schemes)
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 给谁报价。必填 —— 实付金额取决于该会员的支付优先级与余额, 实名位也按他的真实等级填给上游。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code_value |
string | 必填 | 二维码原文。与之后调 payments 时传的那一份必须一致。 |
currency |
string | 可选 | 收单侧法币币种(如 THB)。只在自定义金额码上传
(amount_editable: true),固定金额码上不许传。 |
amount |
string | 可选 | 收单侧法币金额(如 "250.00"),不是数字资产数量。
只在自定义金额码上传。 |
响应
customer_total / fee 是定点十进制串,小数位 = 同一项的
ledger_scale(用它解析,不要用 display_scale —— 后者只决定
收银台上显示几位,拿去解析会掉精度)。
acquirer_currency / acquirer_amount 是收单侧法币那一端,
与 customer_total 不是同一个数、也不是同一个币:会员实付 =
上游报价 + 我方成本保护 + 溢价 + 手续费,天然更大。
blocked_reason 非空 = payments 会以这个码当场拒(超单笔 /
超日累计 / 未达起付额 / 触发实名门槛)。它只针对 chosen
那个币种 —— 换成 options 里的别的币要按那一项自己再判一次。
为空不等于一定成交:payments 还会重报价、再走一遍闸。{
"payee": "Bangkok Coffee Co.",
"acquirer_currency": "THB",
"acquirer_amount": "250.00",
"expires_at": "2026-08-13T09:31:05Z",
"chosen": {
"asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.320000",
"fee": "0.030000",
"sufficient": true
},
"options": [
{
"asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.320000",
"fee": "0.030000",
"sufficient": true
},
{
"asset": "USDC",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.321000",
"fee": "0.030000",
"sufficient": false
}
],
"blocked_reason": ""
}qr_code_invalid 码解不出来 / 上游拒绝 · corridor_not_supported
这条走廊没开通(换十张码也一样,别让用户重扫)·
amount_out_of_range 金额超出这张码允许的范围 ·
kyc_required 触发实名门槛 · insufficient_balance 一个可扣款的
资产都凑不出来(上游给的币我方一个都没上架)·
request_rejected 会员被拉黑 / 账户冻结 / 卡资金保护中 ·
product_not_available 这条线未对你开启 ·
service_unavailable 此刻没有可用上游 · invalid_request ·
member_context_required · member_not_foundinsufficient_scope —— 这把 Key 没有 qrpay:writerate_limited —— 该会员 60 秒内的报价次数用尽(或你整体的写入
配额用尽)。按响应头 Retry-After 退避,不要立刻重试。upstream_error —— 上游 5xx / 网络层失败 / 说不出更细原因的拒绝。
这一档还没动钱(只是没建成待支付单),可以原样重试。curl -X POST 'https://api.zise.com/v1/qrpay/quotes' \
-H 'x-auth-token: Bearer $TOKEN' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'content-type: application/json' \
-d '{
"code_value": "00020101021229300012D156000000000510A93FO3230Q...",
"currency": "THB",
"amount": "250.00"
}'const res = await fetch("https://api.zise.com/v1/qrpay/quotes", {
method: "POST",
headers: {
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
body: JSON.stringify({
"code_value": "00020101021229300012D156000000000510A93FO3230Q...",
"currency": "THB",
"amount": "250.00"
}),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();import requests
res = requests.post(
"https://api.zise.com/v1/qrpay/quotes",
headers={
"x-auth-token": "Bearer $TOKEN",
"x-on-behalf-of": "$MEMBER_ID",
"content-type": "application/json",
},
json={
"code_value": "00020101021229300012D156000000000510A93FO3230Q...",
"currency": "THB",
"amount": "250.00"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()req, _ := http.NewRequest("POST", "https://api.zise.com/v1/qrpay/quotes",
strings.NewReader(`{
"code_value": "00020101021229300012D156000000000510A93FO3230Q...",
"currency": "THB",
"amount": "250.00"
}`))
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)
// 金额字段用 string 接,不要 float64HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.zise.com/v1/qrpay/quotes"))
.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...",
"currency": "THB",
"amount": "250.00"
}
"""))
.build();
// 金额字段用 String / BigDecimal,不要 double$ch = curl_init('https://api.zise.com/v1/qrpay/quotes');
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...",
"currency": "THB",
"amount": "250.00"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
{
"payee": "Bangkok Coffee Co.",
"acquirer_currency": "THB",
"acquirer_amount": "250.00",
"expires_at": "2026-08-13T09:31:05Z",
"chosen": {
"asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.320000",
"fee": "0.030000",
"sufficient": true
},
"options": [
{
"asset": "USDT",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.320000",
"fee": "0.030000",
"sufficient": true
},
{
"asset": "USDC",
"ledger_scale": 6,
"display_scale": 2,
"customer_total": "7.321000",
"fee": "0.030000",
"sufficient": false
}
],
"blocked_reason": ""
}