# Convert API

> Chuyển đổi giữa các loại tiền mã hóa trực tiếp từ số dư cửa hàng của bạn — nhận báo giá trực tiếp và thực hiện theo giá thị trường.

Convert API cho phép bạn hoán đổi giữa các loại tiền tệ có trong số dư cửa hàng theo giá thị trường hiện tại — cùng một engine đang vận hành tab **Swap** trong bảng điều khiển cửa hàng, giờ đây có thể gọi trực tiếp từ backend của bạn.

> **WARNING:** Các endpoint Convert được ký bằng **API key thông thường** của bạn — cùng key dùng cho các yêu cầu [Payment API](/docs/payments), **không phải** Payout API key. Thực hiện một lệnh chuyển đổi sẽ ghi nợ và ghi có số dư cửa hàng của bạn ngay lập tức, vì vậy hãy đối xử với key này cẩn trọng như bất kỳ thông tin xác thực nào liên quan đến di chuyển tiền.

## Lấy báo giá chuyển đổi

Trả về báo giá tham khảo cho một lệnh chuyển đổi theo giá thị trường hiện tại — tỷ giá hiệu lực và số tiền kết quả. Không có khoản nào bị ghi nợ hay giữ chỗ; hãy gọi bao nhiêu lần tùy ý trước khi thực hiện.

`POST /v1/convert/price`

### Tham số yêu cầu

