提现是两段式:提交那一刻钱就从可用余额扣走,之后只有确认或失败两条路。
发起提现
链上出款由你自己执行(merchant_hosted 模型下资金在你手上)。我方做的是扣账 —— 让会员的余额在我方账本上真的减少。
POST /v1/withdrawals ← 扣账(钱立刻从可用扣走)
POST /v1/withdrawals/{id}/confirm ← 你确认链上已出款
POST /v1/withdrawals/{id}/fail ← 你确认出款失败,钱退回
为什么必须先扣账再上链
会员的余额得在我方账本上真的减少,否则同一笔钱能在我方产品上(汇款、兑换、发卡、理财)再花一次 —— 而那时链上那笔已经出去了。
状态机:三档,只有两条出路
┌── confirm ──► settled (终态)
locked ─┤
└── fail ──► failed (终态,钱退回 available)
| 状态 | 钱在哪 | 谁能推进 |
|---|---|---|
locked | member.withdrawing 桶 | 只有你,调 confirm 或 fail |
settled | 已出账,merchant.custody 相应减少 | — 终态 |
failed | 退回 member.available | — 终态 |
⚠ 没有第三条路。钱不会自己从
withdrawing里出来。你不推它,它就永远停在那里 —— 会员看不到、花不了,
而我方运营也碰不到那个桶(它不在解冻操作的范围里)。
这是刻意的:如果它是普通的「冻结」,那么任何一个能解冻的接口都能
把这笔钱放回去,而那笔钱可能已经在链上了。
重复调用与调错方向
| 你调的 | 当前状态 | 结果 |
|---|---|---|
confirm | locked | 200,转 settled |
confirm | 已经是 settled | 200 + replayed: true,幂等回放 |
confirm | 已经是 failed | 400 order_not_cancellable |
fail | 已经是 settled | 400 order_not_cancellable |
| 任意 | 单不存在 / 属于别的商户 | 404 not_found(两种同一响应) |
⚠
fail不是「取消」。它的语义是「我确认链上没出去,请把钱退回」。在你确认之前调它,风险是钱其实已经在链上了,而余额被退了回去 ——
那笔差额没有任何自动化能补回来。
收款地址
to_address 由你在请求体里给。
⚠⚠ 已知偏差,别照着它设计产品。
这个端点目前不查地址簿、不判 24 小时冷静期、不要求强认证 ——
而同一条线的设计纪律是「地址簿是提现主线里唯一的地址来源」。
代码与纪律在这一处不一致,且这个端点后续可能下线
(所有者已决定把会员充值提现从商户面撤掉)。
在那之前请这样写:先调
GET /v1/withdraw-addresses取该会员已备案且
usable=true的地址,把它填进to_address。不要把这里当成「可以填任意地址」的接口用。
地址簿
GET /v1/withdraw-addresses?asset=USDT
POST /v1/withdraw-addresses ← 挂强认证 + 24 小时冷静期
地址是会员的,不是你的 —— 在这个模型下你托管资金,但不代管身份与安全。
| 字段 | 要点 |
|---|---|
usable | 冷静期未过时是 false |
usable_at | 什么时候能用 |
upstream_valid | 1 上游确认可用 · -1 上游没答上来(照常放行并留痕) |
⚠ 冷静期内的地址会列出来,别把它们滤掉。 刚加完地址的用户看不到它
会以为添加失败,然后再加一次。正确做法是列出来 + 置灰 + 显示
usable_at。
⚠ 列表不分页:
next_cursor恒为null、has_more恒为false。那两个字段在只是为了让你的通用翻页器不用为它开特例。
⚠
network过滤大小写敏感、原样匹配(库里存BSC这类大写机读码),而
asset会被转成大写。两个参数规则不一样。
幂等键是唯一的防重保护
POST /v1/withdrawals 的 x-idempotency-key 与入金那条线不同:这里没有 reference 那样的业务流水号。
换了新键重发就是第二次真实扣账。用你系统里那笔提现的唯一标识。
这条线可以被我方停掉
线没开、被人工停售、或你的预付余额掉进低水位触发自动停售时,POST /v1/withdrawals 一律回 400 service_unavailable,不排队 ——它是即时成交线,扣账要么当场成功要么当场失败。
处置:先看 GET /v1/merchant/lines。halted=true 的会随着预付充值在下一轮巡检自己解除;enabled=false 只能找客户经理。
⚠ 已经
locked的单不受影响 ——confirm/fail照常可调。停售的意思是「别再开新的了」,不是把在途的钉死在
withdrawing里。
其它可能的失败
| 码 | 意思 |
|---|---|
asset_not_allowed | 资产不在平台目录里,或已停用 |
insufficient_balance | 会员可用余额不够 |
limit_exceeded | 撞了限额(limit_scope 说明是哪一层) |
request_rejected | 会员被封禁 / 拉黑 / 冻结 |