POST
/v1/remittances
下单(付款重试沿用同一把幂等键)
Scope
remittances:write
代会员调用 · 必带 x-on-behalf-of
需 x-idempotency-key
动钱 · 冻结会员该资产的可用余额(locked_amount,含滑点预留)+ 同步冻结你的预付备付金
这个端点会动钱
失败处置见下方响应表。超时(504)用同一把幂等键重试——我方可能已经处理完;业务失败要换新键,同键会原样返回那次失败。
这是这条线上钱开始动的那一刻。 成功返回意味着:会员的可用余额
已经按 locked_amount 冻进 locked 桶(含滑点预留),你的预付
备付金已经按批发价同步冻结,限额已经预留。
走廊不从请求里读
以收款人自带的那一份为准。少一条可被构造的入参 ——
你传什么走廊我方都不看。
只有「收款人收多少」这一个方向
下单没有 source_amount。理由是上游那一侧 payout_amount
是定死边,必须逐字等于报价的买入额。要按「我出多少」下单,
先用 POST /v1/remit/quotes 反算出 payout_amount,再拿它下单。
幂等键 = 一次「确认」一把
付款重试沿用同一把键(拿回首次那笔,duplicated: true)。
换新键 == 第二笔真实付款。收到 504/网络错误时必须同键重试,
别新生成 —— 我方可能已经建单并冻了钱。
三种成功状态,处置完全不同
dispatching已放行,分发执行器在跑;reviewing/platform_reviewing等我方人工审核
(商户单一律先过一遍总后台,这是制度性关卡,不是出了问题);
pending= 内部的pending_merchant_funds:你的预付余额不足。
此时会员的钱一分未动、限额未预留、上游没有订单,
只有一张排队的订单行。去充预付,我方会自动推进它。
队列深度看 GET /v1/merchant/pending。
⚠ 会员侧永远看不到 pending 的原因。 你的界面上对终端用户
只能说「处理中」,不能说「商户余额不足」。
⚠ duplicated: true 时没有 quote(这一次没有真的报价),
status 是那张既有订单此刻的状态 —— 可能已经是 completed 了。
别把它当成「刚刚下单成功」。
前置条件
- 收款人在这个会员名下且状态可用
- 会员该资产可用余额 ≥ locked_amount(= 客户实付 × (1 + 滑点))
- 会员已过这条线要求的 KYC 层级,且未被封禁/拉黑
- 金额在最低起汇额、单笔上限、日限额之内
- purpose_code 在这条产品线允许的用途码清单内
- 个人线(pobo)还要求这个会员已有一个 active 的上游子账户
请求头
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
x-on-behalf-of |
string | 必填 | 代哪个会员调用。钱从他的余额里扣。 |
x-idempotency-key |
string | 必填 | UUID v4。这把键就是「一笔付款」的身份 —— 重试用同一把,新的一笔换新的一把。24 小时后同键视为新请求。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
payee_id |
string | 必填 | 收款人号。pye_ 前缀可带可不带;剥掉后必须是纯数字。 |
payout_amount |
string | 必填 | 收款人收多少(目标币)。位数必须与该币种一致 ——
多给一位在下单这一刻就被拒(这道校验刻意前移到冻结之前:
晚一步的话,钱已经冻上、订单卡在 dispatching,
而那个状态不解冻也不可取消)。最长 32 字符。GBP 为 2 位:"380.00";JPY 为 0 位:"50000" |
slippage_bps |
integer | 必填 | 滑点上限,基点。必填整数,50 ~ 1000(0.5% ~ 10%)。
成交价相对下单快照上浮超过它时,订单停在 needs_reconfirm
等用户决定,而不是默默多扣。它同时决定冻多少:
locked_amount = customer_total × (1 + slippage_bps/10000)。 |
purpose_code |
string | 必填 | 汇款用途。必填,取值来自我方给你的用途码清单
(上游固定枚举 23 个,如 FAMILY_SUPPORT /
GOODS_PURCHASED / EDUCATION_TRAINING)。
留空或写错在下单这一刻被拒 —— 上游那个字段是 required,
漏到分发那一步就是钱冻完了才被打回来。 |
reference |
string | 可选 | 附言,会带给收款方。超过 200 字符截断。 |
line |
"express" | "pobo" | 可选 | 缺省 express;认不出的值静默按 express 处理。 |
asset |
string | 可选 | 从哪个资产扣款。缺省 USDT。 |
响应
201已受理。幂等命中也是 201(带
duplicated: true、quote: null)——
这个端点不用 200 区分首次与重放,判据是 duplicated
和响应头 X-Idempotent-Replay。{
"id": "rmt_9c1f0a7e-3b2d-4f81-9a55-1d2e3f4a5b6c",
"status": "reviewing",
"quote": {
"source_asset": "USDT",
"customer_total": "499.980000",
"fee": "2.480000",
"locked_amount": "504.979800",
"indicative_rate": "1.2899",
"applied_rate": "1.2743"
}
}400
invalid_request 请求体不是合法 JSON,或 payee_id 不是数字。
resource_not_found 收款人不在这个会员名下。
corridor_not_supported 收款人所在走廊已不可用(下架 / 受限辖区)。
product_not_available 业务线未开通,或源资产不可用。
invalid_fields payout_amount 位数与币种不符 / 超长 / 算不出来。
request_rejected 风控拒绝。不带原因、不带规则名、不带评分
(判据永不出境)。别重试,走商户后台联系我方。
service_unavailable 你的预付余额不足且这条线不排队时的对外形态。
汇款线正常情况下走排队(201 + status: pending)。409
idempotency_key_reused 同键换了请求体 —— 我方拒绝,
因为那意味着你有两笔不同的付款用了同一个身份。
idempotency_in_progress 首次请求还在处理中,稍后同键重试。500⚠ 当前实现的一处缺陷,别把它当契约。 下列业务拒绝目前没有
登记进对外码目录,于是以
api_error(500)返回:
会员可用余额不足、超日限额、超单笔上限、低于最低起汇额、
KYC 层级不足、用途码非法、slippage_bps 缺失或越界、
账户被封禁/拉黑、个人线缺子账户。
对这些 500 重试是没有意义的(同键会一直失败),
请先用 POST /v1/remit/quotes 把金额与最低额那一档挡在前面,
其余带 request_id 报给我方。修复后它们会变成上面那些 400 码。触发的事件
调用样例
curl -X POST 'https://api.zise.com/v1/remittances' \
-H 'authorization: Bearer $TOKEN' \
-H 'x-zise-merchant: $MERCHANT_ID' \
-H 'x-on-behalf-of: $MEMBER_ID' \
-H 'x-idempotency-key: $(uuidgen)' \
-H 'content-type: application/json' \
-d '{
"payee_id": "pye_1042",
"payout_amount": "380.00",
"slippage_bps": 100,
"purpose_code": "FAMILY_SUPPORT",
"reference": "Rent Aug",
"line": "express",
"asset": "USDT"
}'
const res = await fetch(
"https://api.zise.com/v1/remittances",
{
method: "POST",
headers: {
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "MERCHANT_ID",
"x-on-behalf-of": "MEMBER_ID",
"x-idempotency-key": "crypto.randomUUID()",
"content-type": "application/json"
},
body: JSON.stringify({
"payee_id": "pye_1042",
"payout_amount": "380.00",
"slippage_bps": 100,
"purpose_code": "FAMILY_SUPPORT",
"reference": "Rent Aug",
"line": "express",
"asset": "USDT"
}),
},
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests
res = requests.post(
"https://api.zise.com/v1/remittances",
headers={
"authorization": "Bearer $TOKEN",
"x-zise-merchant": "$MERCHANT_ID",
"x-on-behalf-of": "$MEMBER_ID",
"x-idempotency-key": "$(uuidgen)",
"content-type": "application/json"
},
json={
"payee_id": "pye_1042",
"payout_amount": "380.00",
"slippage_bps": 100,
"purpose_code": "FAMILY_SUPPORT",
"reference": "Rent Aug",
"line": "express",
"asset": "USDT"
},
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()