# Convert API

> 直接从商户余额中进行加密货币兑换 — 获取实时报价并按市场价格执行。

Convert API 允许您按当前市场价格在商户余额持有的各币种之间进行兑换 — 与商户后台「兑换」标签页背后使用的是同一套引擎，现在可以从您的后端直接调用。

> **WARNING:** Convert 相关接口使用您的**常规 API 密钥**签名 —— 与用于 [Payment API](/docs/payments) 请求的密钥相同，**不是** Payout API 密钥。执行兑换会立即借记并贷记您的商户余额，因此请像对待任何涉及资金的凭证一样谨慎对待此密钥。

## 获取兑换报价

按当前市场价格返回兑换的指示性报价 —— 包括有效汇率和最终金额。此调用不会扣款也不会锁定资金；您可以在执行前多次调用。

`POST /v1/convert/price`

### 请求参数

| 字段 | 类型 | 是否必填 | 说明 | 值 |
|------|------|----------|------|-----|
| `from_currency` | string | 是 | 兑换的来源币种 | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | 是 | 兑换的目标币种，必须与 `from_currency` 不同 | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | 是 | 兑换金额，须大于 `0` |  |
| `amount_type` | string | 是 | `amount` 所指代的一方 | `from`, `to` |

> **INFO:** `amount_type=from` 表示精确花费 `amount` 数量的 `from_currency`；`amount_type=to` 表示精确获得 `amount` 数量的 `to_currency`。

**🟢 200 OK** · `application/json`

```json
{
  "state": 0,
  "result": {
    "success": true,
    "from_currency": "BTC",
    "to_currency": "USDT",
    "amount_type": "from",
    "from_amount": "0.01000000",
    "to_amount": "947.86690000",
    "effective_rate": "94786.69000000",
    "from_amount_usd": "947.87",
    "to_amount_usd": "947.87"
  }
}
```

#### 响应字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | boolean | 报价是否计算成功 |
| `from_currency` | string | 来源币种 |
| `to_currency` | string | 目标币种 |
| `amount_type` | string | 与请求中的 `amount_type` 一致 |
| `from_amount` | string | 将要以 `from_currency` 扣款的金额 |
| `to_amount` | string | 将要以 `to_currency` 到账的金额 |
| `effective_rate` | string | 应用于此报价的汇率 —— 1 个单位 `from_currency` 兑换为 `to_currency` 的数量（已包含平台定价） |
| `from_amount_usd` | string \| null | `from_amount` 的美元等值 |
| `to_amount_usd` | string \| null | `to_amount` 的美元等值 |

- 该报价**仅供参考** —— 从获取报价到执行兑换之间，市场价格可能发生变化。
- 此调用不会扣款也不会锁定资金。

> Use your project UUID and the endpoint-appropriate API key from the merchant dashboard.

#### Interactive request: `POST /v1/convert/price`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## 执行兑换

按当前市场价格执行兑换并更新您的商户余额。这里没有单独的「确认报价」步骤 —— 直接使用您想要兑换的金额调用本接口即可。

`POST /v1/convert`

> **INFO:** **幂等性。** 在首次调用后约一分钟内，重复完全相同的请求（相同的 `from_currency`、`to_currency`、`amount`、`amount_type`）会返回已存在的兑换记录，而不会再创建一笔新的。超过该窗口后，相同的请求将被视为一次新的兑换 —— 遇到超时请先检查上一次调用的结果，不要盲目重试。

> **WARNING:** 本接口对每个调用方限制为**每分钟 10 次请求** —— 比通用 API 限速更严格，因为每次调用都会移动真实余额。

### 请求参数

| 字段 | 类型 | 是否必填 | 说明 | 值 |
|------|------|----------|------|-----|
| `from_currency` | string | 是 | 兑换的来源币种 | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | 是 | 兑换的目标币种，必须与 `from_currency` 不同 | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | 是 | 兑换金额，须大于 `0` |  |
| `amount_type` | string | 是 | `amount` 所指代的一方 | `from`, `to` |

**🟢 200 OK** · `application/json`

```json
{
  "state": 0,
  "result": {
    "id": 12345,
    "type": "manual",
    "status": "completed",
    "from_currency": "BTC",
    "to_currency": "USDT",
    "from_amount": "0.01000000",
    "requested_from_amount": "0.01000000",
    "refund_amount": null,
    "to_amount": "947.86690000",
    "exchange_rate": "94786.69000000",
    "fee_amount": "0.00000000",
    "from_amount_usd": "947.87",
    "to_amount_usd": "947.87",
    "processed_at": "2026-01-20T15:30:24Z",
    "created_at": "2026-01-20T15:30:22Z"
  }
}
```

#### 响应字段

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 系统分配的兑换订单 ID |
| `type` | string | 本接口始终为 `manual` |
| `status` | string | 当前状态（见下方「兑换状态」） |
| `from_currency` | string | 来源币种 |
| `to_currency` | string | 目标币种 |
| `from_amount` | string | 以 `from_currency` 计的扣款金额 |
| `requested_from_amount` | string \| null | 当 `amount_type = from` 时，您原始请求的来源金额。当 `amount_type = to` 时为 `null` |
| `refund_amount` | string \| null | 部分成交后退还给您的预扣款项部分。若订单完全成交则为 `null` |
| `to_amount` | string | 以 `to_currency` 计的到账金额 |
| `exchange_rate` | string | 本次兑换实际使用的汇率 —— 1 个单位 `from_currency` 兑换为 `to_currency` 的数量（已包含平台定价） |
| `fee_amount` | string | 本次兑换收取的平台费用，币种为 `from_currency` 或 `to_currency`（取决于交易方向）。已计入 `exchange_rate`，此处仅作透明展示 |
| `from_amount_usd` | string \| null | `from_amount` 的美元等值 |
| `to_amount_usd` | string \| null | `to_amount` 的美元等值 |
| `processed_at` | string (ISO 8601) \| null | 兑换执行完成的时间。处理中时为 `null` |
| `created_at` | string (ISO 8601) | 兑换订单创建时间 |

