> ## Documentation Index
> Fetch the complete documentation index at: https://docs.velafi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 从余额支付本地货币

> 从商户的法币余额向已保存的收款人支付本地货币，通过 FUNDING_WEBHOOK 追踪出金，并在您的商户之间划转余额。

使用此流程从商户的法币余额（也称为待处理资金 / Pending Fund 余额）向您已保存为收款人的银行账户或 Pix 密钥支付本地货币（MXN、ARS、COP、BRL、PEN）。您调用 `POST /v2/merchant/claimfunds`，VelaFi 从余额中扣款并通过本地支付通道发出付款，随后通过 `FUNDING_WEBHOOK` 告知您付款是否完成。同一余额也可以通过 `POST /v2/merchant/transfer` 划转给您的另一个商户。

余额通常来自充值。参见[使用虚拟账户收取本地付款](https://docs.velafi.com/cn/docs/use-cases/collect-with-virtual-accounts.md)。

## 工作原理

```mermaid theme={null}
sequenceDiagram
    participant You as 您的平台
    participant V as VelaFi
    participant B as 收款银行
    You->>V: POST /v2/payments (保存收款人)
    V-->>You: id (userPaymentId)
    You->>V: POST /v2/merchant/claimfunds (clientId, amount, userPaymentId)
    V->>V: 从余额中扣除 amount + fee
    V-->>You: txId, fee, totalAmount
    V->>B: 本地付款 (SPEI, CVU, Pix, Bre-B...)
    V->>You: FUNDING_WEBHOOK (type WITHDRAW, status 2 或 3)
    You-->>V: HTTP 200 {"callbackStatus":"SUCCESS"}
```

## 开始之前

* **API 访问权限。** 一个 API 密钥、已加入白名单的服务器 IP，以及放在 `X-BH-TOKEN` 请求头中的业务令牌。参见[认证](https://docs.velafi.com/cn/api-reference/authentication.md)。
* **一个已验证的商户，且在支持出金的渠道上拥有已激活的币种账户**（见下文）。参见[激活商户账户](https://docs.velafi.com/cn/docs/guides/activating-merchant-account.md)。
* **该币种账户上有余额**，来自同一渠道的充值。参见[使用虚拟账户收取本地付款](https://docs.velafi.com/cn/docs/use-cases/collect-with-virtual-accounts.md)。
* **一个 `FUNDING_WEBHOOK` 端点**，已注册并能返回所要求的确认响应。参见 [Webhook 设置](https://docs.velafi.com/cn/api-reference/webhooks/webhook-setup.md)。

### 支持从余额出金的渠道

| 币种 | 渠道 | 收款人 `paymentId` |
| - | - | - |
| MXN | SPEI (Finco Pay) | `105` |
| MXN | SPEI (Tesored) | `72` |
| ARS | CBU/CVU (Momentum) | `63` |
| COP | COP Account (Cobre) | `68`、`69` |
| BRL | Pix (Genial) | `90` |
| BRL | Pix - Woovi (Avenia) | `92` |
| PEN | Bank Transfer | `95`（最低出金金额 10 PEN） |

每笔出金都适用以下三条规则：

1. **同一渠道。** 出金通过持有该余额的币种账户所在渠道发出。只有通过该渠道（或通过 VelaFi Pending Fund）入账的余额才能在该渠道出金。
2. **同一商户币种账户。** 收款人必须保存在同一商户下，且币种和渠道与该账户相同。保存在其他商户下、或属于其他渠道的收款人（例如使用 Tesored 收款人对应 Finco Pay 余额）会被拒绝。
3. **金额加手续费。** 余额必须足以覆盖 `amount` 加出金手续费。

## 分步操作

**1. 保存收款人**

通过 [`GET /v2/payments/templates`](https://docs.velafi.com/cn/api-reference/beneficiary/get-payment-templates) 获取该渠道的字段模板，然后通过 [`POST /v2/payments`](https://docs.velafi.com/cn/api-reference/beneficiary/add-beneficiary) 在持有余额的商户下保存目标账户。各渠道的模板参见[管理收款人](https://docs.velafi.com/cn/docs/guides/managing-beneficiaries.md)。

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/payments \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "paymentId": 105,
    "country": "Mexico",
    "fiat": "MXN",
    "fieldJson": {
      "Account Type": "clabe",
      "Account Number": "000000000000000001",
      "Beneficiary Name": "Example Beneficiary",
      "Bank Code": ""
    }
  }'
```

保存返回的 `id`：它就是出金所用的 `userPaymentId`。只向 `status` 为 `1`（有效）的收款人出金。如果该账号已被保存，响应中的 `status` 为 `3` 并带有 `sameAccountNumberId`；请使用该 ID，而不要新建记录。

**2. 检查余额**

通过 [`GET /v2/merchant/accounts`](https://docs.velafi.com/cn/api-reference/merchant/get-a-list-of-merchant-accounts) 读取该商户、币种和渠道对应的 `balance`。请为手续费预留空间。

**3. 创建出金**

调用 [`POST /v2/merchant/claimfunds`](https://docs.velafi.com/cn/api-reference/merchant/claim-pending-fund)。务必传入您自己的唯一 `clientId`。

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/merchant/claimfunds \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": 100001,
    "clientId": "payout-0001",
    "fiat": "MXN",
    "amount": 1000,
    "userPaymentId": 456
  }'
```

```json Response theme={null}
{
  "code": 200,
  "msg": "SUCCESS",
  "data": {
    "txId": 1123123123,
    "merchantId": 100001,
    "clientId": "payout-0001",
    "fiat": "MXN",
    "totalAmount": 1010,
    "amount": 1000,
    "fee": 10,
    "userPaymentId": 456,
    "createTime": "1737452292000"
  }
}
```

`amount` 是收款人收到的金额。`fee` 是为该渠道和您的账户设定的出金手续费，调用成功时会从余额中扣除 `totalAmount`（`amount` + `fee`）。金额会按该币种的精度向下取整。`200` 响应表示出金已被受理，而不表示资金已到账：在 Webhook 或资金记录显示其他状态之前，出金都处于待处理状态。

**4. 等待结果**

处理该 `txId` 对应的 `type` 为 `WITHDRAW` 的 `FUNDING_WEBHOOK`（参见[需要处理的 Webhook](#webhooks-to-handle)）。如果没有收到，请使用 `txId` 或 `clientId` 通过 [`GET /v2/merchant/funding/records`](https://docs.velafi.com/cn/api-reference/merchant/retrieve-funding-records) 读取该记录。

## 出金状态

| 状态 | 含义 | 余额 |
| - | - | - |
| `1` 待处理 | 已受理，正在发送。刚创建的记录可能短暂显示为 `0`；按 `status=1` 筛选会同时返回这两种。 | `totalAmount` 被占用（已扣除）。 |
| `2` 已完成 | 渠道报告该出金已支付。 | 保持已扣除状态。 |
| `3` 已取消 | 出金在完成前失败或被拒绝。 | `totalAmount`（含手续费）退回余额。法币流水中会出现一条 `WITHDRAW_ROLLBACK` 记录。 |
| `4` 已退款 | 已完成的出金被退回 VelaFi 并冲正。 | `totalAmount` 退回余额，并附带一条 `WITHDRAW_ROLLBACK` 记录。 |

`FUNDING_WEBHOOK` 会针对 `2`、`3` 和 `4` 发送，待处理状态不会发送。

## 需要处理的 Webhook

出金事件是 `type` 为 `WITHDRAW` 的 `FUNDING_WEBHOOK`。提现的 `totalAmount` 为负数。

```json FUNDING_WEBHOOK (WITHDRAW, canceled) theme={null}
{
  "eventType": "FUNDING_WEBHOOK",
  "txId": "1123123123",
  "clientId": "payout-0001",
  "merchantId": "100001",
  "fiat": "MXN",
  "totalAmount": "-1010",
  "amount": "1000",
  "fee": "10",
  "userPaymentId": "456",
  "type": "WITHDRAW",
  "status": "3",
  "failCode": "...",
  "failReason": "...",
  "bankOrderId": "...",
  "createTime": "1737452292000",
  "updateTime": "1737452350000",
  "timestamp": "1737452350512"
}
```

* 通过 `clientId` 或 `txId` 将事件与您的出金匹配。
* 基于 `eventType` + `txId` + `status` 实现幂等处理。同一事件可能到达不止一次，并且出金之后可能从 `2` 变为 `4`，因此请处理完成后又发生退款的情况。
* `failCode` 和 `failReason` 仅在已取消且有记录原因的出金中发送。很多失败并不附带原因；请勿依赖它们来决定后续操作。
* `bankOrderId` 是该转账在支付服务商处的参考号（如有）。请保存以备支持请求使用。
* 快速返回 HTTP `200` 和 `{"callbackStatus":"SUCCESS"}`。VelaFi 最多等待 60 秒；超时或任何其他响应都视为推送失败。参见[推送与重试](https://docs.velafi.com/cn/api-reference/webhooks/webhook-setup.md#delivery-and-retries)。

完整字段列表见[资金记录事件](https://docs.velafi.com/cn/api-reference/webhooks/funding-records-message-push.md)。

## 处理失败

### 创建出金时的错误

如果 `claimfunds` 返回错误，则不会创建资金记录，也不会扣款。常见错误码（参见[错误码](https://docs.velafi.com/cn/docs/error-codes.md)）：

| 错误码 | 原因 | 处理方式 |
| - | - | - |
| `-1131` | 该渠道的余额不足以覆盖 `amount` + `fee`。 | 等待充值，或降低金额。 |
| `-1174` | 该 `clientId` 已被您账户下的某条资金记录使用。 | 该出金已存在：请通过 `clientId` 查询。不要使用相同 ID 再创建一笔。 |
| `-4027` | `userPaymentId` 不存在或属于其他账户。 | 检查收款人 ID。 |
| `-5017` | 收款人无效（状态不为 `1`）。 | 使用有效的收款人，或重新保存。 |
| `-4001` | 请求未通过校验，例如商户在该渠道上的币种账户不支持出金或未激活。 | 对照上表检查渠道，并检查账户状态。 |
| `-1181` | 该商户的出金已被禁用。 | 联系 VelaFi 支持团队。 |
| `-5051` | 收款人的币种与 `fiat` 不一致。 | 使用相同币种的收款人。 |
| `-5070` | 收款人不属于该商户在该渠道上的币种账户。 | 在与余额相同的商户和渠道下保存收款人。 |
| `-5030` | 出金金额超出账户的提现限额。 | 联系 VelaFi 审核限额。 |
| `-5050` | 金额低于最低限额（PEN 为 10）。 | 提高金额。 |
| `-1146` | 同一商户和币种的另一笔出金或划转正在处理中。 | 稍等片刻后使用相同的 `clientId` 重试。 |

### 超时与重试

只要您复用相同的 `clientId`，`clientId` 校验就能确保重试是安全的：

1. 如果调用超时或返回网络错误，不要生成新的 `clientId`。通过 `GET /v2/merchant/funding/records?clientId=...` 查询该出金。
2. 如果存在记录，说明出金已创建：跟踪其状态。
3. 如果不存在，使用相同的 `clientId` 再次发送相同请求。如果第一次请求实际上已成功，您会收到 `-1174`，而不会产生第二笔出金。

重复的 `clientId` 会返回 `-1174` 错误，而不是原出金。请从资金记录中读取原出金。

### 失败的出金

当出金最终状态为 `3` 时，完整的 `totalAmount` 会退回余额。`clientId` 不能复用，即使对应的出金失败也不行，因此如需重试：

1. 检查收款人信息（账号、密钥类型、户名），必要时更正或重新保存收款人。
2. 使用**新的** `clientId`（例如 `payout-0001-r1`）创建新的出金，并在您的系统中将其与原出金关联。

不要在循环中自动重试。如果同一收款人在没有 `failReason` 的情况下反复失败，请携带 `txId` 和 `bankOrderId` 联系 VelaFi 支持团队。

## 在商户之间划转余额

如需在不经银行转账的情况下将余额从您的一个商户划转到另一个商户，请使用 [`POST /v2/merchant/transfer`](https://docs.velafi.com/cn/api-reference/merchant/transfer-pending-funds)。两个商户都必须属于您的账户，并且在支持出金的渠道上拥有相同 `fiat` 和 `paymentId` 的已激活币种账户。

**1. 查询手续费**

使用 `fiat` 调用 [`GET /v2/merchant/transfer_fee`](https://docs.velafi.com/cn/api-reference/merchant/get-transfer-pending-funds-fee)。`feeType` `0` 表示固定金额，`1` 表示按金额比例收取（`0.01` = 1%）。划转本身返回的 `fee` 才是实际收取的金额。

**2. 创建划转**

```bash cURL theme={null}
curl -X POST https://api.velafi.com/v2/merchant/transfer \
  -H "X-BH-TOKEN: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "transfer-0001",
    "fromMerchantId": 100001,
    "toMerchantId": 100002,
    "fiat": "MXN",
    "paymentId": 105,
    "fiatAmount": 1000
  }'
```

转出方在该渠道上必须有 `fiatAmount` + 手续费的可用余额。划转在同一次调用中完成：响应返回 `fromId`（转出方的 `TRANSFER_OUT` 记录）和 `toId`（接收方的 `TRANSFER_IN` 记录），状态均为 `2`。接收方收到 `fiatAmount`，转出方被扣除 `totalFiatAmount`。

划转与出金遵循相同的 `clientId` 规则：已被您账户下任何资金记录使用的 `clientId` 会返回 `-1174`。请始终传入该值，以便在超时后查询划转。在资金记录中，`TRANSFER_OUT` 记录带有您的 `clientId`；`TRANSFER_IN` 记录带有 `TRANSFER_IN_` 加上您的 `clientId`。请通过 `txId`（`fromId`、`toId`）查询这两条记录。

## 对账

* [`GET /v2/merchant/funding/records`](https://docs.velafi.com/cn/api-reference/merchant/retrieve-funding-records) 是每笔出金和划转的唯一可信来源。按 `type=WITHDRAW`（或 `TRANSFER_OUT`、`TRANSFER_IN`）、`fiat` 和时间窗口筛选，检查您创建的每笔出金都已有最终状态（`2`、`3` 或 `4`）。对一直处于待处理状态的出金进行排查。
* [`GET /v2/merchant/fiat/flows`](https://docs.velafi.com/cn/api-reference/merchant/get-a-list-merchant-fiat-flow) 显示每一笔余额变动：创建出金时为 `WITHDRAW`，已取消或已退款的出金退回余额时为 `WITHDRAW_ROLLBACK`，划转则为 `TRANSFER_OUT` / `TRANSFER_IN`。每一行的 `total` 是该笔变动后的余额。

## 在沙盒环境中测试

使用 `https://api-test.velafi.com` 和沙盒环境凭证。使用[法币充值模拟器](https://docs.velafi.com/cn/docs/sandbox.md#sandbox-testing-tools)为测试商户注资，保存一个收款人，然后基于该余额调用 `claimfunds` 和 `transfer`。可借此测试您的请求校验、上述错误码以及基于 `clientId` 的重试逻辑。

* **Webhook 不会在沙盒环境中触发。** 请通过[获取资金记录](https://docs.velafi.com/cn/api-reference/merchant/retrieve-funding-records.md)跟踪每笔出金，并使用示例负载测试您的 Webhook 处理程序，包括已取消（`3`）和已退款（`4`）的提现。
* 模拟器无法生成失败或被退回的充值。

## 上线检查清单

* [ ] 每笔出金和划转都传入唯一的 `clientId`，且您在调用前已保存该值。
* [ ] 超时时，您会在重试前先查询该 `clientId`，并使用相同的 `clientId` 重新发送。
* [ ] 失败的出金只会在检查收款人信息后，使用新的 `clientId` 重试。
* [ ] 收款人保存在与余额相同的商户和渠道下，且只使用 `status` 为 `1` 的收款人。
* [ ] 余额检查包含手续费。
* [ ] `FUNDING_WEBHOOK` 处理程序能处理状态为 `2`、`3` 和 `4` 的 `WITHDRAW`，并能在缺少 `failReason` 时正常工作。
* [ ] 有定时任务标记超过预期结算时间仍处于待处理状态的出金。

另请参阅通用的[上线检查清单](https://docs.velafi.com/cn/docs/use-cases/go-live-checklist.md)。


## Related topics

- [核心使用场景](https://docs.velafi.com/cn/docs/use-cases/overview.md)
- [Agent Skills](https://docs.velafi.com/cn/docs/developer-tools/agent-skills.md)
- [以稳定币注资支付本地货币](https://docs.velafi.com/cn/docs/use-cases/payouts-funded-by-stablecoins.md)
- [将本地货币兑换为稳定币](https://docs.velafi.com/cn/docs/use-cases/convert-to-stablecoins.md)
- [简介](https://docs.velafi.com/cn/docs/introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.