| Trường | Kiểu | Bắt buộc | Mô tả | Giá trị |
|--------|------|----------|-------|---------|
| `from_currency` | string | có | Đồng tiền nguồn | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | có | Đồng tiền đích. Phải khác `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | có | Số tiền cần chuyển đổi, lớn hơn `0` |  |
| `amount_type` | string | có | `amount` đề cập đến phía nào | `from`, `to` |

> **INFO:** `amount_type=from` chi tiêu chính xác `amount` bằng `from_currency`. `amount_type=to` nhận chính xác `amount` bằng `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"
  }
}
```

#### Trường phản hồi

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `success` | boolean | Báo giá có được tính toán thành công hay không |
| `from_currency` | string | Đồng tiền nguồn |
| `to_currency` | string | Đồng tiền đích |
| `amount_type` | string | Lặp lại `amount_type` của yêu cầu |
| `from_amount` | string | Số tiền sẽ bị ghi nợ bằng `from_currency` |
| `to_amount` | string | Số tiền sẽ được ghi có bằng `to_currency` |
| `effective_rate` | string | Tỷ giá áp dụng cho báo giá này — 1 đơn vị `from_currency` bằng bao nhiêu `to_currency` (đã bao gồm mức giá của nền tảng) |
| `from_amount_usd` | string \| null | Giá trị quy đổi sang USD của `from_amount` |
| `to_amount_usd` | string \| null | Giá trị quy đổi sang USD của `to_amount` |

- Báo giá này **chỉ mang tính tham khảo** — giá thị trường có thể thay đổi giữa lúc lấy báo giá và lúc gọi thực hiện.
- Lệnh gọi này không ghi nợ hay giữ chỗ bất kỳ số dư nào.

> 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

## Thực hiện chuyển đổi

Thực hiện một lệnh chuyển đổi theo giá thị trường hiện tại và cập nhật số dư cửa hàng của bạn. Không có bước riêng để "xác nhận báo giá" — hãy gọi trực tiếp endpoint này với số tiền bạn muốn chuyển đổi.

`POST /v1/convert`

> **INFO:** **Tính idempotent.** Lặp lại chính xác cùng một yêu cầu (cùng `from_currency`, `to_currency`, `amount`, `amount_type`) trong khoảng một phút sau lần gọi đầu tiên sẽ trả về lệnh chuyển đổi đã tồn tại thay vì tạo một lệnh thứ hai. Sau khoảng thời gian đó, một yêu cầu giống hệt sẽ được coi là một lệnh chuyển đổi mới — đừng thử lại một cách mù quáng khi gặp timeout mà không kiểm tra kết quả của lần gọi trước.

> **WARNING:** Endpoint này bị giới hạn ở mức **10 yêu cầu mỗi phút** cho mỗi bên gọi — nghiêm ngặt hơn giới hạn API chung — vì mỗi lệnh gọi đều di chuyển số dư thực.

### Tham số yêu cầu

| Trường | Kiểu | Bắt buộc | Mô tả | Giá trị |
|--------|------|----------|-------|---------|
| `from_currency` | string | có | Đồng tiền nguồn | `BTC`, `ETH`, `USDT`, `USDC`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `to_currency` | string | có | Đồng tiền đích. Phải khác `from_currency` | `USDT`, `USDC`, `BTC`, `ETH`, `TRX`, `BNB`, `GRAM`, `SOL`, `POL`, `LTC`, `DASH`, `DOGE`, `ZEC`, `XRP`, `XMR` |
| `amount` | decimal | có | Số tiền cần chuyển đổi, lớn hơn `0` |  |
| `amount_type` | string | có | `amount` đề cập đến phía nào | `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"
  }
}
```

#### Trường phản hồi

| Trường | Kiểu | Mô tả |
|--------|------|-------|
| `id` | int | ID lệnh chuyển đổi do hệ thống cấp |
| `type` | string | Luôn là `manual` đối với API này |
| `status` | string | Trạng thái hiện tại (xem «Trạng thái chuyển đổi» bên dưới) |
| `from_currency` | string | Đồng tiền nguồn |
| `to_currency` | string | Đồng tiền đích |
| `from_amount` | string | Số tiền bị ghi nợ bằng `from_currency` |
| `requested_from_amount` | string \| null | Số tiền nguồn bạn yêu cầu ban đầu khi `amount_type = from`. `null` khi `amount_type = to` |
| `refund_amount` | string \| null | Phần của số tiền đã ghi nợ trước được hoàn lại cho bạn sau khi khớp lệnh một phần. `null` nếu lệnh khớp hoàn toàn |
| `to_amount` | string | Số tiền được ghi có bằng `to_currency` |
| `exchange_rate` | string | Tỷ giá thực tế áp dụng cho lệnh chuyển đổi này — 1 đơn vị `from_currency` bằng bao nhiêu `to_currency` (đã bao gồm mức giá của nền tảng) |
| `fee_amount` | string | Phí nền tảng tính cho lệnh chuyển đổi này, tính bằng `from_currency` hoặc `to_currency` tùy theo hướng giao dịch. Đã được phản ánh trong `exchange_rate` — hiển thị để minh bạch |
| `from_amount_usd` | string \| null | Giá trị quy đổi sang USD của `from_amount` |
| `to_amount_usd` | string \| null | Giá trị quy đổi sang USD của `to_amount` |
| `processed_at` | string (ISO 8601) \| null | Thời điểm lệnh chuyển đổi hoàn tất thực hiện. `null` khi vẫn đang xử lý |
| `created_at` | string (ISO 8601) | Thời điểm lệnh chuyển đổi được tạo |

#### Trạng thái chuyển đổi

| Trạng thái | Mô tả |
|------------|-------|
| `pending` | Đã tạo, chưa gửi đến thị trường |
| `processing` | Số dư đã bị khóa, lệnh đã được đặt trên thị trường |
| `completed` | Đã thực hiện hoàn toàn — `to_amount` đã được ghi có vào số dư của bạn |
| `failed` | Không thể thực hiện — mọi khoản đã ghi nợ trước đó được hoàn lại tự động |
| `partially_completed` | Chỉ áp dụng cho các cặp tiền tệ không có thị trường trực tiếp (được định tuyến qua một đồng tiền trung gian): chặng đầu tiên đã hoàn tất nhưng chặng thứ hai thất bại. Bạn được ghi có bằng đồng tiền trung gian thay vì `to_currency` — hãy chuyển đổi lại từ đó để đạt được mục tiêu ban đầu |

#### 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

## Lỗi

Khi thất bại, phản hồi có `state: 1` và một `error_code` — dùng chung cho `/v1/convert/price` và `/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` | Mã trạng thái HTTP | Mô tả |
|--------------|---------------------|-------|
| `validation_failed` | 422 | Tham số không hợp lệ hoặc thiếu, hoặc bị từ chối do quy tắc nghiệp vụ (ví dụ: số dư không đủ) — xem trường `errors` để biết chi tiết |
| `amount_too_small` | 422 | `amount` thấp hơn mức giao dịch tối thiểu cho cặp tiền tệ này |
| `convert_unavailable` | 400 | Hiện không thể thực hiện lệnh chuyển đổi (dữ liệu thị trường không khả dụng hoặc không có tuyến đường giữa hai đồng tiền) — vui lòng thử lại sau |
| `internal_error` | 400 | Lỗi máy chủ nội bộ không mong muốn khi xử lý yêu cầu |

## Tự động chuyển đổi các khoản thanh toán đến

Tự động chuyển đổi là một cài đặt dự án cho hóa đơn đến và tín dụng ví tĩnh. Nó được cấu hình trong bảng điều khiển thương nhân, không phải bằng cách thêm các trường vào `/v1/payment`. Mỗi quy tắc chọn một hoặc nhiều loại tiền nguồn và một loại tiền đích.

Khi quá trình chuyển đổi hoàn tất, thông tin thanh toán và webhook của thương nhân có thể bao gồm:

```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"
  }
}
```

Các miền số tiền được tách biệt có chủ ý:

- `payment_amount` — số tiền được phát hiện trên chuỗi theo loại tiền thanh toán nguồn;
- `merchant_amount` — số tiền ròng nguồn được tính cho thương nhân trước khi chuyển đổi;
- `convert.amount` — số tiền được ghi có vào `convert.to_currency`;
- `convert.rate` và `convert.commission` — kết quả chuyển đổi đã thực hiện, không phải là giá bạn nên tính toán lại tại chỗ.

> **WARNING:** Sự thiếu hụt của `convert` có ý nghĩa: việc chuyển đổi có thể chưa hoàn tất, có thể chưa được cấu hình cho nguồn đó, hoặc có thể đã quay về tín dụng bằng đơn vị tiền tệ nguồn. Không bao giờ tự tạo một số tiền mục tiêu từ `/exchange-rates` hoặc một giá thị trường công khai.

### Thất bại tự động chuyển đổi và quay về

Chuyển đổi xảy ra sau khi nhận thanh toán blockchain. Tính khả dụng của thị trường, kích thước lệnh tối thiểu, giới hạn độ chính xác, thời gian chờ trao đổi và khả năng thanh khoản không đủ có thể làm chậm hoặc ngăn cản việc chuyển đổi.

- Các khoản gửi dưới mức tối thiểu toàn cầu/dự án sẽ bỏ qua đường ống chuyển đổi và ghi có bằng đơn vị tiền tệ nguồn.
- Các lỗi tạm thời có thể được thử lại một cách bất đồng bộ.
- Các khoản tiền gửi lớn hoặc không thể giao dịch có thể quay lại việc ghi có theo tiền tệ nguồn sau khi chính sách thử lại đã được sử dụng hết.
- Do đó, một khoản thanh toán có thể hợp lệ ngay cả khi việc chuyển đổi sang tiền tệ mục tiêu mong muốn không xảy ra.

Tích hợp của bạn nên lưu trữ khoản thanh toán đã xác minh trước, sau đó đối chiếu số tiền thực tế đã được ghi có từ thông tin thanh toán, khối `convert` tùy chọn và số dư của nhà bán hàng. Không chặn việc xác nhận webhook thanh toán trong khi chờ hệ thống phân tích hoặc thông báo của riêng bạn.

### Kiểm tra chấp nhận tự động chuyển đổi

Kiểm tra ít nhất: chuyển đổi trực tiếp thành công, chuyển đổi qua cầu/nhiều bước, lượng nhỏ dưới mức tối thiểu, thử lại tạm thời, quay về tiền tệ nguồn, thanh toán thiếu, thanh toán thừa, webhook trùng lặp, thiếu `convert`, và đối soát sau khi hết thời gian không rõ ràng.

## Các trường hợp biên của chuyển đổi thủ công

- `/v1/convert/price` là bản xem trước chỉ mang tính tham khảo; biến động thị trường có thể thay đổi kết quả thực hiện.
- `amount_type: from` sửa yêu cầu bên nguồn, trong khi `amount_type: to` yêu cầu số tiền bên mục tiêu. Không hoán đổi ý nghĩa khi trình bày giao diện xác nhận.
- Một cặp không có thị trường trực tiếp có thể được định tuyến qua một đồng tiền trung gian. Nếu chỉ một chặng được thực hiện, `partially_completed` sẽ báo cáo tín dụng trung gian.
- Nếu một lệnh thực thi hết thời gian chờ, hãy đối chiếu trước khi thử lại. Một lệnh thị trường vẫn có thể được thực hiện ngay cả khi phản hồi HTTP của nó bị mất.
- Xử lý `failed` như một trạng thái để đối chiếu, không phải là quyền để áp dụng một mục cân bằng bù tại địa phương; nền tảng sở hữu kế toán ghi nợ/hoàn tiền.