#### 兑换状态

| 状态 | 说明 |
|------|------|
| `pending` | 已创建，尚未提交至市场 |
| `processing` | 余额已锁定，订单已提交至市场 |
| `completed` | 已完全执行 —— `to_amount` 已到账 |
| `failed` | 执行失败 —— 已预扣的金额已自动退还 |
| `partially_completed` | 仅适用于没有直接市场（通过中间币种路由）的币种对：第一段兑换已完成，但第二段失败。您将获得中间币种而非 `to_currency` —— 请再次兑换以达成原目标 |

#### Interactive request: `POST /v1/convert`
  - `from_currency` (enum, required): BTC,ETH,USDT,USDC,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `to_currency` (enum, required): USDT,USDC,BTC,ETH,TRX,BNB,GRAM,SOL,POL,LTC,DASH,DOGE,ZEC,XRP,XMR
  - `amount` (decimal, required)
  - `amount_type` (enum, required): from,to

## 错误

失败时响应中 `state` 为 `1`，并包含 `error_code` —— `/v1/convert/price` 与 `/v1/convert` 共用：

**🔴 422 / 400** · `application/json`

```json
{
  "state": 1,
  "error_code": "amount_too_small",
  "errors": {
    "amount": "Amount is too small for this conversion. Please increase the amount and try again."
  }
}
```

| `error_code` | HTTP 状态码 | 说明 |
|--------------|-------------|------|
| `validation_failed` | 422 | 参数无效或缺失，或触发了业务规则限制（例如余额不足）—— 详见 `errors` 字段 |
| `amount_too_small` | 422 | `amount` 低于该币种对的最小可交易额度 |
| `convert_unavailable` | 400 | 当前无法执行该兑换（行情数据不可用，或两种币种之间没有可用路径）—— 请稍后重试 |
| `internal_error` | 400 | 处理请求时发生意外的服务器错误 |

## 收款自动转换

Auto-convert 是传入 invoice 和静态钱包积分的项目设置。它是在 merchant 仪表板中配置的，而不是通过向 `/v1/payment` 添加字段来配置的。每个规则选择一种或多种源货币和目标货币。

转换完成后，付款信息和 merchant webhooks 可以包括：

```json
{
  "payment_amount": "0.14800000",
  "merchant_amount": "0.146520000000000000",
  "payer_currency": "XMR",
  "convert": {
    "to_currency": "USDT",
    "commission": "0.09000000",
    "rate": "323.21000000",
    "amount": "47.262015740000000000"
  }
}
```

金额域有意分开：

- `payment_amount` — 在源支付货币中在链上检测到的内容；
- `merchant_amount` — 换算前归属于 merchant 的净来源金额；
- `convert.amount` — `convert.to_currency` 中贷记的金额；
- `convert.rate` 和 `convert.commission` — 执行的转换结果，而不是您应该在本地重新计算的价格。

> **WARNING:** 缺少 `convert` 是有意义的：转换可能尚未完成，可能未针对该源进行配置，或者可能已回退到源货币信用。切勿根据 `/exchange-rates` 或公开市场价格发明目标金额。

### Auto-convert 故障和 fallback

转换是接收区块链付款的下游。市场可用性、最小订单规模、精度限制、交易超时和可执行流动性不足可能会延迟或阻止转换。

- 低于全球/项目最低限额的存款绕过转换管道并记入源货币。
- 瞬时失败可以异步重试。
- 在 retry 政策用尽后，大额或不可交易的存款可以回落至源货币信用。
- 因此，即使未发生所需的目标货币转换，付款也可能有效。

您的集成应首先保留已验证的付款，然后根据付款信息、可选的 `convert` 块和 merchant 余额协调实际记入的货币。在等待您自己的分析或通知系统时，请勿阻止付款 webhook 的确认。

### Auto-convert 验收测试

至少测试：成功的直接转换、桥接/多跳转换、dust 低于最小值、瞬时 retry、fallback 到源货币、支付不足、支付过多、重复 webhook、缺失 `convert` 以及reconciliation 在模棱两可的 timeout 之后。

## 手动转换边箱

- `/v1/convert/price` 是指示性预览；市场变动可以改变执行结果。
- `amount_type: from` 修复源端请求，而 `amount_type: to` 请求目标端金额。呈现确认 UI 时请勿交换含义。
- 没有直接市场的货币对可以通过中间货币进行路由。如果仅完成一条腿，则 `partially_completed` 报告中间信用。
- 如果执行调用超时，请在重试之前进行协调。即使 HTTP 响应丢失，市价订单也可以执行。
- 将 `failed` 视为要调节的状态，而不是允许应用本地补偿余额条目；该平台拥有借记/退款会计。