解码 → 报价 → 支付的完整调用序列,含
processing这一档的处置。
解码、报价与支付
开屏先读配置
GET /v1/qrpay/config
这个会员能不能扫码付、能用哪些币付、每个币还剩多少、限额多少。available: false 时 reason 告诉你为什么,assets 是空数组。
⚠ assets 是服务端过滤后的清单,顺序按该会员自己的支付优先级排 ——第一行就是默认要扣的那个。扣款币种不由你指定:preferred_asset 只是「优先试这个」,合法取值就是这里的 assets[].asset。
⚠ require_step_up 恒为 true:每一笔支付都要终端用户在我方托管屏上确认。提前把那一跳画进收银台流程,别等 POST /v1/qrpay/payments 回 400。
解码
POST /v1/qrpay/decode { "code_value": "<扫到的字符串>" }
返回:这是哪家的码、收款方是谁、金额是固定的还是要用户输入。
认不出就是认不出,返回错误。别在你这边做「模糊匹配」——猜错的代价是钱付给了另一个商户。
报价
POST /v1/qrpay/quotes
按会员的每一种可支付资产各给一份报价。
⚠ 先调这一条做确认页。 直接调支付等于让用户在钱已经锁掉之后
才第一次看见金额。
支付
POST /v1/qrpay/payments
{
"code_value": "<同一张码>",
"currency": "THB", ← 只在自定义金额码上传
"amount": "350.00", ← 同上
"preferred_asset": "USDT" ← 可选
}
⚠ 没有
quote_id,也没有支付授权票据。 这条端点自己会重报一次价再锁钱 —— 所以实扣金额可能与你上一步拿到的报价差几个最小单位,
以支付响应为准。
三个入参的坑
| 入参 | 要点 |
|---|---|
currency / amount | 只在自定义金额码上传,且是收单侧法币。固定金额码上传了等于替商户改价 —— 我方会照传给上游,对不上就被上游拒 |
preferred_asset | 只是「优先试这个」。指定了一个上游这次报不出价的资产会直接拒,不会静默换一个 |
⚠ 扣款币种不由你指定。 我方按该会员自己的支付设置里的优先级挑。
悄悄换个币扣钱是替用户做决定。
processing 不是失败,是「结果不明」
这是这条线上最要紧的一段。
我方通知上游那一步网络层出错时,请求可能已经落地、上游可能已经放行 ——所以我方绝不解冻,落 processing 交给查证与巡检。
⚠⚠ 你必须等 webhook(或按订单号轮询)。
不许自己判失败、不许自动重下一单。 自动重下的后果是同一次消费扣两笔。
三种失败与它们的处置
| 返回 | 意思 | 你该做什么 |
|---|---|---|
| 4xx 业务错误 | 明确失败,钱没动 | 按 code 给用户人话 |
upstream_error | 上游 5xx / 网络层失败 | 这一档还没动钱,可以原样重试 |
| 504 / 网络错误 | 结果不明 | 别当失败 —— 同键重试,或查订单 |
⚠ 最后一种最要紧。
payments的网络层失败不是失败 ——我方可能已经锁了钱并通知了上游。提示用户「支付失败请重试」的表现是他付了两次。
两个金额不是同一个数,也不是同一个币
我方报给上游的金额是上游自己那份报价的原文,不是会员实付总额(实付 = 上游报价 + 我方成本保护 + 溢价 + 手续费,天然更大)。
所以响应里的 customer_total(会员付的数字资产)与acquirer_amount(收款方收到的法币)没有可比性。