出款 — 出款未成功时 Liqfy 返回什么
本文档完整定义了出款失败约定:你会收到什么、何时收到,以及每种情况该如何处理。 实现一次即可,之后应无需再改动。
出款可能在两个时间点失败,且两者的行为差异很大。不要在同一代码路径中处理它们。 第三种情况较少见:出款已支付后又被收款机构退回(见第 4 节)。
#1. 立即拒绝 — 你的账户余额不足
同步发生,在 POST /v1/withdrawals 响应中返回。不会创建出款,钱不会离开你的钱包,你的 idempotencyKey 也不会被消耗。
POST /v1/withdrawals
HTTP/1.1 400 Bad Request
{
"error": {
"type": "invalid_request_error",
"code": "INSUFFICIENT_BALANCE",
"message": "INSUFFICIENT_BALANCE",
"details": { "available": 1000, "requested": 100005, "currency": "BRL" }
},
"request_id": "req_..."
}根据 error.code 进行分支。message 用于你的日志;代码才是约定。
| 字段 | 含义 |
|---|---|
error.code | 此处始终为 INSUFFICIENT_BALANCE。稳定常量。 |
details.available | 可用余额,以该币种的最小单位表示(BRL 为分)。 |
details.requested | 将从钱包扣除的金额:amount + 手续费。 不是你发送的 amount。 |
details.currency | 被评估钱包的 ISO 4217 币种代码。 |
#能帮你省掉一张支持工单的三件事
手续费是额外加收的。 要提取 amount,钱包需要
amount + fee(amountType 默认为 "gross")。这就是 requested
已预先求和的原因 — 否则你会看到“请求 1,000,余额 1,000”,拒绝看起来就像是错的。
(旧版 amountType: "net" 模式在 POST /v1/withdrawals 上仍被接受 — 参见 创建出款:
收款人收到 amount − fee,此时 requested 就是 amount。
CRYPTO 提现不接受该模式。)
只计算 available。 它是可用余额。pending(已结算但仍被冻结)不计入出款,
也永远不能用来支付出款。如果账户在同一币种下有多个钱包,available 是
单个钱包的最大余额,而非总和 — 预留金额从一个钱包中扣除。
你可以通过可选字段 walletId 指定钱包;若省略,则在该币种的默认钱包有余额时使用默认钱包,
否则使用任意余额充足的可用钱包。
拒绝不会消耗 idempotencyKey。 你可以充值后使用同一个 key 重新发送;出款会被正常处理。
(常规拒绝本来就是如此;自 2026-08-19 起,对于余额在检查与预留之间消失的罕见竞态情况,也同样适用。)
#如何完全避免遇到这种情况
GET /v1/withdrawals/payout-info?currency= — 无副作用,专为此设计。返回 { currency, available, pendingAdjustment, fee: { fixed, variablePct, minimum }, maxWithdrawable }: available 是可用余额,
fee 是当日适用的参数集,maxWithdrawable 是在已计入手续费的情况下
你可请求的最大总金额 — 正是“全部提现”按钮该用的数字。完整约定与示例:
报价、手续费与可提现余额。
#其他立即拒绝
同样的规则适用于所有同步 4xx:不会创建任何内容,也不会从钱包扣款。
| HTTP | 代码 / 消息 | 何时 |
|---|---|---|
| 400 | 校验错误 | 请求体不符合 schema(例如 type 不在 PIX、TED、CRYPTO 之内;amount 不是正整数)。 |
| 400 | Idempotency-Key must be at most 255 characters | Idempotency-Key 请求头超过 255 个字符。 |
| 4xx | TENANT_KYC_REQUIRED | 账户的注册审核(KYC)未通过。 |
| 4xx | 提现规则拒绝 | 账户配置的某条提现规则拒绝了该请求。拒绝信息中可能包含 ruleId 和 reasonKey(withdrawal.rule.pixKeyTypeNotAllowed 或 withdrawal.rule.notAllowed)。 |
| 403 | CRYPTO_PANEL_ONLY | 使用 API key 发送的 CRYPTO 提现 — 加密货币仅可通过控制台操作。 |
| 400 | INVALID_CRYPTO_WITHDRAWAL | 不符合规则的 CRYPTO 提现(仅限 BRL,最低 R$ 5.000,00,不支持 net,必须提供钱包地址和估算)。 |
每个 4xx 拒绝都会留下记录。 记录内容包括金额、原因和来源(API 或控制台),
并显示在提现页面和审计日志(WITHDRAWAL_REJECTED)中。使用相同幂等键和相同代码重复的同一请求,
每小时最多记录一次。5xx 是我们的故障,而非拒绝,不会以这种方式记录。
#2. 延后拒绝 — 出款已被接受但在处理中失败
此时 POST 返回了 201 及 status: "PENDING",并且钱已经离开你的钱包(处于预留状态)。
结果稍后通过两种方式送达:
- 通过
payout.failedwebhook; - 或通过轮询
GET /v1/withdrawals/{id}(po_...或纯 id)。列表GET /v1/withdrawals?status=failed接受与payout.*webhook 相同的小写词:pending、processing、paid、failed(failed=FAILED、REJECTED和CANCELLED)。未知值返回 400。
关键字段:
| 字段 | 含义 |
|---|---|
status | FAILED、REJECTED 或 CANCELLED(规范 payout 上为 failed) |
failure_code | 稳定、小写、机器可读的代码。 请根据它进行流程分支。出款为 FAILED 时出现。 |
failure_message | 与该代码对应的中性葡萄牙语句子。可安全展示给你的终端用户。 |
rejectionReason | 用于日志/支持的自由文本(旧版 webhook 上为 reason)。在 FAILED 上,该值就是公开的 failure_message(自 2026-10-21 起);在 REJECTED/CANCELLED 上,它仍可能包含收款机构的技术文本。不要针对它编写 if 判断。 |
legs[].failureCode | 仅限拆分出款:每个部分的原因。封闭词表:INVALID_PIX_KEY、ACCOUNT_CLOSED、ACCOUNT_BLOCKED、KYC_REJECTED、LIMIT_REJECTED、DESTINATION_UNAVAILABLE、DESTINATION_NOT_ALLOWED、INSUFFICIENT_BALANCE、TIMEOUT、PROVIDER_CANCELLED、PROVIDER_REJECTED、UNKNOWN。 |
各字段出现的位置:
| 接口面 | failure_code / failure_message | 自由文本 |
|---|---|---|
GET /v1/withdrawals/{id}(及列表) | 顶层 failure_code、failure_message | rejectionReason |
payout.failed | data.object.failure_code、data.object.failure_message | 从不 |
{
"id": "evt_...",
"object": "event",
"type": "payout.failed",
"data": {
"object": {
"id": "po_...",
"object": "payout",
"status": "failed",
"failure_code": "destination_unavailable",
"failure_message": "O banco de destino não confirmou o PIX a tempo. Tente novamente mais tarde."
}
}
}#failure_code 取值
failure_code | failure_message | 值得重试吗? |
|---|---|---|
invalid_pix_key | A chave PIX de destino é inválida ou não foi encontrada. | 仅在更换 key 后 |
account_blocked | A conta de destino está bloqueada para receber PIX. | 仅在更换账户后 |
account_closed | A conta de destino está encerrada ou inativa. | 仅在更换账户后 |
limit_rejected | O valor excede um limite permitido para esta transferência. | 是,降低金额后 |
kyc_rejected | A transferência foi recusada na verificação cadastral do destinatário. | 否 |
destination_unavailable | O banco de destino não confirmou o PIX a tempo. Tente novamente mais tarde. | 是,稍后重试:收款银行未及时结算 |
destination_not_allowed | O provedor não aceita esse tipo de destino; use outra chave Pix. | 仅在更换 key 类型或收款目标后 |
payout_cancelled | A transferência foi cancelada antes de ser concluída. | 是 |
provider_rejected | A transferência PIX foi recusada e não foi concluída. | 是,但请先调查 |
provider_rejected 是通用代码:凡是我们无法更精确归因的情况都归入此类。将未知的 failure_code 视为 provider_rejected
— 可能会新增代码,但绝不会重命名或删除。
经验法则: FAILED 状态的出款是最终结果,钱会自动退回你的钱包;你无需发起任何请求。
payout.failed 何时到达取决于一项账户设置:
- 仅最终失败(如果你在收到
payout.failed时为用户退款,推荐使用):每个payout.failed到达时退款都已入账,因此收到后即可为用户退款。如果退款需要与银行确认,payout.failed会等到退款入账后再发送;如果 PIX 实际已发出,则会收到payout.paid。请联系支持团队启用。 - 默认:
payout.failed会立即发送,此时退款几乎总是已经入账。当无法证明该 PIX 从未发出时, 退款会被暂扣,待与银行确认:failure_message为 "Estamos confirmando com o banco; você será avisado.",legs[].refunded为false。 此时请勿为用户退款:请查询GET /v1/withdrawals/{id}或钱包流水,直到退款入账。 退款释放时不会再发送 webhook。
(failure_message 并不承诺退款 — 钱包流水才是依据。)
#拆分为多笔的出款
每笔 PIX 都有上限(默认 R$ 15.000,00)。超过上限的出款会被拆分为多笔 PIX 转账 — 即分笔(legs) — 每笔填满至上限,余数放在最后一笔(R$ 110.000,00 → 7 × R$ 15.000,00 + 1 × R$ 5.000,00)。 每笔都有自己的 E2E,且可能单独失败。
当部分分笔已支付而其他分笔失败时,出款最终状态为
PARTIALLY_COMPLETED — 在公开词表中为 paid — 失败分笔的金额会在退款入账后回到你的钱包。
查看 legs[] 以准确了解发生了什么:
| 字段 | 含义 |
|---|---|
legIndex | 分笔编号,从 1 开始。 |
amount | 分笔金额,单位为分。 |
status | PENDING、PROCESSING、COMPLETED、FAILED 或 REVERSED。 |
endToEndId | 该分笔的 E2E,用于与银行对账单对账。 |
failureCode | 见上文封闭词表;分笔未失败时为 null。 |
refunded | 仅限 FAILED 分笔:其退款已入账钱包时为 true。 |
reversalCredited | 仅限 REVERSED 分笔:退回金额已入账给你时为 true(第 4 节)。 |
reversalEndToEndId, reversedAmount, reversedAt | REVERSED 分笔上的退回数据。 |
被重新发送所替代的分笔永远不会出现在 legs[] 中;其金额已包含在替代它的分笔中。
#取消仍处于 PENDING 状态的出款
当出款处于 PENDING 状态(尚未发送任何内容 — 例如正在等待审批)时,你可以自行取消:
POST /v1/withdrawals/{id}/cancel
{ "reason": "optional, up to 500 characters" }返回 200 及状态为 CANCELLED 的出款,金额退回余额。不处于 PENDING 状态时返回 409 WITHDRAWAL_NOT_CANCELLABLE。
在控制台中,只有账户所有者或管理员可以取消。
#3. 处理方流动性
当负责发送 PIX 的处理方没有余额支付时,这不是你的问题。目前该出款会以 FAILED 及
failure_code: "provider_rejected" 的形式返回给你,钱会在钱包流水入账后自动退回你的钱包
(此处同样适用暂扣退款 — 见上文经验法则)。没有自动重试,也没有等待窗口:
如有需要,可以重新请求出款 — 最好稍后再试。
该怎么做: 当出款处于 PROCESSING 状态时,不要重复请求。新的 POST 会创建第二笔出款并再次从钱包扣款。
向你的终端用户显示类似“付款处理中”的信息,并等待最终 webhook。
当 PIX 发送结果尚不明确时,PROCESSING 可能比平常持续更久。此时出款会保持 PROCESSING 状态,
同时 Liqfy 与提供方确认结果:它绝不会仅因时间流逝而被标记为失败并退款
— 只有在有确凿证据表明 PIX 未发出时才会。
#4. 已支付,随后被退回(REVERSED)
已支付的 PIX 可能被收款机构退回。此时该出款(或分笔)变为 REVERSED。
该状态在公开的 payout.* 词表中没有对应词:要列出它,请使用 ?status=REVERSED 进行筛选。
退回的钱不会直接进入你的余额。在提供方对账单中确认退回后,它会进入 Liqfy 冲正箱,
仅当品牌政策允许且该退回未被标记为欺诈或可疑时才会自动入账给你;否则由人工决定
— 入账给你或予以保留。在此之前,legs[].reversalCredited 为 false:
将其视为“尚未回到你的余额”。
#实现总结
POST /v1/withdrawals
├── 400 INSUFFICIENT_BALANCE ....... your wallet doesn't cover it. Nothing created.
│ → check /v1/withdrawals/payout-info first
├── other 4xx ...................... refused at entry. Nothing created.
└── 201 PENDING .................... accepted, funds reserved
├── still PENDING .............. you can cancel: POST /v1/withdrawals/{id}/cancel
├── stays PROCESSING ........... do NOT retry. Wait for the webhook.
├── PARTIALLY_COMPLETED (paid) . some legs paid, failed legs refunded
│ (legs[].refunded tells which already landed)
├── REVERSED ................... paid and returned; credit only when
│ legs[].reversalCredited is true
└── webhook payout.failed
└── failure_code = invalid_pix_key | account_* | limit_rejected |
kyc_rejected | destination_unavailable | destination_not_allowed |
payout_cancelled |
provider_rejected (generic / unknown)
→ definitive; the refund is automatic once the wallet
movement lands. If failure_message is "Estamos confirmando
com o banco; você será avisado." the refund is held
(legs[].refunded is false): check the payout or the
statement until it lands. Does not happen with
"final failures only".
Retry only after fixing the cause (e.g. the PIX key), or later
for destination_unavailable.幂等性: 在 POST /v1/withdrawals 上始终发送 Idempotency-Key 请求头
(请求体字段 idempotencyKey 为后备;以请求头为准;最多 255 个字符)。它能在任何网络重试时防止重复出款
— 使用相同 key 重新发送会返回原始出款,而不会创建新的出款。如果请求超时且你不确定出款是否已创建,
可按 key 查询:GET /v1/withdrawals/by-idempotency-key/{key} or GET /v1/withdrawals?idempotency_key={key}。